"""エントリポイント(`python -m tiasjudge`).
`QApplication` を生成してメイン画面(SPEC_01)を表示する.
ログの出力先設定はエントリポイント側の責務とする(error-handling.md「ログ」).
起動オプション:
- `--check`: 画面を生成して即終了する(起動可能性の確認用.イベントループを回さない)
- `--preview-result`: 結果画面(SPEC_01「結果画面」)だけをダミー結果で開く(デバッグ用)
- `--failed`: `--preview-result` と併用し,診断失敗パターンで開く
- `--english`: `--preview-result` と併用し,英語表示で開く(結果画面は表示専用のため,
プレビューでは画面内で言語を切り替えられない)
"""
from __future__ import annotations
import argparse
import logging
import sys
from collections.abc import Sequence
from typing import TYPE_CHECKING
import numpy as np
if TYPE_CHECKING: # pragma: no cover - 型チェック時のみ
from numpy.typing import NDArray
from PySide6.QtWidgets import QApplication
from tiasjudge.core.svm import DiagnosisResult
logger = logging.getLogger(__name__)
#: `--check`(イベントループを回さずに終了する)オプション
CHECK_OPTION = "--check"
#: ログの書式(診断の入出力は `core` が INFO で記録する)
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s: %(message)s"
# --- `--preview-result` 用のダミーデータ(デバッグ表示専用.診断は実行しない)---
#: プレビューに使うダミー画像の一辺(結果画面は 1/2 に縮小して表示する)
PREVIEW_IMAGE_SIZE = 400
#: プレビューに使うダミー画像の色(舌色に近い RGB)
PREVIEW_IMAGE_RGB = (198, 120, 120)
#: プレビューのダミー L*a*b*(学習データの範囲内にある代表値)
PREVIEW_LAB = (58.0, 24.0, 12.0)
#: 成功パターンのダミースコア(%)
PREVIEW_PERCENTAGES: dict[str, int] = {
"purple": 5,
"dark_red": 10,
"crimson": 25,
"pink": 55,
"pale": 5,
}
#: 成功パターンの上位 2 色(`PREVIEW_PERCENTAGES` を降順に並べた先頭 2 件)
PREVIEW_TOP_LABELS = ("pink", "crimson")
#: 失敗パターンの上位 2 色(`LEGACY_FAILURE_PERCENTAGES` を降順に並べた先頭 2 件)
PREVIEW_FAILED_TOP_LABELS = ("purple", "pink")
def _build_parser() -> argparse.ArgumentParser:
"""コマンドライン引数のパーサを作る."""
parser = argparse.ArgumentParser(
prog="tiasjudge",
description="舌診支援システム(TIAS)を起動する.",
)
parser.add_argument(
CHECK_OPTION,
action="store_true",
help="画面を生成して即終了する(起動可能性の確認用)",
)
parser.add_argument(
"--preview-result",
action="store_true",
help="結果画面だけをダミー結果で開く(デバッグ用.診断は実行しない)",
)
parser.add_argument(
"--failed",
action="store_true",
help="--preview-result と併用し,診断失敗パターンで開く",
)
parser.add_argument(
"--english",
action="store_true",
help="--preview-result と併用し,英語表示で開く",
)
return parser
def _preview_image() -> NDArray[np.uint8]:
"""プレビュー用の単色ダミー画像(BGR)を作る."""
red, green, blue = PREVIEW_IMAGE_RGB
image = np.empty((PREVIEW_IMAGE_SIZE, PREVIEW_IMAGE_SIZE, 3), dtype=np.uint8)
# OpenCV と同じ BGR 並びで埋める
image[:, :] = (blue, green, red)
return image
def _preview_result(*, failed: bool) -> DiagnosisResult:
"""プレビュー用のダミー診断結果を組み立てる(SVM 学習は行わない)."""
from tiasjudge.core.color import Lab
from tiasjudge.core.csv_io import EVALUATION_COLUMN_COUNT
from tiasjudge.core.svm import (
LEGACY_FAILURE_PERCENTAGES,
DiagnosisResult,
FailureReason,
)
percentages = dict(LEGACY_FAILURE_PERCENTAGES) if failed else dict(PREVIEW_PERCENTAGES)
top_labels = PREVIEW_FAILED_TOP_LABELS if failed else PREVIEW_TOP_LABELS
return DiagnosisResult(
lab=Lab(*PREVIEW_LAB),
scores={key: value / 100 for key, value in percentages.items()},
percentages=percentages,
top_labels=top_labels,
predictions=np.zeros(EVALUATION_COLUMN_COUNT, dtype=np.float32),
failed=failed,
failure_reason=FailureReason.OUT_OF_DOMAIN if failed else None,
)
def _run_preview(app: QApplication, *, failed: bool, check: bool, english: bool = False) -> int:
"""結果画面だけをダミー結果で開く(`--preview-result`).
結果画面は表示専用(言語切替ボタンを持たない)ため,プレビューの言語は
`--english` オプションで指定する.
"""
# 関数内 import は起動時のみ GUI を読み込むため(`core` は GUI 非依存)
from tiasjudge.gui.result_window import Language, ResultWindow, show_on_secondary_screen
language = Language.ENGLISH if english else Language.JAPANESE
logger.info("結果画面のプレビューを開きます(failed=%s・language=%s)", failed, language.value)
window = ResultWindow(_preview_result(failed=failed), _preview_image(), language)
if check:
window.close()
return 0
# プレビューにはメイン画面が無いため,プライマリモニタを基準に別モニタを選ぶ
show_on_secondary_screen(window, app.screens(), app.primaryScreen())
return int(app.exec())
def main(argv: Sequence[str] | None = None) -> int:
"""アプリを起動する.戻り値はプロセスの終了コード.
Args:
argv: コマンドライン引数(プログラム名を含まない).省略時は `sys.argv[1:]`.
Returns:
プロセスの終了コード(0 が正常終了).
"""
arguments = _build_parser().parse_args(list(sys.argv[1:] if argv is None else argv))
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
# GUI ツールキットの import は起動時のみに限定する(`core` は GUI 非依存)
from PySide6.QtWidgets import QApplication
app = QApplication.instance() or QApplication(sys.argv[:1])
if arguments.preview_result:
return _run_preview(
app,
failed=arguments.failed,
english=arguments.english,
check=arguments.check,
)
from tiasjudge.gui.main_window import MainWindow
window = MainWindow()
if arguments.check:
# 画面を構築できることだけを確認して終了する(ヘッドレス環境での確認用)
window.close()
return 0
window.show()
return int(app.exec())
if __name__ == "__main__":
raise SystemExit(main())