blob: e48fbfb4b21b1dcc7c1afabe4e094af618f091e9 [file] [log] [blame]
Austin Schuh7c75e582020-11-14 16:41:18 -08001import {aos} from 'aos/configuration_generated';
Alex Perryd1969882020-03-06 21:19:00 -08002import {Connection} from 'aos/network/www/proxy';
Austin Schuh7c75e582020-11-14 16:41:18 -08003import {aos} from 'aos/network/connect_generated';
4import {frc971} from 'y2020/vision/sift/sift_generated'
5import {frc971} from 'y2020/vision/vision_generated';
Alex Perryd1969882020-03-06 21:19:00 -08006
7/*
8 * All the messages that are required to show an image with metadata.
9 * Messages not readable on the server node are ignored.
10 */
11const REQUIRED_CHANNELS = [
12 {
13 name: '/pi1/camera',
Austin Schuh7c75e582020-11-14 16:41:18 -080014 type: frc971.vision.CameraImage.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080015 },
16 {
17 name: '/pi2/camera',
Austin Schuh7c75e582020-11-14 16:41:18 -080018 type: frc971.vision.CameraImage.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080019 },
20 {
21 name: '/pi3/camera',
Austin Schuh7c75e582020-11-14 16:41:18 -080022 type: frc971.vision.CameraImage.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080023 },
24 {
25 name: '/pi4/camera',
Austin Schuh7c75e582020-11-14 16:41:18 -080026 type: frc971.vision.CameraImage.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080027 },
28 {
29 name: '/pi5/camera',
Austin Schuh7c75e582020-11-14 16:41:18 -080030 type: frc971.vision.CameraImage.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080031 },
32 {
33 name: '/pi1/camera/detailed',
Austin Schuh7c75e582020-11-14 16:41:18 -080034 type: frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080035 },
36 {
37 name: '/pi2/camera/detailed',
Austin Schuh7c75e582020-11-14 16:41:18 -080038 type: frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080039 },
40 {
41 name: '/pi3/camera/detailed',
Austin Schuh7c75e582020-11-14 16:41:18 -080042 type: frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080043 },
44 {
45 name: '/pi4/camera/detailed',
Austin Schuh7c75e582020-11-14 16:41:18 -080046 type: frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080047 },
48 {
49 name: '/pi5/camera/detailed',
Austin Schuh7c75e582020-11-14 16:41:18 -080050 type: frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(),
Alex Perryd1969882020-03-06 21:19:00 -080051 },
52];
Alex Perry5f474f22020-02-01 12:14:24 -080053
54export class ImageHandler {
55 private canvas = document.createElement('canvas');
Alex Perryfffe2e32020-02-29 19:48:17 -080056 private select = document.createElement('select');
57
Alex Perryb41d5782020-02-09 17:06:40 -080058 private imageBuffer: Uint8ClampedArray|null = null;
Austin Schuh7c75e582020-11-14 16:41:18 -080059 private image: frc971.vision.CameraImage|null = null;
Alex Perryb41d5782020-02-09 17:06:40 -080060 private imageTimestamp: flatbuffers.Long|null = null;
Austin Schuh7c75e582020-11-14 16:41:18 -080061 private result: frc971.vision.sift.ImageMatchResult|null = null;
Alex Perryb41d5782020-02-09 17:06:40 -080062 private resultTimestamp: flatbuffers.Long|null = null;
Alex Perry22824d72020-02-29 17:11:43 -080063 private width = 0;
64 private height = 0;
Alex Perryfffe2e32020-02-29 19:48:17 -080065 private selectedIndex = 0;
Alex Perry22824d72020-02-29 17:11:43 -080066 private imageSkipCount = 3;
Alex Perry5f474f22020-02-01 12:14:24 -080067
Alex Perryd1969882020-03-06 21:19:00 -080068 constructor(private readonly connection: Connection) {
Alex Perryfffe2e32020-02-29 19:48:17 -080069 document.body.appendChild(this.select);
70 const defaultOption = document.createElement('option');
71 defaultOption.innerText = 'Show all features';
72 this.select.appendChild(defaultOption);
73 this.select.addEventListener('change', (ev) => this.handleSelect(ev));
Alex Perry5f474f22020-02-01 12:14:24 -080074 document.body.appendChild(this.canvas);
Alex Perryd1969882020-03-06 21:19:00 -080075
76 this.connection.addConfigHandler(() => {
77 this.sendConnect();
78 });
Austin Schuh7c75e582020-11-14 16:41:18 -080079 this.connection.addHandler(
80 frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(), (data) => {
81 this.handleImageMetadata(data);
82 });
83 this.connection.addHandler(
84 frc971.vision.CameraImage.getFullyQualifiedName(), (data) => {
85 this.handleImage(data);
86 });
Alex Perryd1969882020-03-06 21:19:00 -080087 }
88
89 private sendConnect(): void {
90 const builder = new flatbuffers.Builder(512);
91 const channels: flatbuffers.Offset[] = [];
92 for (const channel of REQUIRED_CHANNELS) {
93 const nameFb = builder.createString(channel.name);
94 const typeFb = builder.createString(channel.type);
Austin Schuh7c75e582020-11-14 16:41:18 -080095 aos.Channel.startChannel(builder);
96 aos.Channel.addName(builder, nameFb);
97 aos.Channel.addType(builder, typeFb);
98 const channelFb = aos.Channel.endChannel(builder);
Alex Perryd1969882020-03-06 21:19:00 -080099 channels.push(channelFb);
100 }
101
Austin Schuh7c75e582020-11-14 16:41:18 -0800102 const channelsFb = aos.message_bridge.Connect.createChannelsToTransferVector(builder, channels);
103 aos.message_bridge.Connect.startConnect(builder);
104 aos.message_bridge.Connect.addChannelsToTransfer(builder, channelsFb);
105 const connect = aos.message_bridge.Connect.endConnect(builder);
Alex Perryd1969882020-03-06 21:19:00 -0800106 builder.finish(connect);
107 this.connection.sendConnectMessage(builder);
Alex Perry5f474f22020-02-01 12:14:24 -0800108 }
109
Alex Perryfffe2e32020-02-29 19:48:17 -0800110 handleSelect(ev: Event) {
111 this.selectedIndex = ev.target.selectedIndex;
112 }
113
Alex Perryb41d5782020-02-09 17:06:40 -0800114 handleImage(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800115 console.log('got an image to process');
Alex Perry22824d72020-02-29 17:11:43 -0800116 if (this.imageSkipCount != 0) {
117 this.imageSkipCount--;
118 return;
119 } else {
120 this.imageSkipCount = 3;
121 }
122
Alex Perry5f474f22020-02-01 12:14:24 -0800123 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuh7c75e582020-11-14 16:41:18 -0800124 this.image = frc971.vision.CameraImage.getRootAsCameraImage(fbBuffer);
Alex Perry3dfcb812020-03-04 19:32:17 -0800125 this.imageTimestamp = this.image.monotonicTimestampNs();
Alex Perry5f474f22020-02-01 12:14:24 -0800126
Alex Perry3dfcb812020-03-04 19:32:17 -0800127 this.width = this.image.cols();
128 this.height = this.image.rows();
Alex Perry22824d72020-02-29 17:11:43 -0800129 if (this.width === 0 || this.height === 0) {
Alex Perry5f474f22020-02-01 12:14:24 -0800130 return;
131 }
Alex Perry5f474f22020-02-01 12:14:24 -0800132
Alex Perry3dfcb812020-03-04 19:32:17 -0800133 this.draw();
134 }
135
136 convertImage(): void {
137 this.imageBuffer = new Uint8ClampedArray(this.width * this.height * 4); // RGBA
Alex Perry5f474f22020-02-01 12:14:24 -0800138 // Read four bytes (YUYV) from the data and transform into two pixels of
139 // RGBA for canvas
Alex Perry22824d72020-02-29 17:11:43 -0800140 for (const j = 0; j < this.height; j++) {
141 for (const i = 0; i < this.width; i += 2) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800142 const y1 = this.image.data((j * this.width + i) * 2);
143 const u = this.image.data((j * this.width + i) * 2 + 1);
144 const y2 = this.image.data((j * this.width + i + 1) * 2);
145 const v = this.image.data((j * this.width + i + 1) * 2 + 1);
Alex Perry5f474f22020-02-01 12:14:24 -0800146
147 // Based on https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
148 const c1 = y1 - 16;
149 const c2 = y2 - 16;
150 const d = u - 128;
151 const e = v - 128;
152
Alex Perryfffe2e32020-02-29 19:48:17 -0800153 this.imageBuffer[(j * this.width + i) * 4 + 0] =
154 (298 * c1 + 409 * e + 128) >> 8;
155 this.imageBuffer[(j * this.width + i) * 4 + 1] =
156 (298 * c1 - 100 * d - 208 * e + 128) >> 8;
157 this.imageBuffer[(j * this.width + i) * 4 + 2] =
158 (298 * c1 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -0800159 this.imageBuffer[(j * this.width + i) * 4 + 3] = 255;
Alex Perryfffe2e32020-02-29 19:48:17 -0800160 this.imageBuffer[(j * this.width + i) * 4 + 4] =
161 (298 * c2 + 409 * e + 128) >> 8;
162 this.imageBuffer[(j * this.width + i) * 4 + 5] =
163 (298 * c2 - 100 * d - 208 * e + 128) >> 8;
164 this.imageBuffer[(j * this.width + i) * 4 + 6] =
165 (298 * c2 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -0800166 this.imageBuffer[(j * this.width + i) * 4 + 7] = 255;
Alex Perry5f474f22020-02-01 12:14:24 -0800167 }
168 }
Alex Perryb41d5782020-02-09 17:06:40 -0800169 }
170
171 handleImageMetadata(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800172 console.log('got an image match result to process');
Alex Perryb41d5782020-02-09 17:06:40 -0800173 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuh7c75e582020-11-14 16:41:18 -0800174 this.result =
175 frc971.vision.sift.ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
Alex Perry22824d72020-02-29 17:11:43 -0800176 this.resultTimestamp = this.result.imageMonotonicTimestampNs();
177 this.draw();
Alex Perryb41d5782020-02-09 17:06:40 -0800178 }
179
180 draw(): void {
Alex Perryfffe2e32020-02-29 19:48:17 -0800181 if (!this.imageTimestamp || !this.resultTimestamp ||
Alex Perry22824d72020-02-29 17:11:43 -0800182 this.imageTimestamp.low !== this.resultTimestamp.low ||
183 this.imageTimestamp.high !== this.resultTimestamp.high) {
Alex Perryf23c05d2020-03-07 13:52:02 -0800184 // console.log('image and result do not match');
185 // console.log(this.imageTimestamp.low, this.resultTimestamp.low);
186 // console.log(this.imageTimestamp.high, this.resultTimestamp.high);
Alex Perryb41d5782020-02-09 17:06:40 -0800187 return;
188 }
Alex Perry3dfcb812020-03-04 19:32:17 -0800189 this.convertImage();
Alex Perry5f474f22020-02-01 12:14:24 -0800190 const ctx = this.canvas.getContext('2d');
191
Alex Perry22824d72020-02-29 17:11:43 -0800192 this.canvas.width = this.width;
193 this.canvas.height = this.height;
194 const idata = ctx.createImageData(this.width, this.height);
Alex Perryb41d5782020-02-09 17:06:40 -0800195 idata.data.set(this.imageBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -0800196 ctx.putImageData(idata, 0, 0);
Alex Perryfffe2e32020-02-29 19:48:17 -0800197 console.log('features: ', this.result.featuresLength();
198 if (this.selectedIndex === 0) {
199 for (const i = 0; i < this.result.featuresLength(); i++) {
200 const feature = this.result.features(i);
201 this.drawFeature(feature);
202 }
203 } else {
Alex Perryf23c05d2020-03-07 13:52:02 -0800204 console.log(this.result.imageMatchesLength(), this.result.cameraPosesLength());
Alex Perryfffe2e32020-02-29 19:48:17 -0800205 const imageMatch = this.result.imageMatches(this.selectedIndex - 1);
206 for (const i = 0; i < imageMatch.matchesLength(); i++) {
207 const featureIndex = imageMatch.matches(i).queryFeature();
208 this.drawFeature(this.result.features(featureIndex));
209 }
Alex Perryf23c05d2020-03-07 13:52:02 -0800210 // Draw center of target.
211 const cameraPose = this.result.cameraPoses(this.selctedIndex - 1);
212 ctx.strokeStyle = 'red';
213 ctx.beginPath();
214 ctx.arc(
215 cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(),
216 cameraPose.queryTargetPointRadius(), 0, 2 * Math.PI);
217 console.log(cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(), cameraPose.queryTargetPointRadius());
218 ctx.stroke();
Alex Perryb41d5782020-02-09 17:06:40 -0800219 }
Alex Perryfffe2e32020-02-29 19:48:17 -0800220
Alex Perryfffe2e32020-02-29 19:48:17 -0800221 while (this.select.lastChild) {
222 this.select.removeChild(this.select.lastChild);
223 }
224 const defaultOption = document.createElement('option');
225 defaultOption.innerText = 'Show all features';
226 defaultOption.setAttribute('value', 0);
227 this.select.appendChild(defaultOption);
228 for (const i = 0; i < this.result.imageMatchesLength(); i++) {
229 const imageMatch = this.result.imageMatches(i);
230 const option = document.createElement('option');
231 option.setAttribute('value', i + 1);
232 option.innerText =
233 `Show image ${i} features (${imageMatch.matchesLength()})`;
234 this.select.appendChild(option);
235 }
236 this.select.selectedIndex = this.selectedIndex;
237 }
238
239 // Based on OpenCV drawKeypoint.
240 private drawFeature(feature: Feature) {
241 const ctx = this.canvas.getContext('2d');
242 ctx.beginPath();
243 ctx.arc(feature.x(), feature.y(), feature.size(), 0, 2 * Math.PI);
244 ctx.stroke();
245
246 ctx.beginPath();
247 ctx.moveTo(feature.x(), feature.y());
248 const angle = feature.angle() * Math.PI / 180;
249 ctx.lineTo(
250 feature.x() + feature.size() * Math.cos(angle),
251 feature.y() + feature.size() * Math.sin(angle));
252 ctx.stroke();
Alex Perry5f474f22020-02-01 12:14:24 -0800253 }
Alex Perry5f474f22020-02-01 12:14:24 -0800254}