Newer
Older
TIASShot / TIASshot / Cameras / CameraBase.HwTest.cs
#if HWTEST
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using OpenCvSharp;

namespace TIASshot {

    /// <summary>
    /// CameraBase の実機テスト用の観測口(HWTEST 限定.本番・SELFTEST のみのビルドには含まれない).
    /// 校正の各段階の時刻(最初のフレーム・チャート検出・校正完了)と,校正後のフレーム・チャートマスクの複製を取る.
    /// 本番の校正・撮影の処理の流れや値は変えない(読むだけ.複製は要求があるときだけ作る).
    /// </summary>
    internal abstract partial class CameraBase {

        // 校正の各段階の時刻(Stopwatch.GetTimestamp の値.0 = まだ).コールバックスレッドが書き,UI スレッドが読む
        private long _hwTestFirstFrameAt = 0;
        private long _hwTestChartDetectedAt = 0;
        private long _hwTestCalibratedAt = 0;

        // 校正完了時の保存フォルダ(校正データ.CalcTcc の SetSaveFolder("校正") の結果)
        private string _hwTestCalibrationFolder = null;

        // 校正後のフレームの複製の要求と結果(_hwTestCaptureLock で保護)
        private readonly object _hwTestCaptureLock = new object();
        private bool _hwTestFrameRequested = false;
        private Mat _hwTestCapturedFrame = null;
        private List<Mat> _hwTestCapturedMasks = null;

        /// <summary>このカメラのインスタンスで最初に校正処理(ProcessCalibrationFrame)にフレームが届いた時刻(Stopwatch のタイムスタンプ.0 = まだ)</summary>
        internal long HwTestFirstFrameAt => Interlocked.Read(ref _hwTestFirstFrameAt);

        /// <summary>チャートを検出して校正(ホワイトバランスの調整)を始めた時刻(最後の検出.0 = まだ)</summary>
        internal long HwTestChartDetectedAt => Interlocked.Read(ref _hwTestChartDetectedAt);

        /// <summary>校正(変換行列の算出)が完了した時刻(0 = まだ)</summary>
        internal long HwTestCalibratedAt => Interlocked.Read(ref _hwTestCalibratedAt);

        /// <summary>校正データの保存フォルダ(校正完了前は null)</summary>
        internal string HwTestCalibrationFolder => Volatile.Read(ref _hwTestCalibrationFolder);

        /// <summary>直近の保存フォルダ(撮影の後は撮影データのフォルダ.UI スレッドの撮影処理が書く)</summary>
        internal string HwTestSaveFolder => _saveFolder;

        /// <summary>
        /// 保存スレッド(SaveThread.まだ起動していなければ null.終了済みのこともある).
        /// タイムアウト時にウォッチドッグがスタックを取るためだけに使う(スレッドの操作はしない)
        /// </summary>
        internal Thread HwTestSaveThread => Volatile.Read(ref _saveThread);

        /// <summary>変換行列の項目数の一覧(ColorCorrector の Calib/ConversionChannels の解釈そのもの)</summary>
        internal IReadOnlyList<int> HwTestConversionChannels => _colorCorrector.Channels.ToArray();

        /// <summary>
        /// 校正済みの状態で次に届くフレームとチャートマスクの複製を要求する(それまでの複製は解放する).
        /// 複製はコールバックスレッドで作る(チャートマスクはコールバックスレッドだけが読み書きするため).
        /// </summary>
        internal void HwTestRequestCalibratedFrame() {
            lock (_hwTestCaptureLock) {
                ReleaseHwTestCapture();
                _hwTestFrameRequested = true;
            }
        }

        /// <summary>
        /// 要求したフレームとチャートマスクの複製を取り出す(所有権は呼び出し側に移る.必ず Dispose すること).
        /// </summary>
        /// <returns>複製があれば true(取り出した分はカメラ側から消える)</returns>
        internal bool HwTestTakeCalibratedFrame(out Mat frame, out List<Mat> masks) {
            lock (_hwTestCaptureLock) {
                frame = _hwTestCapturedFrame;
                masks = _hwTestCapturedMasks;
                _hwTestCapturedFrame = null;
                _hwTestCapturedMasks = null;
                return frame != null;
            }
        }

        /// <summary>
        /// 校正で得た変換行列でフレームを sRGB に変換する(撮影の保存 SaveImages と同じ ColorCorrector.ConvertImage.入力は変えない).
        /// </summary>
        /// <param name="frame">変換するフレーム(BGR 8bit.呼び出し側が所有)</param>
        /// <param name="channel">変換行列の項目数</param>
        /// <returns>変換後の 8bit 画像(呼び出し側が Dispose する)</returns>
        internal Mat HwTestConvertToSrgb(Mat frame, int channel) {
            // ConvertImage は入力を in-place で CV_64FC3 に変えるため複製を渡す(SaveImages と同じ)
            using (var clone = frame.Clone()) {
                return _colorCorrector.ConvertImage(clone, channel);
            }
        }

        /// <summary>
        /// 画素の値(BGR 8bit 階調の並び.長さは 3 の倍数)を校正で得た変換行列で sRGB に変換し,8bit に切り詰める前の値を返す
        /// (HwTestConvertToSrgb と同じ計算で,最後の CV_8UC3 への変換だけを行わない.白飛びの調査用).
        /// </summary>
        /// <param name="bgr">変換する画素の値(B0, G0, R0, B1, G1, R1, ...)</param>
        /// <param name="channel">変換行列の項目数</param>
        /// <returns>変換後の値(同じ並び.BGR)</returns>
        internal double[] HwTestConvertPixelsUnclipped(double[] bgr, int channel) {
            var n = bgr.Length / 3;
            var result = new double[n * 3];
            if (n == 0) return result;
            using (var pixels = new Mat(n, 1, MatType.CV_64FC3)) {
                for (var i = 0; i < n; i++) {
                    pixels.At<Vec3d>(i, 0) = new Vec3d(bgr[i * 3], bgr[i * 3 + 1], bgr[i * 3 + 2]);
                }
                using (var converted = _colorCorrector.HwTestConvertPixelsUnclipped(pixels, channel)) {
                    for (var i = 0; i < n; i++) {
                        for (var j = 0; j < 3; j++) result[i * 3 + j] = converted.At<double>(i, j);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// ProcessCalibrationFrame(コールバックスレッド)の先頭から呼ぶ観測.
        /// 最初のフレームの時刻を記録し,校正済みでフレームの複製が要求されていれば,フレームとチャートマスクを複製する.
        /// </summary>
        private void HwTestObserveFrame(Mat imgt) {
            Interlocked.CompareExchange(ref _hwTestFirstFrameAt, Stopwatch.GetTimestamp(), 0);
            lock (_hwTestCaptureLock) {
                if (!_hwTestFrameRequested || !_calibrated || _chartMasks.Count == 0) return;
                _hwTestCapturedFrame = imgt.Clone();
                _hwTestCapturedMasks = _chartMasks.Select(m => m.Clone()).ToList();
                _hwTestFrameRequested = false;
            }
        }

        /// <summary>チャートを検出して校正を始めたことの記録(DetectChart から呼ぶ)</summary>
        private void HwTestMarkChartDetected() {
            Interlocked.Exchange(ref _hwTestChartDetectedAt, Stopwatch.GetTimestamp());
        }

        /// <summary>校正の完了の記録(CalcTcc の最後から呼ぶ.このときの保存フォルダが校正データのフォルダ)</summary>
        private void HwTestMarkCalibrated() {
            Volatile.Write(ref _hwTestCalibrationFolder, _saveFolder);
            Interlocked.Exchange(ref _hwTestCalibratedAt, Stopwatch.GetTimestamp());
        }

        private void ReleaseHwTestCapture() {
            _hwTestCapturedFrame?.Dispose();
            _hwTestCapturedFrame = null;
            if (_hwTestCapturedMasks != null) {
                foreach (var m in _hwTestCapturedMasks) m?.Dispose();
                _hwTestCapturedMasks = null;
            }
        }
    }
}
#endif