前回の議論で「推奨」とした方針を全採用しています。
| 論点 | 採用した方針 | 理由 |
|---|---|---|
| WebSocket プロトコル型 | scandata-proto として独立クレート | Rust ↔ TS で型を共有。scandata-ws に同居させると循環依存と TS 自動生成スクリプトが書きにくい |
| Application 層 (use case) | scandata-app として独立クレート | 純粋に保ち、tokio 依存を持ち込まない (FP 強寄り方針) |
| Client 配置 | モノレポ (ScanDataNet/client/) | WebSocket メッセージ型の同期コストを下げる最大の利益 |
| テストデータ | tiny fixtures を Phase L1 で生成し server/tests/fixtures/ に配置。実データは workspace 外 (../../220408/) を参照 | リポジトリを軽く保つ + CI で確実に回る |
| 設定永続化 | scandata-settings として独立クレート | JSON 永続化は ファイル I/O と用途が違う。混在で scandata-io が肥大化するのを防ぐ |
| PyO3 ブリッジ | scandata-py として独立クレート (Phase L5 後半に追加) | maturin ビルドの設定が他と衝突。任意機能として隔離 |
リポジトリルートから見た全構造です。L1〜 はその Phase 開始時に追加されるディレクトリで、最初から物理的に作成しておきます (空フォルダ + .gitkeep)。
Hexagonal Architecture の Adapter / Application / Domain がクレート単位で物理分離されています。 これにより「Domain から fs を import していない」がコンパイラレベルで保証されます。
scandata-domain) の Cargo.toml に tokio, std::fs 関連クレートを書かなければ、
「Domain から副作用を呼んだコード」はコンパイルエラーで止まる。これがクレート分割の最大の利益。
| クレート | 層 | 主な依存 | 純粋? | 役割 |
|---|---|---|---|---|
scandata-io | Adapter | ndarray, byteorder, thiserror | 副作用 | TSM/TBN/DA/HEKA を ScanRecord に変換 |
scandata-domain | Domain | ndarray, thiserror のみ | 純粋 | TraceData / Modifier / Roi / analyze。fs/tokio 一切なし |
scandata-app | Application | scandata-io, scandata-domain | 純粋 | port (trait) + usecase。tokio 依存しない |
scandata-proto | Shared | serde, schemars | 純粋 | WebSocket メッセージ型。TS と共有 |
scandata-ws | Adapter | axum, tokio, rmp-serde, scandata-app, scandata-proto | 副作用 | WS ハンドラ + セッション管理 |
scandata-settings | Adapter | serde_json, scandata-app (port impl) | 副作用 | JSON 設定ファイル永続化 |
scandata-cli | Adapter | clap, anyhow, scandata-app | 副作用 | tsm_info 等の開発用 CLI |
scandata-py | Adapter (任意) | pyo3, numpy, maturin, scandata-io | 副作用 | Python に open_tsm 等を公開 (AI 解析用) |
# Domain は誰にも依存しない (ndarray, thiserror のみ)
scandata-domain ────► (外部 crate のみ)
# Application は io + domain に依存。tokio に依存しない (純粋保持)
scandata-app ────► scandata-io, scandata-domain
# 副作用クレートは app と proto を使う
scandata-ws ────► scandata-app, scandata-proto, scandata-io (+ tokio, axum)
scandata-cli ────► scandata-app, scandata-io (+ clap, anyhow)
scandata-settings────► scandata-app (port impl) (+ serde_json, fs)
scandata-py ────► scandata-io, scandata-domain (+ pyo3, numpy)
| ScanDataPy (Python) | ScanDataNet (Rust + TS) | 備考 |
|---|---|---|
__main__.py | server/.../scandata-ws/src/main.rs + client/src/main.tsx | エントリポイント 2 系統に分離 |
common_class.py (FileService, WholeFilename) | scandata-io/types.rs | パス系は io 寄り |
common_class.py (KeyManager) | scandata-domain/types/identifier.rs | キーはドメイン |
model/file_io.py | scandata-io/formats/{tsm,da,heka}/ | 形式別サブモジュール |
model/value_object.py | scandata-domain/types/{trace,image,text,roi}.rs | 不変 struct |
model/modifier.py | scandata-domain/modifier/*.rs | Modifier 1 ファイル |
model/builder.py | scandata-app/usecase/open_file.rs + scandata-domain/experiment.rs | 集約と use case に分離 |
model/model.py (DataService, Repository) | scandata-app/port/ + scandata-app/usecase/ | Port trait + use case |
model/analyze/ | scandata-domain/analyze/ | 純粋関数で実装 |
view/view.py (PyQt6) | client/src/components/ | React に置換 |
view/plotting/ | client/src/components/{TracePlot,ImageView}/ | uPlot + regl |
controller/controller_main.py | scandata-app/usecase/ + client/src/state/ | ロジックと UI 状態に分離 |
controller/controller_axes.py | client/src/components/ 内のローカル状態 | UI 側に移動 |
controller/controller_live_view.py | scandata-app/usecase/ + WS ハンドラ | 副作用は WS 層に |
setting/*.json | scandata-settings/ + JSON 互換維持 | 段階的に置換 |
test_pyqt.py | client/src/components/*.test.tsx | フロント側のテストへ |
最初から空フォルダを全部作っておきますが、各 Phase で実体を書き込む順番は以下の通りです。
| Phase | この時点で書き始めるクレート / ディレクトリ | 主な成果物 |
|---|---|---|
| L0 Rust 基礎 | server/Cargo.toml (workspace 雛形), scandata-cli |
tsm-info CLI 動作 |
| L1 TSM パーサ | scandata-io, server/tests/fixtures/ (tiny データ), scripts/make_tiny_fixtures.py |
open_tsm_pair + .npy 一致テスト |
| L2 ドメイン層 | scandata-domain, scandata-app (骨組み) |
Modifier chain + 数値が ScanDataPy と ±0.001% 一致 |
| L3 WebSocket | scandata-proto, scandata-ws, scandata-settings (終盤), .github/workflows/ |
CLI クライアントから WS でファイル操作 |
| L4 フロント | client/ 全体, scripts/gen_ts_types.sh |
ブラウザでファイル開く + ROI + トレース表示 |
| L5 機能パリティ | 各クレート充填, benches/, scandata-py (後半), deploy/ |
ScanDataPy の主要機能を Web 版で完全再現 |
| L6 主用途切替 | ScanDataPy 凍結, README.md 最終化 |
主ツールが ScanDataNet に |
server/tests/fixtures/ に置く。実データとの一致確認は workspace 外の ../../220408/ 等を参照。
scripts/make_tiny_fixtures.py で、ScanDataPy の TsmFileIo と互換な
最小バイナリを生成します。具体的には:
| ファイル | サイズ | 内容 |
|---|---|---|
tiny_4x4x2.tsm | 約 3 KB | 2880 byte FITS ヘッダ (NAXIS1=4, NAXIS2=4, NAXIS3=3) + 4×4×3 int16 (2 fluo + 1 dark) |
tiny_4x4x2.tbn | 約 100 byte | 4 byte ヘッダ + 1 ch × 2 frame × bnc=2 の float64 トレース |
tiny_4x4x2.da | 約 6 KB | 5120 byte ヘッダ + 4×4×2 imaging + 8ch×2×2 elec + 4×4 dark |
# scripts/make_tiny_fixtures.py
import numpy as np
from pathlib import Path
OUT = Path(__file__).parent.parent / "server/tests/fixtures"
OUT.mkdir(parents=True, exist_ok=True)
def make_tsm(num_x=4, num_y=4, num_frames=2, exposure_s=0.01):
header = (
f"NAXIS1 = {num_x:>20d}"
f"NAXIS2 = {num_y:>20d}"
f"NAXIS3 = {num_frames+1:>20d}" # +1 for dark
f"EXPOSURE= {exposure_s:>20.6f}"
).ljust(2880, ' ').encode('ascii')
# 簡単な決定的データ (テストで検証しやすいパターン)
data = np.arange(num_x * num_y * (num_frames + 1), dtype=np.int16) + 100
data = data.reshape(num_x, num_y, num_frames + 1, order='F')
return header + data.tobytes(order='F')
(OUT / "tiny_4x4x2.tsm").write_bytes(make_tsm())
# ... tbn / da も同様
# Tiny test fixtures
これらは小さな決定的テストデータです。再生成する場合:
```
python ../../../scripts/make_tiny_fixtures.py
```
実データとの一致確認は workspace 外の以下を参照:
- ../../220408/20408A001new.tsm (Phase L1 数値検証用)
- ../../70127A/ (Phase L1 Stage 3 DA 検証用)
全 Phase で必要になるディレクトリを最初に物理配置します。各空フォルダには
.gitkeep を置いて Git で追跡可能にします。
| 配置内容 | 意図 |
|---|---|
クレート 8 個の src/ サブディレクトリまで全展開 | 各 Phase 着手時に「どこに何を書くか」を迷わない |
各空ディレクトリに .gitkeep | Git は空ディレクトリを追跡しないため |
Cargo.toml 類は書かない | 実装時に Phase に応じて書く (空フォルダで十分) |
fixtures/README.md は書く | tiny データ生成方法を残しておく |
ScanDataNet/ 配下には LEARNING_WEB_PROJECT_PLAN.md 等の Markdown が既に置かれています。
これらは Phase L0 で git init される時点までは現状の位置で OK。
Phase L0 で正式に Git リポジトリ化するときに、必要なら docs/ 配下に移動するか検討します。
| 議題 | 選択肢 | 採用 |
|---|---|---|
| ファイル形式の拡張機構 | (A) trait + dyn / (B) 関数ポインタ + Registry / (C) enum_dispatch | (B) FP 強寄り、Python 連携時に素直 |
| trait 化のタイミング | Stage 1 / Stage 3 後 / 最後まで不要 | Stage 3 完了後に切り出し |
src/io/ サブディレクトリ | 作る / 作らない | 作らない (クレート名が scandata-io で冗長) |
副作用 (open_*) の配置 | 1 ファイル (io.rs) / 形式別 mod.rs | 形式別 mod.rs に分散 |
| 共通出力型 | 形式ごとの struct / ScanRecord 統一 | ScanRecord 統一 |
| WebSocket プロトコル | scandata-ws 内 / scandata-proto 独立 | scandata-proto 独立 |
| Application 層 | scandata-ws 内 / scandata-app 独立 | scandata-app 独立 |
| Client 配置 | モノレポ / 別リポジトリ | モノレポ |
| テストデータ | Git LFS / tiny 生成 / 外部参照 | tiny 生成 + 外部参照 |
| PyO3 (AI 連携) | 最初から / 任意機能 / 不採用 | Phase L5 後半に scandata-py として追加 |
作成 2026-05-17 · ScanDataPy → ScanDataNet 全面移行計画