blob: 07cee10d8e3eb27f4ed5749359a6f71746d757fb [file] [log] [blame]
Philipp Schrader548aedf2023-02-17 20:09:13 -08001import {Channel, Configuration} from '../../aos/configuration_generated';
2import {Connection} from '../../aos/network/www/proxy';
James Kuszmauldac091f2022-03-22 09:35:06 -07003import {ByteBuffer} from 'flatbuffers';
Philipp Schrader548aedf2023-02-17 20:09:13 -08004import {ImageMatchResult, Feature} from '../vision/sift/sift_generated'
5import {CameraImage} from '../../frc971/vision/vision_generated';
Alex Perryd1969882020-03-06 21:19:00 -08006
Alex Perry5f474f22020-02-01 12:14:24 -08007export class ImageHandler {
8 private canvas = document.createElement('canvas');
Alex Perryfffe2e32020-02-29 19:48:17 -08009 private select = document.createElement('select');
10
Alex Perryb41d5782020-02-09 17:06:40 -080011 private imageBuffer: Uint8ClampedArray|null = null;
Philipp Schradere625ba22020-11-16 20:11:37 -080012 private image: CameraImage|null = null;
James Kuszmauldac091f2022-03-22 09:35:06 -070013 private imageTimestamp: BigInt|null = null;
Philipp Schradere625ba22020-11-16 20:11:37 -080014 private result: ImageMatchResult|null = null;
James Kuszmauldac091f2022-03-22 09:35:06 -070015 private resultTimestamp: BigInt|null = null;
Alex Perry22824d72020-02-29 17:11:43 -080016 private width = 0;
17 private height = 0;
Alex Perryfffe2e32020-02-29 19:48:17 -080018 private selectedIndex = 0;
Alex Perry22824d72020-02-29 17:11:43 -080019 private imageSkipCount = 3;
Alex Perry5f474f22020-02-01 12:14:24 -080020
Alex Perryd1969882020-03-06 21:19:00 -080021 constructor(private readonly connection: Connection) {
Alex Perryfffe2e32020-02-29 19:48:17 -080022 document.body.appendChild(this.select);
23 const defaultOption = document.createElement('option');
24 defaultOption.innerText = 'Show all features';
25 this.select.appendChild(defaultOption);
26 this.select.addEventListener('change', (ev) => this.handleSelect(ev));
Alex Perry5f474f22020-02-01 12:14:24 -080027 document.body.appendChild(this.canvas);
Alex Perryd1969882020-03-06 21:19:00 -080028
29 this.connection.addConfigHandler(() => {
James Kuszmaul527038a2020-12-21 23:40:44 -080030 this.connection.addHandler(
31 '/camera', ImageMatchResult.getFullyQualifiedName(), (data) => {
32 this.handleImageMetadata(data);
33 });
34 this.connection.addHandler(
35 '/camera', CameraImage.getFullyQualifiedName(), (data) => {
36 this.handleImage(data);
37 });
Alex Perryd1969882020-03-06 21:19:00 -080038 });
Alex Perry5f474f22020-02-01 12:14:24 -080039 }
40
Alex Perryfffe2e32020-02-29 19:48:17 -080041 handleSelect(ev: Event) {
Philipp Schradera227d042020-11-14 17:33:52 -080042 this.selectedIndex = (ev.target as HTMLSelectElement).selectedIndex;
Alex Perryfffe2e32020-02-29 19:48:17 -080043 }
44
Alex Perryb41d5782020-02-09 17:06:40 -080045 handleImage(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -080046 console.log('got an image to process');
Alex Perry22824d72020-02-29 17:11:43 -080047 if (this.imageSkipCount != 0) {
48 this.imageSkipCount--;
49 return;
50 } else {
51 this.imageSkipCount = 3;
52 }
53
Philipp Schradere625ba22020-11-16 20:11:37 -080054 const fbBuffer = new ByteBuffer(data);
James Kuszmauldac091f2022-03-22 09:35:06 -070055 this.image = CameraImage.getRootAsCameraImage(fbBuffer);
Alex Perry3dfcb812020-03-04 19:32:17 -080056 this.imageTimestamp = this.image.monotonicTimestampNs();
Alex Perry5f474f22020-02-01 12:14:24 -080057
Alex Perry3dfcb812020-03-04 19:32:17 -080058 this.width = this.image.cols();
59 this.height = this.image.rows();
Alex Perry22824d72020-02-29 17:11:43 -080060 if (this.width === 0 || this.height === 0) {
Alex Perry5f474f22020-02-01 12:14:24 -080061 return;
62 }
Alex Perry5f474f22020-02-01 12:14:24 -080063
Alex Perry3dfcb812020-03-04 19:32:17 -080064 this.draw();
65 }
66
67 convertImage(): void {
Philipp Schradere625ba22020-11-16 20:11:37 -080068 this.imageBuffer =
69 new Uint8ClampedArray(this.width * this.height * 4); // RGBA
Alex Perry5f474f22020-02-01 12:14:24 -080070 // Read four bytes (YUYV) from the data and transform into two pixels of
71 // RGBA for canvas
Philipp Schradera227d042020-11-14 17:33:52 -080072 for (let j = 0; j < this.height; j++) {
73 for (let i = 0; i < this.width; i += 2) {
Alex Perry3dfcb812020-03-04 19:32:17 -080074 const y1 = this.image.data((j * this.width + i) * 2);
75 const u = this.image.data((j * this.width + i) * 2 + 1);
76 const y2 = this.image.data((j * this.width + i + 1) * 2);
77 const v = this.image.data((j * this.width + i + 1) * 2 + 1);
Alex Perry5f474f22020-02-01 12:14:24 -080078
79 // Based on https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
80 const c1 = y1 - 16;
81 const c2 = y2 - 16;
82 const d = u - 128;
83 const e = v - 128;
84
Alex Perryfffe2e32020-02-29 19:48:17 -080085 this.imageBuffer[(j * this.width + i) * 4 + 0] =
86 (298 * c1 + 409 * e + 128) >> 8;
87 this.imageBuffer[(j * this.width + i) * 4 + 1] =
88 (298 * c1 - 100 * d - 208 * e + 128) >> 8;
89 this.imageBuffer[(j * this.width + i) * 4 + 2] =
90 (298 * c1 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -080091 this.imageBuffer[(j * this.width + i) * 4 + 3] = 255;
Alex Perryfffe2e32020-02-29 19:48:17 -080092 this.imageBuffer[(j * this.width + i) * 4 + 4] =
93 (298 * c2 + 409 * e + 128) >> 8;
94 this.imageBuffer[(j * this.width + i) * 4 + 5] =
95 (298 * c2 - 100 * d - 208 * e + 128) >> 8;
96 this.imageBuffer[(j * this.width + i) * 4 + 6] =
97 (298 * c2 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -080098 this.imageBuffer[(j * this.width + i) * 4 + 7] = 255;
Alex Perry5f474f22020-02-01 12:14:24 -080099 }
100 }
Alex Perryb41d5782020-02-09 17:06:40 -0800101 }
102
103 handleImageMetadata(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800104 console.log('got an image match result to process');
Philipp Schradere625ba22020-11-16 20:11:37 -0800105 const fbBuffer = new ByteBuffer(data);
James Kuszmauldac091f2022-03-22 09:35:06 -0700106 this.result = ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
Alex Perry22824d72020-02-29 17:11:43 -0800107 this.resultTimestamp = this.result.imageMonotonicTimestampNs();
108 this.draw();
Alex Perryb41d5782020-02-09 17:06:40 -0800109 }
110
111 draw(): void {
Alex Perryfffe2e32020-02-29 19:48:17 -0800112 if (!this.imageTimestamp || !this.resultTimestamp ||
James Kuszmauldac091f2022-03-22 09:35:06 -0700113 this.imageTimestamp !== this.resultTimestamp) {
Alex Perryf23c05d2020-03-07 13:52:02 -0800114 // console.log('image and result do not match');
115 // console.log(this.imageTimestamp.low, this.resultTimestamp.low);
116 // console.log(this.imageTimestamp.high, this.resultTimestamp.high);
Alex Perryb41d5782020-02-09 17:06:40 -0800117 return;
118 }
Alex Perry3dfcb812020-03-04 19:32:17 -0800119 this.convertImage();
Alex Perry5f474f22020-02-01 12:14:24 -0800120 const ctx = this.canvas.getContext('2d');
121
Alex Perry22824d72020-02-29 17:11:43 -0800122 this.canvas.width = this.width;
123 this.canvas.height = this.height;
124 const idata = ctx.createImageData(this.width, this.height);
Alex Perryb41d5782020-02-09 17:06:40 -0800125 idata.data.set(this.imageBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -0800126 ctx.putImageData(idata, 0, 0);
Philipp Schradera227d042020-11-14 17:33:52 -0800127 console.log('features: ', this.result.featuresLength());
Alex Perryfffe2e32020-02-29 19:48:17 -0800128 if (this.selectedIndex === 0) {
Philipp Schradera227d042020-11-14 17:33:52 -0800129 for (let i = 0; i < this.result.featuresLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800130 const feature = this.result.features(i);
131 this.drawFeature(feature);
132 }
133 } else {
Alex Perryf23c05d2020-03-07 13:52:02 -0800134 console.log(this.result.imageMatchesLength(), this.result.cameraPosesLength());
Alex Perryfffe2e32020-02-29 19:48:17 -0800135 const imageMatch = this.result.imageMatches(this.selectedIndex - 1);
Philipp Schradera227d042020-11-14 17:33:52 -0800136 for (let i = 0; i < imageMatch.matchesLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800137 const featureIndex = imageMatch.matches(i).queryFeature();
138 this.drawFeature(this.result.features(featureIndex));
139 }
Alex Perryf23c05d2020-03-07 13:52:02 -0800140 // Draw center of target.
Philipp Schradera227d042020-11-14 17:33:52 -0800141 const cameraPose = this.result.cameraPoses(this.selectedIndex - 1);
Alex Perryf23c05d2020-03-07 13:52:02 -0800142 ctx.strokeStyle = 'red';
143 ctx.beginPath();
144 ctx.arc(
145 cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(),
146 cameraPose.queryTargetPointRadius(), 0, 2 * Math.PI);
147 console.log(cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(), cameraPose.queryTargetPointRadius());
148 ctx.stroke();
Alex Perryb41d5782020-02-09 17:06:40 -0800149 }
Alex Perryfffe2e32020-02-29 19:48:17 -0800150
Alex Perryfffe2e32020-02-29 19:48:17 -0800151 while (this.select.lastChild) {
152 this.select.removeChild(this.select.lastChild);
153 }
154 const defaultOption = document.createElement('option');
155 defaultOption.innerText = 'Show all features';
Philipp Schradera227d042020-11-14 17:33:52 -0800156 defaultOption.setAttribute('value', '0');
Alex Perryfffe2e32020-02-29 19:48:17 -0800157 this.select.appendChild(defaultOption);
Philipp Schradera227d042020-11-14 17:33:52 -0800158 for (let i = 0; i < this.result.imageMatchesLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800159 const imageMatch = this.result.imageMatches(i);
160 const option = document.createElement('option');
Philipp Schradera227d042020-11-14 17:33:52 -0800161 option.setAttribute('value', (i + 1).toString());
Alex Perryfffe2e32020-02-29 19:48:17 -0800162 option.innerText =
163 `Show image ${i} features (${imageMatch.matchesLength()})`;
164 this.select.appendChild(option);
165 }
166 this.select.selectedIndex = this.selectedIndex;
167 }
168
169 // Based on OpenCV drawKeypoint.
170 private drawFeature(feature: Feature) {
171 const ctx = this.canvas.getContext('2d');
172 ctx.beginPath();
173 ctx.arc(feature.x(), feature.y(), feature.size(), 0, 2 * Math.PI);
174 ctx.stroke();
175
176 ctx.beginPath();
177 ctx.moveTo(feature.x(), feature.y());
178 const angle = feature.angle() * Math.PI / 180;
179 ctx.lineTo(
180 feature.x() + feature.size() * Math.cos(angle),
181 feature.y() + feature.size() * Math.sin(angle));
182 ctx.stroke();
Alex Perry5f474f22020-02-01 12:14:24 -0800183 }
Alex Perry5f474f22020-02-01 12:14:24 -0800184}