changeset 2419:e3af0ac507c8

new exposure-finder algorithm
author drewp@bigasterisk.com
date Tue, 21 May 2024 14:08:17 -0700
parents 9bb0eb587d5b
children d5750b2aaa9e
files package.json pnpm-lock.yaml web/calibrate/FindSafeExposure.ts web/calibrate/Light9Calibrate.ts web/calibrate/Light9Camera.ts web/calibrate/XyPlot.ts
diffstat 6 files changed, 117 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/package.json	Tue May 21 11:58:13 2024 -0700
+++ b/package.json	Tue May 21 14:08:17 2024 -0700
@@ -18,6 +18,7 @@
     "@types/d3": "^7.4.3",
     "@types/debug": "^4.1.12",
     "@types/fpsmeter": "^0.3.34",
+    "@types/lodash": "^4.17.4",
     "@types/n3": "^1.16.4",
     "@types/node": "^20.12.11",
     "@types/reconnectingwebsocket": "^1.0.10",
@@ -32,6 +33,7 @@
     "immutable": "^4.3.5",
     "knockout": "^3.5.1",
     "lit": "^2.8.0",
+    "lodash": "^4.17.21",
     "n3": "^1.17.3",
     "onecolor": "^4.1.0",
     "parse-prometheus-text-format": "^1.1.1",
--- a/pnpm-lock.yaml	Tue May 21 11:58:13 2024 -0700
+++ b/pnpm-lock.yaml	Tue May 21 14:08:17 2024 -0700
@@ -32,6 +32,9 @@
   '@types/fpsmeter':
     specifier: ^0.3.34
     version: 0.3.34
+  '@types/lodash':
+    specifier: ^4.17.4
+    version: 4.17.4
   '@types/n3':
     specifier: ^1.16.4
     version: 1.16.4
@@ -74,6 +77,9 @@
   lit:
     specifier: ^2.8.0
     version: 2.8.0
+  lodash:
+    specifier: ^4.17.21
+    version: 4.17.21
   n3:
     specifier: ^1.17.3
     version: 1.17.3
@@ -935,6 +941,10 @@
     resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
     dev: false
 
+  /@types/lodash@4.17.4:
+    resolution: {integrity: sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==}
+    dev: false
+
   /@types/ms@0.7.34:
     resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
     dev: false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/calibrate/FindSafeExposure.ts	Tue May 21 14:08:17 2024 -0700
@@ -0,0 +1,82 @@
+import { clamp } from "lodash";
+import { sleep } from "./Light9Calibrate";
+import { Light9Camera } from "./Light9Camera";
+import { XyPlot } from "./XyPlot";
+
+export class FindSafeExposure {
+  expoMin: number;
+  expoMax: number;
+  expo: number;
+  expoStep: number;
+  sameStateSteps: number;
+
+  constructor(public cam: Light9Camera, public plot: XyPlot) {
+    const fixedSteps = 5;
+
+    this.plot.clear();
+    this.expoMin = 1;
+    this.expoMax = 800;
+    this.expo = this.expoMin;
+    this.expoStep = (this.expoMax - this.expoMin) / fixedSteps;
+    this.sameStateSteps = 0;
+  }
+
+  async run() {
+    while (true) {
+      const currentOverexposed = await this.gatherSample();
+      this.step(currentOverexposed);
+      if (this.plot.data.length > 50 || Math.abs(this.expoStep) < 1) {
+        this.expo = this.expoMin;
+        await this.gatherSample();
+        break;
+      }
+    }
+  }
+  
+  step(currentOverexposed: number) {
+    const maxAllowedOverexposedPixels = 5;
+    const turnaroundScale = 0.6;
+    const stepsPerSide = 3;
+
+    const overexposed = currentOverexposed > maxAllowedOverexposedPixels;
+    if (this.expoStep > 0) {
+      if (overexposed) {
+        this.sameStateSteps += 1;
+      }
+    } else {
+      if (!overexposed) {
+        this.sameStateSteps += 1;
+      }
+    }
+    this.plot.setXMarklines([
+      { txt: "min", x: this.expoMin },
+      { txt: "max", x: this.expoMax },
+      { txt: "expo", x: this.expo },
+    ]);
+    const nextExpo = clamp(this.expo + this.expoStep, this.expoMin, this.expoMax);
+    if (this.sameStateSteps > stepsPerSide || nextExpo <= this.expoMin || nextExpo >= this.expoMax) {
+      if (this.expoStep > 0) {
+        this.expoMax = this.expo;
+      } else {
+        this.expoMin = this.expo;
+      }
+      this.expoStep = this.expoStep * -1 * turnaroundScale;
+      this.sameStateSteps = 0;
+    }
+    this.expo = clamp(this.expo + this.expoStep, this.expoMin, this.expoMax);
+  }
+  
+  async gatherSample() {
+    const settleMs = 200;
+
+    await this.cam.set("exposureTime", this.expo);
+    const settleUntil = Date.now() + settleMs;
+    let miny = this.cam.saturatedPixelCount!;
+    while (Date.now() < settleUntil) {
+      await sleep(1000 / this.cam.videoSettings.frameRate);
+      miny = Math.min(miny, this.cam.saturatedPixelCount!);
+    }
+    this.plot!.insertPoint(this.expo, clamp(miny, 0, 100));
+    return miny;
+  }
+}
--- a/web/calibrate/Light9Calibrate.ts	Tue May 21 11:58:13 2024 -0700
+++ b/web/calibrate/Light9Calibrate.ts	Tue May 21 14:08:17 2024 -0700
@@ -4,15 +4,17 @@
 import { CollectorClient } from "../collector/CollectorClient";
 import { getTopGraph } from "../RdfdbSyncedGraph";
 import { SyncedGraph } from "../SyncedGraph";
+import { FindSafeExposure } from "./FindSafeExposure";
 import { Light9Camera } from "./Light9Camera";
 import { XyPlot } from "./XyPlot";
 export { RdfdbSyncedGraph } from "../RdfdbSyncedGraph";
 export { Light9Camera } from "./Light9Camera";
 export { XyPlot } from "./XyPlot";
+
 debug.enable("*");
 const log = debug("calibrate");
 
-async function sleep(ms: number) {
+export async function sleep(ms: number) {
   return new Promise((resolve) => setTimeout(resolve, ms));
 }
 
@@ -78,37 +80,8 @@
 
   async findSafeExposure(ev: MouseEvent) {
     await this.withButtonSpinner(ev, async () => {
-
-      const gatherSample = async (expo: number) => {
-        await this.cam?.set("exposureTime", expo);
-        const settleUntil = Date.now() + 1000;
-        let miny = this.cam?.saturatedPixelCount!;
-        while (Date.now() < settleUntil) {
-          await sleep(50);
-          miny = Math.min(miny, this.cam?.saturatedPixelCount!);
-        }
-        this.plot!.insertPoint(expo, miny);
-      };
-
-      // todo: drive around without big skips, gradually slower, looking for the max workable expo
-      let fixedSteps = 8;
-      const expoMin = 1;
-      const expoMax = 200;
-      let expo = 0;
-      const data=this.plot!.data;
-      while (data.length < 20) {
-        if (data.length < fixedSteps + 1) {
-          expo = expoMin + ((expoMax - expoMin) / fixedSteps) * data.length;
-        } else {
-          let x2 = data.findIndex(([_, y]) => y > 2);
-          if (x2 < 1) x2 = 1;
-          const x1 = x2 - 1;
-          log(JSON.stringify([x1, data[x1], x2, data[x2]]));
-          expo = (data[x1][0] + data[x2][0]) / 2;
-          log(data);
-        }
-        await gatherSample(expo);
-      }
+      const algo = new FindSafeExposure(this.cam!, this.plot!);
+      await algo.run();
     });
   }
   async setToZero(ev: MouseEvent) {
--- a/web/calibrate/Light9Camera.ts	Tue May 21 11:58:13 2024 -0700
+++ b/web/calibrate/Light9Camera.ts	Tue May 21 14:08:17 2024 -0700
@@ -179,7 +179,7 @@
     focusDistance: { min: "0", max: "1023" },
     brightness: { min: "0", max: "64" },
     colorTemperature: { min: "2800", max: "6500" },
-    exposureTime: { min: "0", max: "400" },
+    exposureTime: { min: "0", max: "800" },
   };
 
   @property() cam!: Light9Camera;
--- a/web/calibrate/XyPlot.ts	Tue May 21 11:58:13 2024 -0700
+++ b/web/calibrate/XyPlot.ts	Tue May 21 14:08:17 2024 -0700
@@ -18,7 +18,7 @@
   chart!: echarts.ECharts;
   @property() label: string = "";
   @property() data: number[][] = [];
- 
+
   render() {
     return html`
       <fieldset>
@@ -52,10 +52,25 @@
   clear() {
     this.data.length = 0;
   }
-  
+
   insertPoint(x: number, y: number) {
     this.data.push([x, y]);
     this.data.sort((a, b) => a[0] - b[0]);
     this.chart.setOption({ series: [{ name: "d", data: this.data }] });
   }
+  setXMarklines(lines: { txt: string; x: number }[]) {
+    const markLineData = (row: { txt: string; x: number }, i: number) => ({ name: row.txt, label: { distance: 10*i, formatter: "{b} {c}", color: "#fff", textBorderWidth: 0 }, xAxis: row.x });
+    this.chart.setOption(
+      {
+        series: [
+          {
+            name: "d",
+            markLine: {
+              data: lines.map(markLineData),
+            },
+          },
+        ],
+      } //
+    );
+  }
 }