# 匿名化ツール

医療・研究データのID匿名化を一括で行うデスクトップアプリケーション

- **対応OS:** Windows 10 / 11
- **バージョン:** v1.1.0
- **価格:** 無料
- **インストール不要**（exeファイル単体で動作）

---

## 概要

フォルダをドラッグ&ドロップするだけで、フォルダ名・ファイル名・テキストファイル内容・DICOMタグに含まれるIDを一括匿名化。Feistel暗号化による可逆変換、対応表CSVの自動生成、DICOMタグ限定モードなど、研究データの取り扱いに必要な機能を揃えています。

入力データの構造を自動判別し、ID名サブフォルダがある場合・親フォルダに直接DICOMがある場合・CSV/Excelのみの場合のいずれにも対応します。CSV/Excel内のID列は値のパターンから自動検出し、名前列はヘッダキーワードから自動検出して匿名化します。

---

## 使い方

1. `匿名化ツール.exe` をダウンロードし、任意のフォルダに配置
2. 初回起動時に「SEED値 作成」ボタンでSEED値を設定
3. 匿名化モードを選択（Feistel暗号化 / 対応表作成 / DICOMタグのみ）
4. 左の「元データ」ゾーンにフォルダをドラッグ&ドロップ
5. 「→」ボタンで匿名化を実行
6. Feistelモードでは「←」ボタンで復元も可能

> **注意:** ウイルス対策ソフトが誤検知する場合があります。その場合は例外設定に追加してください。

---

## 匿名化モード

### Feistel暗号化モード

- IDを暗号学的に安全な方式（Feistel cipher, PBKDF2-SHA256 + HMAC-SHA256, 16ラウンド）で別のIDに変換
- 同じSEED値で元のIDに復元可能（可逆暗号化）
- 接頭辞の設定可能（例: `1234567` → `K8392041`）

### 対応表作成モード

- IDを任意のテキストパターンに置換（例: `KMS_{0001}_aaa`）
- 元ID↔匿名化IDの `_対応表.csv` を自動生成
- 既存の対応表がある場合、読み込んで続番を割当（追記）。なければ新規作成
- 既存の対応表がある場合、追加分のみの差分処理にも対応
- パターンA/B/Cすべてで対応表の自動生成・追記が動作

### DICOMタグのみモード

- フォルダ構造やID桁数に依存せず、DICOMファイルのタグのみを指定テキストに書き換え
- DICOMタグ内のPatientIDをファイル名・フォルダ名からも検出し、置換テキストに自動リネーム
- 異なるIDのファイル名が衝突する場合、`_0`, `_1`... の連番を自動付与

---

## 対応するデータ構造

入力フォルダの中身を自動判別し、以下の3パターンに対応します。すべてのパターンでFeistel暗号化・対応表作成・DICOMタグのみの各モードが使用可能です。

### パターンA: ID名サブフォルダ + 親DIR内ファイル

最も一般的なパターンです。親フォルダの中にID（数字N桁）で始まるサブフォルダがあり、その中に各患者のデータが格納されている構造です。

```
入力フォルダ/
  ├── 患者一覧.xlsx        ← 親DIR内のCSV/Excelも自動匿名化
  ├── study_list.csv
  ├── 1234567_検査A/       ← ID名サブフォルダ
  │   ├── 1234567.dcm
  │   ├── result_1234567.csv
  │   └── ...
  ├── 2345678_検査B/
  │   └── ...
```

↓ 匿名化後

```
入力フォルダ_匿名化/
  ├── 患者一覧.xlsx        ← ID列・名前列が匿名化済み
  ├── study_list.csv
  ├── K8392041_検査A/
  │   ├── K8392041.dcm     ← DICOM タグも書換済み
  │   ├── result_K8392041.csv
  │   └── ...
  ├── K1057263_検査B/
  │   └── ...
```

**処理順序:** サブフォルダを処理 → 親DIR内のCSV/Excelを処理

### パターンB: 親DIRに直接DICOMファイル

サブフォルダがなく、DICOMファイルが親フォルダに直接格納されている場合です。DICOMタグ内の `PatientID` からIDを自動取得して匿名化します。

```
入力フォルダ/
  ├── 1234567.dcm
  ├── 1234568.dcm
  ├── 患者リスト.csv       ← あれば一緒に匿名化
```

- DICOMタグからPatientIDを抽出し、IDマッピングを構築
- ファイル名に含まれるPatientIDも自動リネーム
- 異なるIDが同じファイル名になる場合は `_0`, `_1`... の連番を自動付与
- 対応表モードでは `_対応表.csv` を自動生成（既存があれば追記）

### パターンC: CSV/Excelのみ

DICOMもサブフォルダもなく、CSV/Excelファイルのみの場合です。ファイル内のID列を自動検出して匿名化します。

```
入力フォルダ/
  ├── 患者一覧.xlsx
  ├── 検査結果.csv
```

- 全ファイルを先読みしてID列を自動検出（後述の「ID列・名前列の自動検出」参照）
- 検出したIDに対してFeistel暗号化 or 対応表割当を実施
- 対応表モードでは `_対応表.csv` を自動生成（既存があれば追記）

---

## ID列・名前列の自動検出

親DIR内のCSV/Excelファイルは、列の内容を分析してID列と名前列を自動判別します。

### ID列の検出（ヘッダ名に依存しない）

ヘッダ名（「患者ID」「プランID」等）ではなく、**列の値そのもの**で判定します。これにより「プランID（1-2桁）」と「患者ID（7桁）」が同じファイルにあっても正しく区別できます。

| 判定方法 | 条件 | 説明 |
|---|---|---|
| 既知ID一致 | 値が既知IDと **2件以上** 一致 | サブフォルダ名やDICOMタグから得たIDと照合。7桁の数値が偶然2件一致する確率はほぼゼロ |
| 桁数パターン（フォールバック） | 値の **70%以上** がID桁数と一致する数値 | 既知IDがない場合に使用 |
| 日付除外 | YYYYMMDD形式やExcelシリアル日付を自動除外 | 8桁IDと日付(20240301等)、5桁IDとExcelシリアル日付(1〜73000)の混同を防止 |

**例:** 以下のようなCSVの場合

| プランID | 患者番号 | 患者ID | 患者名 |
|---|---|---|---|
| 1 | 5 | 1234567 | 山田太郎 |
| 2 | 12 | 2345678 | 鈴木花子 |

→ 「患者ID」列のみが7桁の既知IDと一致するため、正しくID列として検出されます。「プランID」「患者番号」は1-2桁なので無視されます。

### 名前列の検出（ヘッダキーワードベース）

以下のキーワードがヘッダに含まれる列を名前列として検出し、該当行のIDが特定できた場合に空白化します。

| 検出強度 | キーワード |
|---|---|
| 強一致 | 患者名, PatientName, 被験者名, 対象者名, 氏名, 名前, 姓名 |
| 中一致 | LastName, FirstName, FamilyName, GivenName, カナ, フリガナ, ふりがな, ローマ字, Romaji, Kana |
| 完全一致のみ | 姓, 名, Name, Last, First |

姓・名が別々の列に分かれている場合や、ローマ字表記の列も検出対象です。

---

## 処理対象ファイル

**フォルダ名・ファイル名** — IDを含む名前を匿名化IDに置換（ON/OFF可能）

**テキストファイル内容** — 以下の形式に含まれるIDを置換:

| 形式 | 拡張子 |
|---|---|
| プレーンテキスト | `.csv` `.txt` `.tsv` `.log` `.ini` `.yaml` `.yml` `.toml` `.bin` |
| 構造化テキスト | `.json` `.xml` |
| Office文書 | `.xlsx` `.docx` `.pptx` |

**DICOMタグ** — 患者名・患者ID・生年月日など15種類のタグを個別にON/OFF可能:

| タグ名 | 説明 | デフォルト値 |
|---|---|---|
| PatientName | 患者名 | `{id}`（匿名化IDに置換） |
| PatientID | 患者ID | `{id}` |
| PatientBirthDate | 生年月日 | 空欄（削除） |
| PatientSex | 患者性別 | 空欄 |
| StudyDate / SeriesDate / AcquisitionDate / ContentDate | 各種日付 | 空欄 |
| AccessionNumber | アクセッション番号 | 空欄 |
| InstitutionName / Manufacturer | 施設名・製造元 | 空欄 |
| ReferringPhysicianName / OperatorsName | 紹介医・操作者名 | 空欄 |
| StudyDescription / SeriesDescription | 検査・シリーズ説明 | 空欄 |

---

## 出力先の設定

| 出力先 | 説明 |
|---|---|
| アプリと同じ場所 | exeファイルがあるフォルダに出力 |
| 元フォルダと同じ場所 | 元データの隣に「_匿名化」サフィックス付きで出力（デフォルト） |
| 指定した場所 | 任意のフォルダを指定 |

---

## SEED.conf とセキュリティ

- アプリと同じフォルダに自動生成される設定ファイル
- 暗号化SEED値と全設定が保存される
- **同じSEED値 → 同じ匿名化結果** が保証（Feistelモード）
- SEED値を変更すると全く異なる匿名化結果になる

> **セキュリティ上の注意:**
> - **SEED.conf を匿名化データと一緒に配布しないでください**
> - SEED値があれば匿名化を解除できるため、厳重に管理してください

---

## 暗号強度について

Feistelモードでは、すべてのID変換がSEED値を鍵として行われます。仮にある患者の「元ID → 匿名化ID」の対応が漏洩しても、**SEED値がなければ他の患者のID対応を逆算することはできません。**

では、既知の対応ペアからSEED値を逆算できるか？ SEED値は95種の文字から20文字を生成（約131ビットのエントロピー）し、さらにPBKDF2-SHA256で10万回反復して256ビット鍵に変換されます。この鍵空間を総当たりで探索するには、毎秒1兆回の試行でも**約10²⁵年**（宇宙の年齢の約1,000兆倍）が必要であり、事実上不可能です。

---

## よくある質問

**Q. SEED値を忘れた場合、匿名化を解除できますか？**
A. Feistelモードの場合、SEED値がなければ解除できません。SEED.conf を安全な場所にバックアップしてください。

**Q. 同じIDは常に同じ匿名化IDになりますか？**
A. Feistelモードでは、同じSEED値を使う限り常に同じ結果になります。対応表モードでは対応表CSVで管理されます。

**Q. 匿名化済みデータに新しいデータを追加できますか？**
A. 対応表モードでは差分処理に対応しています。既存の対応表を維持したまま、新規IDのみ追加処理できます。

**Q. DICOMファイルかどうかの判定基準は？**
A. 拡張子が `.dcm` のファイル、または拡張子がないファイルで先頭128バイト後に `DICM` マジックバイトがあるファイルを認識します。

**Q. 親フォルダにある患者一覧CSV/Excelも匿名化されますか？**
A. はい。ID名サブフォルダの処理後に、親フォルダ直下のCSV/Excelも自動で匿名化されます。ID列は値のパターンから自動検出され、名前列はヘッダキーワードから検出されます。

**Q. CSVに「プランID」「患者番号」「患者ID」など複数のID列がある場合、正しく区別できますか？**
A. ヘッダ名ではなく列の値で判定するため、1-2桁の「プランID」「患者番号」と7桁の「患者ID」は正しく区別されます。既知のIDと値が一致する列のみが匿名化対象になります。

**Q. サブフォルダがなくDICOMやCSVだけのフォルダでも使えますか？**
A. はい。サブフォルダがない場合は自動的に親フォルダ内のDICOMまたはCSV/Excelを検出して処理します。Feistel暗号化・対応表作成のどちらのモードでも動作します。

---

## ライセンス

### 匿名化ツール 利用許諾

- 個人利用・業務利用（研究・医療現場等）を問わず、**無料で使用**できます
- 本ソフトウェアの**再配布・転載は禁止**です
- 本ソフトウェアは「現状のまま」提供され、作者は使用に起因するいかなる損害についても責任を負いません
- 匿名化処理の結果については、ユーザー自身の責任で確認してください

### サードパーティライセンス

本アプリケーションは Python, customtkinter, pydicom, openpyxl, windnd（いずれもMITまたはPSFライセンス）を使用しています。ビルドにはPyInstaller（GPL-2.0 with Bootloader Exception）を使用。詳細は同梱の THIRD_PARTY_LICENSES.txt を参照してください。
