Переглянути джерело

varvusensor: calibrate.py — tume+valge referents (R=(S-D)/(W-D))

ardo 1 тиждень тому
батько
коміт
8f1db39437
1 змінених файлів з 55 додано та 26 видалено
  1. 55 26
      dev/varvusensor/collector/calibrate.py

+ 55 - 26
dev/varvusensor/collector/calibrate.py

@@ -1,17 +1,23 @@
 """Kalibreerimise abivahend AS7341 värviandurile.
 
 Loeb ClickHouse'ist (vaikimisi uus67.varvus) ja toetab:
-  * capture-white: salvesta valge referents (viimased N mõõtmist) -> calibration.json
-  * show:          näita viimaseid mõõtmisi (toores hex vs kalibreeritud hex)
+  * capture-dark:  salvesta tume referents (andur kaetud) -> calibration.json
+  * capture-white: salvesta valge referents (valge pind, SAMA valgustus) -> calibration.json
+  * show:          näita viimaseid mõõtmisi (toores vs tume+valge kalibreeritud)
 
-Kalibreeritud värvus = iga kanal jagatud valge referentsiga (reflectance),
-mis eemaldab valgustuse värvitooni ja anduri kanalite erinevuse.
+Kalibreeritud reflekteeruvus:  R = (S - D) / (W - D)  iga kanali kohta.
+  * Tume (D) eemaldab tumedavoolu ja plaadi toite-LED-i (roheline) panuse.
+  * Valge (W) eemaldab valgustuse spektri ja kanalite tundlikkuse erinevuse.
+
+Protseduur:
+  1. Fikseeri valgustus ja geomeetria (nurk, kaugus) kogu seeria jooksul.
+  2. Kata andur (täiesti pime) -> capture-dark --last 5
+  3. Mõõda valge pind (nt valge paber/PTFE) -> capture-white --last 5
+  4. Mõõda proovid -> show --last 10
 
 Käivitamine (projekti .venv):
   CH_URL=http://100.87.1.24:8125 CH_USER=zigbee CH_PASS=... \
-    .venv/bin/python varvusensor/collector/calibrate.py capture-white --last 5
-  CH_URL=http://100.87.1.24:8125 CH_USER=zigbee CH_PASS=... \
-    .venv/bin/python varvusensor/collector/calibrate.py show --last 10
+    .venv/bin/python varvusensor/collector/calibrate.py capture-dark --last 5
 """
 from __future__ import annotations
 
@@ -55,43 +61,62 @@ def fetch_rows(last: int) -> list[dict]:
     return [json.loads(line) for line in ch_query(sql).strip().splitlines() if line]
 
 
-def capture_white(last: int) -> int:
+def load_calib() -> dict:
+    if not CALIB_FILE.exists():
+        return {}
+    try:
+        return json.loads(CALIB_FILE.read_text())
+    except Exception:
+        return {}
+
+
+def save_calib(data: dict) -> None:
+    CALIB_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False))
+
+
+def capture_reference(kind: str, last: int) -> int:
     rows = fetch_rows(last)
     if not rows:
-        print("Andmeid pole — mõõda esmalt valget pinda.")
+        print("Andmeid pole — mõõda esmalt (kaetud andur / valge pind).")
         return 1
     ref: dict[str, float] = {}
     for field in FIELDS:
         values = [r[field] for r in rows if r.get(field) is not None]
         ref[field] = round(statistics.mean(values), 3) if values else 0.0
-    payload = {"reference_rows": len(rows), "white": ref}
-    CALIB_FILE.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
-    print(f"Valge referents salvestatud ({len(rows)} reast) -> {CALIB_FILE}")
+    data = load_calib()
+    data[kind] = ref
+    data[f"{kind}_rows"] = len(rows)
+    save_calib(data)
+    label = "Tume" if kind == "dark" else "Valge"
+    print(f"{label} referents salvestatud ({len(rows)} reast) -> {CALIB_FILE}")
     print(json.dumps(ref, indent=2))
+    if kind == "white" and "dark" not in data:
+        print("NB! Tume referents puudub — soovitatav teha enne capture-dark (LED/tumedavool).")
     return 0
 
 
-def load_white() -> dict[str, float] | None:
-    if not CALIB_FILE.exists():
-        return None
-    return json.loads(CALIB_FILE.read_text()).get("white")
-
-
-def reflectance(channels: dict[str, float], white: dict[str, float]) -> dict[str, float]:
+def reflectance(
+    channels: dict[str, float], dark: dict[str, float], white: dict[str, float]
+) -> dict[str, float]:
     out: dict[str, float] = {}
     for band in BANDS:
-        ref = white.get(band, 0.0)
         value = channels.get(band)
-        if value is not None and ref:
-            out[band] = value / ref
+        d = dark.get(band, 0.0) or 0.0
+        w = white.get(band, 0.0) or 0.0
+        if value is not None and (w - d) > 0:
+            out[band] = max(0.0, (value - d) / (w - d))
     return out
 
 
 def show(last: int) -> int:
     rows = fetch_rows(last)
-    white = load_white()
+    data = load_calib()
+    dark = data.get("dark") or {}
+    white = data.get("white")
     if white is None:
-        print("(kalibratsioon puudub — näitan ainult toorest; tee capture-white)")
+        print("(valge kalibratsioon puudub — näitan ainult toorest; tee capture-white)")
+    elif not dark:
+        print("(tume referents puudub — kasutan D=0; soovitatav capture-dark)")
     for row in rows:
         channels = {b: row[b] for b in BANDS if row.get(b) is not None}
         raw = color.analyze(channels)
@@ -100,7 +125,7 @@ def show(last: int) -> int:
             f"(x={raw['x_chrom']:.3f} y={raw['y_chrom']:.3f})"
         )
         if white is not None:
-            cal = color.analyze(reflectance(channels, white))
+            cal = color.analyze(reflectance(channels, dark, white))
             line += (
                 f"  | kalib. {cal['rgb_hex']} "
                 f"(x={cal['x_chrom']:.3f} y={cal['y_chrom']:.3f})"
@@ -112,13 +137,17 @@ def show(last: int) -> int:
 def main() -> int:
     parser = argparse.ArgumentParser(description="AS7341 kalibreerimine")
     sub = parser.add_subparsers(dest="cmd", required=True)
+    cd = sub.add_parser("capture-dark", help="salvesta tume referents (andur kaetud)")
+    cd.add_argument("--last", type=int, default=5)
     cw = sub.add_parser("capture-white", help="salvesta valge referents")
     cw.add_argument("--last", type=int, default=5)
     sh = sub.add_parser("show", help="näita viimaseid mõõtmisi")
     sh.add_argument("--last", type=int, default=10)
     args = parser.parse_args()
+    if args.cmd == "capture-dark":
+        return capture_reference("dark", args.last)
     if args.cmd == "capture-white":
-        return capture_white(args.last)
+        return capture_reference("white", args.last)
     if args.cmd == "show":
         return show(args.last)
     return 1