calibrate.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """Kalibreerimise abivahend AS7341 värviandurile.
  2. Loeb ClickHouse'ist (vaikimisi uus67.varvus) ja toetab:
  3. * capture-white: salvesta valge referents (viimased N mõõtmist) -> calibration.json
  4. * show: näita viimaseid mõõtmisi (toores hex vs kalibreeritud hex)
  5. Kalibreeritud värvus = iga kanal jagatud valge referentsiga (reflectance),
  6. mis eemaldab valgustuse värvitooni ja anduri kanalite erinevuse.
  7. Käivitamine (projekti .venv):
  8. CH_URL=http://100.87.1.24:8125 CH_USER=zigbee CH_PASS=... \
  9. .venv/bin/python varvusensor/collector/calibrate.py capture-white --last 5
  10. CH_URL=http://100.87.1.24:8125 CH_USER=zigbee CH_PASS=... \
  11. .venv/bin/python varvusensor/collector/calibrate.py show --last 10
  12. """
  13. from __future__ import annotations
  14. import argparse
  15. import json
  16. import os
  17. import statistics
  18. import sys
  19. from pathlib import Path
  20. import requests
  21. import color
  22. from settings import CH_URL, CH_DB, CH_TABLE, CH_USER, CH_PASS
  23. CALIB_FILE = Path(
  24. os.environ.get("CALIB_FILE", Path(__file__).with_name("calibration.json"))
  25. )
  26. FIELDS = ("f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "clear", "nir")
  27. BANDS = tuple(color.CHANNEL_WAVELENGTHS) # f1..f8
  28. def ch_query(sql: str) -> str:
  29. resp = requests.post(
  30. f"{CH_URL}/",
  31. params={"database": CH_DB, "query": sql},
  32. auth=(CH_USER, CH_PASS),
  33. timeout=15,
  34. )
  35. resp.raise_for_status()
  36. return resp.text
  37. def fetch_rows(last: int) -> list[dict]:
  38. cols = ", ".join(FIELDS + ("rgb_hex",))
  39. sql = (
  40. f"SELECT ts, {cols} FROM {CH_DB}.{CH_TABLE} "
  41. f"ORDER BY ts DESC LIMIT {int(last)} FORMAT JSONEachRow"
  42. )
  43. return [json.loads(line) for line in ch_query(sql).strip().splitlines() if line]
  44. def capture_white(last: int) -> int:
  45. rows = fetch_rows(last)
  46. if not rows:
  47. print("Andmeid pole — mõõda esmalt valget pinda.")
  48. return 1
  49. ref: dict[str, float] = {}
  50. for field in FIELDS:
  51. values = [r[field] for r in rows if r.get(field) is not None]
  52. ref[field] = round(statistics.mean(values), 3) if values else 0.0
  53. payload = {"reference_rows": len(rows), "white": ref}
  54. CALIB_FILE.write_text(json.dumps(payload, indent=2, ensure_ascii=False))
  55. print(f"Valge referents salvestatud ({len(rows)} reast) -> {CALIB_FILE}")
  56. print(json.dumps(ref, indent=2))
  57. return 0
  58. def load_white() -> dict[str, float] | None:
  59. if not CALIB_FILE.exists():
  60. return None
  61. return json.loads(CALIB_FILE.read_text()).get("white")
  62. def reflectance(channels: dict[str, float], white: dict[str, float]) -> dict[str, float]:
  63. out: dict[str, float] = {}
  64. for band in BANDS:
  65. ref = white.get(band, 0.0)
  66. value = channels.get(band)
  67. if value is not None and ref:
  68. out[band] = value / ref
  69. return out
  70. def show(last: int) -> int:
  71. rows = fetch_rows(last)
  72. white = load_white()
  73. if white is None:
  74. print("(kalibratsioon puudub — näitan ainult toorest; tee capture-white)")
  75. for row in rows:
  76. channels = {b: row[b] for b in BANDS if row.get(b) is not None}
  77. raw = color.analyze(channels)
  78. line = (
  79. f"{row['ts']} toores {raw['rgb_hex']} "
  80. f"(x={raw['x_chrom']:.3f} y={raw['y_chrom']:.3f})"
  81. )
  82. if white is not None:
  83. cal = color.analyze(reflectance(channels, white))
  84. line += (
  85. f" | kalib. {cal['rgb_hex']} "
  86. f"(x={cal['x_chrom']:.3f} y={cal['y_chrom']:.3f})"
  87. )
  88. print(line)
  89. return 0
  90. def main() -> int:
  91. parser = argparse.ArgumentParser(description="AS7341 kalibreerimine")
  92. sub = parser.add_subparsers(dest="cmd", required=True)
  93. cw = sub.add_parser("capture-white", help="salvesta valge referents")
  94. cw.add_argument("--last", type=int, default=5)
  95. sh = sub.add_parser("show", help="näita viimaseid mõõtmisi")
  96. sh.add_argument("--last", type=int, default=10)
  97. args = parser.parse_args()
  98. if args.cmd == "capture-white":
  99. return capture_white(args.last)
  100. if args.cmd == "show":
  101. return show(args.last)
  102. return 1
  103. if __name__ == "__main__":
  104. sys.exit(main())