blob: 661100b264ff600952d5b7c87602006c24b63594 [file] [log] [blame]
Philipp Schradere625ba22020-11-16 20:11:37 -08001import * as configuration from 'org_frc971/aos/configuration_generated';
Philipp Schradere625ba22020-11-16 20:11:37 -08002import {Connection} from 'org_frc971/aos/network/www/proxy';
3import * as flatbuffers_builder from 'org_frc971/external/com_github_google_flatbuffers/ts/builder';
4import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
5import {Long} from 'org_frc971/external/com_github_google_flatbuffers/ts/long';
6import * as sift from 'org_frc971/y2020/vision/sift/sift_generated'
Jim Ostrowski977850f2022-01-22 21:04:22 -08007import * as vision from 'org_frc971/frc971/vision/vision_generated';
James Kuszmaul71a81932020-12-15 21:08:01 -08008import * as web_proxy from 'org_frc971/aos/network/web_proxy_generated';
Philipp Schradere625ba22020-11-16 20:11:37 -08009
10import Channel = configuration.aos.Channel;
11import Configuration = configuration.aos.Configuration;
Philipp Schradere625ba22020-11-16 20:11:37 -080012import CameraImage = vision.frc971.vision.CameraImage;
13import ImageMatchResult = sift.frc971.vision.sift.ImageMatchResult;
14import Feature = sift.frc971.vision.sift.Feature;
James Kuszmaul71a81932020-12-15 21:08:01 -080015import SubscriberRequest = web_proxy.aos.web_proxy.SubscriberRequest;
16import ChannelRequest = web_proxy.aos.web_proxy.ChannelRequest;
17import TransferMethod = web_proxy.aos.web_proxy.TransferMethod;
Alex Perryd1969882020-03-06 21:19:00 -080018
Alex Perry5f474f22020-02-01 12:14:24 -080019export class ImageHandler {
20 private canvas = document.createElement('canvas');
Alex Perryfffe2e32020-02-29 19:48:17 -080021 private select = document.createElement('select');
22
Alex Perryb41d5782020-02-09 17:06:40 -080023 private imageBuffer: Uint8ClampedArray|null = null;
Philipp Schradere625ba22020-11-16 20:11:37 -080024 private image: CameraImage|null = null;
25 private imageTimestamp: Long|null = null;
26 private result: ImageMatchResult|null = null;
27 private resultTimestamp: Long|null = null;
Alex Perry22824d72020-02-29 17:11:43 -080028 private width = 0;
29 private height = 0;
Alex Perryfffe2e32020-02-29 19:48:17 -080030 private selectedIndex = 0;
Alex Perry22824d72020-02-29 17:11:43 -080031 private imageSkipCount = 3;
Alex Perry5f474f22020-02-01 12:14:24 -080032
Alex Perryd1969882020-03-06 21:19:00 -080033 constructor(private readonly connection: Connection) {
Alex Perryfffe2e32020-02-29 19:48:17 -080034 document.body.appendChild(this.select);
35 const defaultOption = document.createElement('option');
36 defaultOption.innerText = 'Show all features';
37 this.select.appendChild(defaultOption);
38 this.select.addEventListener('change', (ev) => this.handleSelect(ev));
Alex Perry5f474f22020-02-01 12:14:24 -080039 document.body.appendChild(this.canvas);
Alex Perryd1969882020-03-06 21:19:00 -080040
41 this.connection.addConfigHandler(() => {
James Kuszmaul527038a2020-12-21 23:40:44 -080042 this.connection.addHandler(
43 '/camera', ImageMatchResult.getFullyQualifiedName(), (data) => {
44 this.handleImageMetadata(data);
45 });
46 this.connection.addHandler(
47 '/camera', CameraImage.getFullyQualifiedName(), (data) => {
48 this.handleImage(data);
49 });
Alex Perryd1969882020-03-06 21:19:00 -080050 });
Alex Perry5f474f22020-02-01 12:14:24 -080051 }
52
Alex Perryfffe2e32020-02-29 19:48:17 -080053 handleSelect(ev: Event) {
Philipp Schradera227d042020-11-14 17:33:52 -080054 this.selectedIndex = (ev.target as HTMLSelectElement).selectedIndex;
Alex Perryfffe2e32020-02-29 19:48:17 -080055 }
56
Alex Perryb41d5782020-02-09 17:06:40 -080057 handleImage(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -080058 console.log('got an image to process');
Alex Perry22824d72020-02-29 17:11:43 -080059 if (this.imageSkipCount != 0) {
60 this.imageSkipCount--;
61 return;
62 } else {
63 this.imageSkipCount = 3;
64 }
65
Philipp Schradere625ba22020-11-16 20:11:37 -080066 const fbBuffer = new ByteBuffer(data);
67 this.image = CameraImage.getRootAsCameraImage(
68 fbBuffer as unknown as flatbuffers.ByteBuffer);
Alex Perry3dfcb812020-03-04 19:32:17 -080069 this.imageTimestamp = this.image.monotonicTimestampNs();
Alex Perry5f474f22020-02-01 12:14:24 -080070
Alex Perry3dfcb812020-03-04 19:32:17 -080071 this.width = this.image.cols();
72 this.height = this.image.rows();
Alex Perry22824d72020-02-29 17:11:43 -080073 if (this.width === 0 || this.height === 0) {
Alex Perry5f474f22020-02-01 12:14:24 -080074 return;
75 }
Alex Perry5f474f22020-02-01 12:14:24 -080076
Alex Perry3dfcb812020-03-04 19:32:17 -080077 this.draw();
78 }
79
80 convertImage(): void {
Philipp Schradere625ba22020-11-16 20:11:37 -080081 this.imageBuffer =
82 new Uint8ClampedArray(this.width * this.height * 4); // RGBA
Alex Perry5f474f22020-02-01 12:14:24 -080083 // Read four bytes (YUYV) from the data and transform into two pixels of
84 // RGBA for canvas
Philipp Schradera227d042020-11-14 17:33:52 -080085 for (let j = 0; j < this.height; j++) {
86 for (let i = 0; i < this.width; i += 2) {
Alex Perry3dfcb812020-03-04 19:32:17 -080087 const y1 = this.image.data((j * this.width + i) * 2);
88 const u = this.image.data((j * this.width + i) * 2 + 1);
89 const y2 = this.image.data((j * this.width + i + 1) * 2);
90 const v = this.image.data((j * this.width + i + 1) * 2 + 1);
Alex Perry5f474f22020-02-01 12:14:24 -080091
92 // Based on https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
93 const c1 = y1 - 16;
94 const c2 = y2 - 16;
95 const d = u - 128;
96 const e = v - 128;
97
Alex Perryfffe2e32020-02-29 19:48:17 -080098 this.imageBuffer[(j * this.width + i) * 4 + 0] =
99 (298 * c1 + 409 * e + 128) >> 8;
100 this.imageBuffer[(j * this.width + i) * 4 + 1] =
101 (298 * c1 - 100 * d - 208 * e + 128) >> 8;
102 this.imageBuffer[(j * this.width + i) * 4 + 2] =
103 (298 * c1 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -0800104 this.imageBuffer[(j * this.width + i) * 4 + 3] = 255;
Alex Perryfffe2e32020-02-29 19:48:17 -0800105 this.imageBuffer[(j * this.width + i) * 4 + 4] =
106 (298 * c2 + 409 * e + 128) >> 8;
107 this.imageBuffer[(j * this.width + i) * 4 + 5] =
108 (298 * c2 - 100 * d - 208 * e + 128) >> 8;
109 this.imageBuffer[(j * this.width + i) * 4 + 6] =
110 (298 * c2 + 516 * d + 128) >> 8;
Alex Perry22824d72020-02-29 17:11:43 -0800111 this.imageBuffer[(j * this.width + i) * 4 + 7] = 255;
Alex Perry5f474f22020-02-01 12:14:24 -0800112 }
113 }
Alex Perryb41d5782020-02-09 17:06:40 -0800114 }
115
116 handleImageMetadata(data: Uint8Array): void {
Alex Perry3dfcb812020-03-04 19:32:17 -0800117 console.log('got an image match result to process');
Philipp Schradere625ba22020-11-16 20:11:37 -0800118 const fbBuffer = new ByteBuffer(data);
119 this.result = ImageMatchResult.getRootAsImageMatchResult(
120 fbBuffer as unknown as flatbuffers.ByteBuffer);
Alex Perry22824d72020-02-29 17:11:43 -0800121 this.resultTimestamp = this.result.imageMonotonicTimestampNs();
122 this.draw();
Alex Perryb41d5782020-02-09 17:06:40 -0800123 }
124
125 draw(): void {
Alex Perryfffe2e32020-02-29 19:48:17 -0800126 if (!this.imageTimestamp || !this.resultTimestamp ||
Alex Perry22824d72020-02-29 17:11:43 -0800127 this.imageTimestamp.low !== this.resultTimestamp.low ||
128 this.imageTimestamp.high !== this.resultTimestamp.high) {
Alex Perryf23c05d2020-03-07 13:52:02 -0800129 // console.log('image and result do not match');
130 // console.log(this.imageTimestamp.low, this.resultTimestamp.low);
131 // console.log(this.imageTimestamp.high, this.resultTimestamp.high);
Alex Perryb41d5782020-02-09 17:06:40 -0800132 return;
133 }
Alex Perry3dfcb812020-03-04 19:32:17 -0800134 this.convertImage();
Alex Perry5f474f22020-02-01 12:14:24 -0800135 const ctx = this.canvas.getContext('2d');
136
Alex Perry22824d72020-02-29 17:11:43 -0800137 this.canvas.width = this.width;
138 this.canvas.height = this.height;
139 const idata = ctx.createImageData(this.width, this.height);
Alex Perryb41d5782020-02-09 17:06:40 -0800140 idata.data.set(this.imageBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -0800141 ctx.putImageData(idata, 0, 0);
Philipp Schradera227d042020-11-14 17:33:52 -0800142 console.log('features: ', this.result.featuresLength());
Alex Perryfffe2e32020-02-29 19:48:17 -0800143 if (this.selectedIndex === 0) {
Philipp Schradera227d042020-11-14 17:33:52 -0800144 for (let i = 0; i < this.result.featuresLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800145 const feature = this.result.features(i);
146 this.drawFeature(feature);
147 }
148 } else {
Alex Perryf23c05d2020-03-07 13:52:02 -0800149 console.log(this.result.imageMatchesLength(), this.result.cameraPosesLength());
Alex Perryfffe2e32020-02-29 19:48:17 -0800150 const imageMatch = this.result.imageMatches(this.selectedIndex - 1);
Philipp Schradera227d042020-11-14 17:33:52 -0800151 for (let i = 0; i < imageMatch.matchesLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800152 const featureIndex = imageMatch.matches(i).queryFeature();
153 this.drawFeature(this.result.features(featureIndex));
154 }
Alex Perryf23c05d2020-03-07 13:52:02 -0800155 // Draw center of target.
Philipp Schradera227d042020-11-14 17:33:52 -0800156 const cameraPose = this.result.cameraPoses(this.selectedIndex - 1);
Alex Perryf23c05d2020-03-07 13:52:02 -0800157 ctx.strokeStyle = 'red';
158 ctx.beginPath();
159 ctx.arc(
160 cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(),
161 cameraPose.queryTargetPointRadius(), 0, 2 * Math.PI);
162 console.log(cameraPose.queryTargetPointX(), cameraPose.queryTargetPointY(), cameraPose.queryTargetPointRadius());
163 ctx.stroke();
Alex Perryb41d5782020-02-09 17:06:40 -0800164 }
Alex Perryfffe2e32020-02-29 19:48:17 -0800165
Alex Perryfffe2e32020-02-29 19:48:17 -0800166 while (this.select.lastChild) {
167 this.select.removeChild(this.select.lastChild);
168 }
169 const defaultOption = document.createElement('option');
170 defaultOption.innerText = 'Show all features';
Philipp Schradera227d042020-11-14 17:33:52 -0800171 defaultOption.setAttribute('value', '0');
Alex Perryfffe2e32020-02-29 19:48:17 -0800172 this.select.appendChild(defaultOption);
Philipp Schradera227d042020-11-14 17:33:52 -0800173 for (let i = 0; i < this.result.imageMatchesLength(); i++) {
Alex Perryfffe2e32020-02-29 19:48:17 -0800174 const imageMatch = this.result.imageMatches(i);
175 const option = document.createElement('option');
Philipp Schradera227d042020-11-14 17:33:52 -0800176 option.setAttribute('value', (i + 1).toString());
Alex Perryfffe2e32020-02-29 19:48:17 -0800177 option.innerText =
178 `Show image ${i} features (${imageMatch.matchesLength()})`;
179 this.select.appendChild(option);
180 }
181 this.select.selectedIndex = this.selectedIndex;
182 }
183
184 // Based on OpenCV drawKeypoint.
185 private drawFeature(feature: Feature) {
186 const ctx = this.canvas.getContext('2d');
187 ctx.beginPath();
188 ctx.arc(feature.x(), feature.y(), feature.size(), 0, 2 * Math.PI);
189 ctx.stroke();
190
191 ctx.beginPath();
192 ctx.moveTo(feature.x(), feature.y());
193 const angle = feature.angle() * Math.PI / 180;
194 ctx.lineTo(
195 feature.x() + feature.size() * Math.cos(angle),
196 feature.y() + feature.size() * Math.sin(angle));
197 ctx.stroke();
Alex Perry5f474f22020-02-01 12:14:24 -0800198 }
Alex Perry5f474f22020-02-01 12:14:24 -0800199}