blob: a44156ac587e57f300a7abd1e8d9a39abdec2a04 [file] [log] [blame]
Alex Perryd1969882020-03-06 21:19:00 -08001import {Channel} from 'aos/configuration_generated';
2import {Connection} from 'aos/network/www/proxy';
3import {Connect} from 'aos/network/connect_generated';
Austin Schuhf6e71392020-02-26 23:10:15 -08004import {ImageMatchResult} from 'y2020/vision/sift/sift_generated'
Alex Perryd1969882020-03-06 21:19:00 -08005import {CameraImage} from 'y2020/vision/vision_generated';
6
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',
14 type: CameraImage.getFullyQualifiedName(),
15 },
16 {
17 name: '/pi2/camera',
18 type: CameraImage.getFullyQualifiedName(),
19 },
20 {
21 name: '/pi3/camera',
22 type: CameraImage.getFullyQualifiedName(),
23 },
24 {
25 name: '/pi4/camera',
26 type: CameraImage.getFullyQualifiedName(),
27 },
28 {
29 name: '/pi5/camera',
30 type: CameraImage.getFullyQualifiedName(),
31 },
32 {
33 name: '/pi1/camera/detailed',
34 type: ImageMatchResult.getFullyQualifiedName(),
35 },
36 {
37 name: '/pi2/camera/detailed',
38 type: ImageMatchResult.getFullyQualifiedName(),
39 },
40 {
41 name: '/pi3/camera/detailed',
42 type: ImageMatchResult.getFullyQualifiedName(),
43 },
44 {
45 name: '/pi4/camera/detailed',
46 type: ImageMatchResult.getFullyQualifiedName(),
47 },
48 {
49 name: '/pi5/camera/detailed',
50 type: ImageMatchResult.getFullyQualifiedName(),
51 },
52];
Alex Perry5f474f22020-02-01 12:14:24 -080053
54export class ImageHandler {
55 private canvas = document.createElement('canvas');
Alex Perryb41d5782020-02-09 17:06:40 -080056 private imageBuffer: Uint8ClampedArray|null = null;
Alex Perry3dfcb812020-03-04 19:32:17 -080057 private image: CameraImage|null = null;
Alex Perryb41d5782020-02-09 17:06:40 -080058 private imageTimestamp: flatbuffers.Long|null = null;
Austin Schuhf6e71392020-02-26 23:10:15 -080059 private result: ImageMatchResult|null = null;
Alex Perryb41d5782020-02-09 17:06:40 -080060 private resultTimestamp: flatbuffers.Long|null = null;
Alex Perry22824d72020-02-29 17:11:43 -080061 private width = 0;
62 private height = 0;
63 private imageSkipCount = 3;
Alex Perry5f474f22020-02-01 12:14:24 -080064
Alex Perryd1969882020-03-06 21:19:00 -080065 constructor(private readonly connection: Connection) {
Alex Perry5f474f22020-02-01 12:14:24 -080066 document.body.appendChild(this.canvas);
Alex Perryd1969882020-03-06 21:19:00 -080067
68 this.connection.addConfigHandler(() => {
69 this.sendConnect();
70 });
71 this.connection.addHandler(ImageMatchResult.getFullyQualifiedName(), (data) => {
72 this.handleImageMetadata(data);
73 });
74 this.connection.addHandler(CameraImage.getFullyQualifiedName(), (data) => {
75 this.handleImage(data);
76 });
77 }
78
79 private sendConnect(): void {
80 const builder = new flatbuffers.Builder(512);
81 const channels: flatbuffers.Offset[] = [];
82 for (const channel of REQUIRED_CHANNELS) {
83 const nameFb = builder.createString(channel.name);
84 const typeFb = builder.createString(channel.type);
85 Channel.startChannel(builder);
86 Channel.addName(builder, nameFb);
87 Channel.addType(builder, typeFb);
88 const channelFb = Channel.endChannel(builder);
89 channels.push(channelFb);
90 }
91
92 const channelsFb = Connect.createChannelsToTransferVector(builder, channels);
93 Connect.startConnect(builder);
94 Connect.addChannelsToTransfer(builder, channelsFb);
95 const connect = Connect.endConnect(builder);
96 builder.finish(connect);
97 this.connection.sendConnectMessage(builder);
Alex Perry5f474f22020-02-01 12:14:24 -080098 }
99
Alex Perryb41d5782020-02-09 17:06:40 -0800100 handleImage(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800101 console.log('got an image to process');
Alex Perry22824d72020-02-29 17:11:43 -0800102 if (this.imageSkipCount != 0) {
103 this.imageSkipCount--;
104 return;
105 } else {
106 this.imageSkipCount = 3;
107 }
108
Alex Perry5f474f22020-02-01 12:14:24 -0800109 const fbBuffer = new flatbuffers.ByteBuffer(data);
Alex Perry3dfcb812020-03-04 19:32:17 -0800110 this.image = CameraImage.getRootAsCameraImage(fbBuffer);
111 this.imageTimestamp = this.image.monotonicTimestampNs();
Alex Perry5f474f22020-02-01 12:14:24 -0800112
Alex Perry3dfcb812020-03-04 19:32:17 -0800113 this.width = this.image.cols();
114 this.height = this.image.rows();
Alex Perry22824d72020-02-29 17:11:43 -0800115 if (this.width === 0 || this.height === 0) {
Alex Perry5f474f22020-02-01 12:14:24 -0800116 return;
117 }
Alex Perry5f474f22020-02-01 12:14:24 -0800118
Alex Perry3dfcb812020-03-04 19:32:17 -0800119 this.draw();
120 }
121
122 convertImage(): void {
123 this.imageBuffer = new Uint8ClampedArray(this.width * this.height * 4); // RGBA
Alex Perry5f474f22020-02-01 12:14:24 -0800124 // Read four bytes (YUYV) from the data and transform into two pixels of
125 // RGBA for canvas
Alex Perry22824d72020-02-29 17:11:43 -0800126 for (const j = 0; j < this.height; j++) {
127 for (const i = 0; i < this.width; i += 2) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800128 const y1 = this.image.data((j * this.width + i) * 2);
129 const u = this.image.data((j * this.width + i) * 2 + 1);
130 const y2 = this.image.data((j * this.width + i + 1) * 2);
131 const v = this.image.data((j * this.width + i + 1) * 2 + 1);
Alex Perry5f474f22020-02-01 12:14:24 -0800132
133 // Based on https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
134 const c1 = y1 - 16;
135 const c2 = y2 - 16;
136 const d = u - 128;
137 const e = v - 128;
138
Alex Perry22824d72020-02-29 17:11:43 -0800139 this.imageBuffer[(j * this.width + i) * 4 + 0] = (298 * c1 + 409 * e + 128) >> 8;
140 this.imageBuffer[(j * this.width + i) * 4 + 1] = (298 * c1 - 100 * d - 208 * e + 128) >> 8;
141 this.imageBuffer[(j * this.width + i) * 4 + 2] = (298 * c1 + 516 * d + 128) >> 8;
142 this.imageBuffer[(j * this.width + i) * 4 + 3] = 255;
143 this.imageBuffer[(j * this.width + i) * 4 + 4] = (298 * c2 + 409 * e + 128) >> 8;
144 this.imageBuffer[(j * this.width + i) * 4 + 5] = (298 * c2 - 100 * d - 208 * e + 128) >> 8;
145 this.imageBuffer[(j * this.width + i) * 4 + 6] = (298 * c2 + 516 * d + 128) >> 8;
146 this.imageBuffer[(j * this.width + i) * 4 + 7] = 255;
Alex Perry5f474f22020-02-01 12:14:24 -0800147 }
148 }
Alex Perryb41d5782020-02-09 17:06:40 -0800149 }
150
151 handleImageMetadata(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800152 console.log('got an image match result to process');
Alex Perryb41d5782020-02-09 17:06:40 -0800153 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuhf6e71392020-02-26 23:10:15 -0800154 this.result = ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
Alex Perry22824d72020-02-29 17:11:43 -0800155 this.resultTimestamp = this.result.imageMonotonicTimestampNs();
156 this.draw();
Alex Perryb41d5782020-02-09 17:06:40 -0800157 }
158
159 draw(): void {
Alex Perry22824d72020-02-29 17:11:43 -0800160 if (!this.imageTimestamp || !this.resultTimestamp ||
161 this.imageTimestamp.low !== this.resultTimestamp.low ||
162 this.imageTimestamp.high !== this.resultTimestamp.high) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800163 console.log('image and result do not match');
164 console.log(this.imageTimestamp.low, this.resultTimestamp.low);
165 console.log(this.imageTimestamp.high, this.resultTimestamp.high);
Alex Perryb41d5782020-02-09 17:06:40 -0800166 return;
167 }
Alex Perry3dfcb812020-03-04 19:32:17 -0800168 this.convertImage();
Alex Perry5f474f22020-02-01 12:14:24 -0800169 const ctx = this.canvas.getContext('2d');
170
Alex Perry22824d72020-02-29 17:11:43 -0800171 this.canvas.width = this.width;
172 this.canvas.height = this.height;
173 const idata = ctx.createImageData(this.width, this.height);
Alex Perryb41d5782020-02-09 17:06:40 -0800174 idata.data.set(this.imageBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -0800175 ctx.putImageData(idata, 0, 0);
Alex Perry22824d72020-02-29 17:11:43 -0800176 for (const i = 0; i < this.result.featuresLength(); i++) {
177 const feature = this.result.features(i);
Alex Perryb41d5782020-02-09 17:06:40 -0800178 // Based on OpenCV drawKeypoint.
Alex Perry22824d72020-02-29 17:11:43 -0800179 ctx.beginPath();
180 ctx.arc(feature.x(), feature.y(), feature.size(), 0, 2 * Math.PI);
181 ctx.stroke();
182
183 ctx.beginPath();
184 ctx.moveTo(feature.x(), feature.y());
185 const angle = feature.angle() * Math.PI / 180;
Alex Perryb41d5782020-02-09 17:06:40 -0800186 ctx.lineTo(
Alex Perry22824d72020-02-29 17:11:43 -0800187 feature.x() + feature.size() * Math.cos(angle),
188 feature.y() + feature.size() * Math.sin(angle));
189 ctx.stroke();
Alex Perryb41d5782020-02-09 17:06:40 -0800190 }
Alex Perry5f474f22020-02-01 12:14:24 -0800191 }
Alex Perry5f474f22020-02-01 12:14:24 -0800192}