#if HWTEST
#if !SELFTEST
#error HWTEST は SELFTEST の部品(SelfTestConfig・SelfTestReport・SelfTestPump・閉じる処理の段階の記録)を使うため,SELFTEST と同時に定義すること
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace TIASshot {
public partial class Form1 {
/// <summary>
/// 実機テスト用コンストラクタの目印型(HWTEST 限定).
/// </summary>
internal sealed class HwTestMarker {
internal static readonly HwTestMarker Instance = new HwTestMarker();
private HwTestMarker() { }
}
// 実機テストのモードか(Form1_Load の自動接続をしない・ShowError をモーダル表示しない・メモリ観測窓を開かない)
private bool _hwTestMode = false;
// ShowImage に届いたフレームの数(コールバックスレッドが書き,UI スレッドが読む)
private long _hwTestFrames = 0;
private int _hwTestFrameWidth = 0;
private int _hwTestFrameHeight = 0;
// フレームの記録中の例外(最初の 1 件)
private string _hwTestFrameError = null;
// 平均輝度・飽和画素の割合の測定の要求と結果(_hwTestSampleLock で保護)
private readonly object _hwTestSampleLock = new object();
private int _hwTestSamplesWanted = 0;
private readonly List<HwTestFrameStats> _hwTestSamples = new List<HwTestFrameStats>();
// ShowError に渡されたメッセージ(_hwTestSampleLock で保護)
private readonly List<string> _hwTestErrors = new List<string>();
// 直近の接続失敗の理由(_hwTestSampleLock で保護)
private readonly List<string> _hwTestConnectErrors = new List<string>();
/// <summary>
/// 実機テスト用コンストラクタ(HWTEST 限定).通常のコンストラクタ(InitializeComponent を含む)を通したうえで,
/// 実機テストのモードにする.表示(Show)すると Form1_Load が走るが,自動接続はしない(HwTestConnect で機種を指定して接続する).
/// </summary>
internal Form1(HwTestMarker marker) : this() {
_hwTestMode = true;
Text += " [HWTEST]";
}
// ================================================================================
// カメラ・光源の接続
// ================================================================================
/// <summary>接続されている Lumenera カメラの台数(SDK が無い・列挙に失敗した場合は例外)</summary>
internal static int HwTestCountLucam() {
return Lucam.HwTestCountCameras();
}
/// <summary>見つかった ImagingSource カメラの名前(IC Imaging Control の列挙)</summary>
internal string[] HwTestListIScam() {
return icImagingControl1.Devices.Select(d => d.Name).ToArray();
}
/// <summary>
/// 指定した機種だけを接続する(通常起動の Form1_Load と同じ接続処理を使う.ただし Lucam 優先の自動選択はしない).
/// 接続できたら現在のカメラ(_camera)にする.
/// </summary>
/// <returns>接続できたカメラ.できなければ null</returns>
internal CameraBase HwTestConnect(HwTestCameraKind kind) {
var camera = kind == HwTestCameraKind.Lucam ? ConnectLucam() : ConnectIScam();
_camera = camera;
return camera;
}
/// <summary>
/// 接続後の画面・光源・副モニタ・本モニタの再描画タイマーの開始(通常起動の Form1_Load と同じ処理).
/// </summary>
internal void HwTestStartSession() {
StartSession();
}
partial void OnConnectFailed(string device, string error) {
lock (_hwTestSampleLock) {
_hwTestConnectErrors.Add($"{device}: {error}");
}
}
/// <summary>接続失敗の理由を取り出す(古い順.取り出した分は消す)</summary>
internal List<string> HwTestTakeConnectErrors() {
lock (_hwTestSampleLock) {
var result = _hwTestConnectErrors.ToList();
_hwTestConnectErrors.Clear();
return result;
}
}
/// <summary>現在のカメラ(未接続なら null)</summary>
internal CameraBase HwTestCamera => _camera;
/// <summary>現在のカメラを切断する(閉じる処理を通さない.再接続の検査・後始末用)</summary>
internal void HwTestDisconnectCamera() {
_camera?.Disconnect();
}
/// <summary>光源のポートが開いているか</summary>
internal bool HwTestLightIsOpen => _light.IsOpen;
/// <summary>光源の点灯状態(画面の照明ボタンが管理する状態)</summary>
internal bool HwTestLightOn => _isLightOn;
/// <summary>照明ボタンの表示文</summary>
internal string HwTestLightButtonText => btnLightSW.Text;
/// <summary>照明ボタンを押す(通常の操作と同じ処理.校正中は何もしない)</summary>
internal void HwTestClickLightButton() {
btnLightSW_Click(btnLightSW, EventArgs.Empty);
}
/// <summary>光源のポートを閉じる(閉じる処理と同じ LightSource.Close.消灯はしない)</summary>
internal void HwTestCloseLight() {
_light.Close();
}
/// <summary>
/// 光源のポートを開き直して点灯する(Form1_Load と同じ手順).画面の照明ボタンの状態も点灯に合わせる.
/// </summary>
/// <returns>開けたら true</returns>
internal bool HwTestReopenLight() {
if (!_light.Open()) return false;
_light.SetLight();
_isLightOn = true;
btnLightSW.Text = "照明 OFF";
btnLightSW.Enabled = true;
return true;
}
/// <summary>
/// ウォッチドッグ(別スレッド)からの光源のポートの緊急クローズ(タイムアウト時の後始末).
/// 消灯はしない(終了時と同じく光源は点いたまま残る).LightSource.Close はクローズの失敗をログに残して戻る.
/// </summary>
internal void HwTestEmergencyCloseLight() {
_light.Close();
}
// ================================================================================
// 閉じる処理
// ================================================================================
/// <summary>
/// ユーザーが閉じようとしたときの処理(Form1_FormClosing)を呼ぶ(UI スレッドで呼ぶこと).
/// 閉じると決まった場合は後始末(閉じ始めのフラグ → タイマー停止 → カメラ切断 → 光源)まで行う.
/// フォームは破棄しない(後続の再接続の検査で使う).
/// </summary>
/// <returns>閉じると決まったら true,取り消されたら(保存中)false</returns>
internal bool HwTestRequestClose() {
var e = new FormClosingEventArgs(CloseReason.UserClosing, false);
Form1_FormClosing(this, e);
return !e.Cancel;
}
/// <summary>閉じるときの後始末が通過した段階(SELFTEST の記録を使う)</summary>
internal IReadOnlyList<string> HwTestCloseSteps => SelfTestCloseSteps;
/// <summary>閉じ始めたか</summary>
internal bool HwTestIsClosing => _isClosing;
/// <summary>本モニタの再描画タイマーが動作中か</summary>
internal bool HwTestRefreshTimerRunning => _mainRefreshTimer.Enabled;
/// <summary>本モニタ(picDisplay)に表示中の画像</summary>
internal Image HwTestDisplayedImage => picDisplay.Image;
/// <summary>
/// 閉じる処理で後始末まで済んだ後に,次の検査のために画面の状態を接続直後に戻す(閉じる処理を 2 回以上検査するため).
/// 閉じ始めのフラグを下ろし(以降の ShowMessage・EnableShots を通常どおり効かせる),撮影ボタンを無効にし,
/// 本モニタの再描画タイマーを動かし直し,光源のポートを開き直して点灯する(StartSession と同じ状態.
/// StartSession を 2 回呼ぶとタイマーの Tick の登録と副モニタが重複するため,ここで必要な分だけ戻す).
/// カメラは接続しない(呼び出し側が HwTestConnect で接続する).
/// </summary>
/// <returns>光源のポートを開き直せたら true</returns>
internal bool HwTestResumeAfterClose() {
_isClosing = false;
EnableShots(false);
_mainRefreshTimer.Start();
// 開いたままのポートを開き直すと SerialPort が例外を投げるため,閉じているときだけ開く
if (_light.IsOpen) return true;
return HwTestReopenLight();
}
// ================================================================================
// 撮影
// ================================================================================
/// <summary>データ名(txtDataName.保存フォルダ名の一部)を設定する</summary>
internal void HwTestSetDataName(string dataName) {
txtDataName.Text = dataName;
}
/// <summary>連続撮影の枚数・間隔(ms)の入力欄を設定する</summary>
internal void HwTestSetMultiShots(int numShots, int intervalMs) {
txtNumMultiShots.Text = numShots.ToString(System.Globalization.CultureInfo.InvariantCulture);
txtMultiShotsInterval.Text = intervalMs.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
/// <summary>撮影ボタン(単発・連続)が両方とも有効か</summary>
internal bool HwTestShotsEnabled => btnShotOne.Enabled && btnShotMulti.Enabled;
/// <summary>ステータス表示(txtMessage)の現在値</summary>
internal string HwTestMessage => txtMessage.Text;
/// <summary>単発撮影のボタンを押す(通常の操作と同じ処理.撮影は UI スレッドで同期に行われ,保存は保存スレッドで続く)</summary>
internal void HwTestClickShotOne() {
btnShot1_Click(btnShotOne, EventArgs.Empty);
}
/// <summary>連続撮影のボタンを押す(通常の操作と同じ処理)</summary>
internal void HwTestClickShotMulti() {
btnShotMulti_Click(btnShotMulti, EventArgs.Empty);
}
// ================================================================================
// フレームの観測
// ================================================================================
/// <summary>ShowImage に届いたフレームの数</summary>
internal long HwTestFrameCount => Interlocked.Read(ref _hwTestFrames);
/// <summary>最後に届いたフレームの大きさ</summary>
internal Size HwTestLastFrameSize => new Size(Volatile.Read(ref _hwTestFrameWidth), Volatile.Read(ref _hwTestFrameHeight));
/// <summary>フレームの記録中に起きた例外(無ければ null)</summary>
internal string HwTestFrameError => Volatile.Read(ref _hwTestFrameError);
/// <summary>ShowError に渡されたメッセージ(古い順のコピー)</summary>
internal IReadOnlyList<string> HwTestErrors {
get { lock (_hwTestSampleLock) return _hwTestErrors.ToArray(); }
}
/// <summary>
/// 次に届く count 枚のフレームの平均輝度・飽和画素の割合の測定を要求する(それまでの測定結果は捨てる).
/// </summary>
internal void HwTestRequestSamples(int count) {
lock (_hwTestSampleLock) {
_hwTestSamples.Clear();
_hwTestSamplesWanted = count;
}
}
/// <summary>測定済みのフレーム数</summary>
internal int HwTestSampleCount {
get { lock (_hwTestSampleLock) return _hwTestSamples.Count; }
}
/// <summary>測定結果を取り出す(未達の要求は取り消す)</summary>
internal List<HwTestFrameStats> HwTestTakeSamples() {
lock (_hwTestSampleLock) {
_hwTestSamplesWanted = 0;
var result = _hwTestSamples.ToList();
_hwTestSamples.Clear();
return result;
}
}
/// <summary>
/// ShowImage(カメラのコールバックスレッド)から呼ばれるフレームの記録.入力 bmp は読むだけ(所有権は呼び出し側).
/// 例外をコールバックへ伝えるとプレビューが止まるため,記録して戻る(実機テストの結果で FAIL として報告する).
/// </summary>
private void HwTestOnFrame(Bitmap bmp) {
if (!_hwTestMode) return;
try {
Interlocked.Increment(ref _hwTestFrames);
Volatile.Write(ref _hwTestFrameWidth, bmp.Width);
Volatile.Write(ref _hwTestFrameHeight, bmp.Height);
bool wanted;
lock (_hwTestSampleLock) wanted = _hwTestSamplesWanted > 0;
if (!wanted) return;
var stats = MeasureFrameStats(bmp);
lock (_hwTestSampleLock) {
if (_hwTestSamplesWanted > 0) {
_hwTestSamples.Add(stats);
_hwTestSamplesWanted--;
}
}
} catch (Exception ex) {
Debug.WriteLine($"[HWTEST] フレームの記録に失敗しました: {ex}");
Interlocked.CompareExchange(ref _hwTestFrameError, $"{ex.GetType().FullName}: {ex.Message}", null);
}
}
/// <summary>
/// フレームの平均輝度と飽和画素の割合を求める.間引いた行だけを複製して純粋関数(HwTestJudge.FrameStats)に渡す.
/// </summary>
private static HwTestFrameStats MeasureFrameStats(Bitmap bmp) {
var bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
if (bytesPerPixel < 1 || bytesPerPixel > 4) {
throw new NotSupportedException($"平均輝度を求められない画素形式です: {bmp.PixelFormat}");
}
var step = HwTestThresholds.PixelStep;
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
try {
var rowCount = (bmp.Height + step - 1) / step;
var rowBytes = bmp.Width * bytesPerPixel;
var rows = new byte[(long)rowCount * rowBytes];
for (var r = 0; r < rowCount; r++) {
var src = IntPtr.Add(data.Scan0, r * step * data.Stride);
Marshal.Copy(src, rows, r * rowBytes, rowBytes);
}
return HwTestJudge.FrameStats(rows, rowCount, bmp.Width, rowBytes, bytesPerPixel, step, HwTestThresholds.SaturatedPixelLevel);
} finally {
bmp.UnlockBits(data);
}
}
/// <summary>ShowError の実機テスト版(モーダル表示すると無人実行が止まるため記録のみ行う)</summary>
private void HwTestRecordError(string msg) {
Debug.WriteLine($"[HWTEST] ShowError: {msg}");
lock (_hwTestSampleLock) {
_hwTestErrors.Add(msg);
}
}
}
}
#endif