cyd0806 commited on
Commit
35b365b
ยท
verified ยท
1 Parent(s): c2d8817

Upload NeuroScan AI code

Browse files
Files changed (5) hide show
  1. BENCHMARK.md +160 -0
  2. README.md +117 -6
  3. data/README.md +66 -0
  4. scripts/benchmark.py +618 -0
  5. scripts/stress_test.py +428 -0
BENCHMARK.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NeuroScan AI ๆ€ง่ƒฝๅŸบๅ‡†ๆต‹่ฏ•
2
+
3
+ ## ๐Ÿ”ฌ ๆ€ง่ƒฝๅŸบๅ‡†ๆต‹่ฏ•ๆŠฅๅ‘Š
4
+
5
+ > ๆต‹่ฏ•ๆ—ถ้—ด: 2026-01-28 17:16:29
6
+
7
+ ### ๆต‹่ฏ•็Žฏๅขƒ
8
+
9
+ | ็ป„ไปถ | ้…็ฝฎ |
10
+ |------|------|
11
+ | **CPU** | Intel(R) Xeon(R) Platinum 8350C CPU @ 2.60GHz |
12
+ | **CPUๆ ธๅฟƒ** | 64 ็‰ฉ็†ๆ ธ / 128 ้€ป่พ‘ๆ ธ |
13
+ | **ๅ†…ๅญ˜** | 2014 GB |
14
+ | **GPU** | NVIDIA A800-SXM4-80GB |
15
+ | **GPUๆ˜พๅญ˜** | 80 GB |
16
+ | **Python** | 3.11.11 |
17
+ | **PyTorch** | 2.8.0+cu129 |
18
+ | **CUDA** | 12.9 |
19
+ | **MONAI** | 1.5.2 |
20
+ | **SimpleITK** | 2.4.1 |
21
+
22
+ ### ๆต‹่ฏ•ๆ•ฐๆฎ
23
+
24
+ | ๅฑžๆ€ง | ๅ€ผ |
25
+ |------|------|
26
+ | **ๆ•ฐๆฎ้›†** | Learn2Reg Lung CT |
27
+ | **ๆ ทๆœฌๆ•ฐ้‡** | 20 ๅฏน (ๅธๆฐ”/ๅ‘ผๆฐ”้…ๅฏน) |
28
+ | **่พ“ๅ…ฅๅฐบๅฏธ** | 192 ร— 192 ร— 208 ไฝ“็ด  |
29
+ | **ไฝ“็ด ้—ด่ท** | 1.0 ร— 1.0 ร— 1.0 mm |
30
+ | **ๆ•ฐๆฎ็ฑปๅž‹** | float32 |
31
+ | **ๅ•ๅทๅคงๅฐ** | 58.5 MB |
32
+ | **ๅ•ๅทไฝ“็ด ๆ•ฐ** | 7,667,712 |
33
+
34
+ ---
35
+
36
+ ## CPU ๅนถๅ‘ๅŸบๅ‡†ๆต‹่ฏ•
37
+
38
+ ### ๆต‹่ฏ•ไปปๅŠก: ้…ๅ‡† + ๅ˜ๅŒ–ๆฃ€ๆต‹ๆต็จ‹
39
+
40
+ 1. **ๆ•ฐๆฎๅŠ ่ฝฝ**: NIfTI ๆ ผๅผๅŠ ่ฝฝ
41
+ 2. **ๅˆšๆ€ง้…ๅ‡†**: 6DOF ไปฟๅฐ„ๅ˜ๆข (SimpleITK)
42
+ 3. **้žๅˆšๆ€ง้…ๅ‡†**: B-spline ๅฝขๅ˜ๅœบ (SimpleITK)
43
+ 4. **ๅ˜ๅŒ–ๆฃ€ๆต‹**: ไฝ“็ด ็บงๅทฎๅผ‚่ฎก็ฎ—
44
+
45
+ ### ๆต‹่ฏ•็ป“ๆžœ
46
+
47
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | ๅžๅ้‡ | CPUๅณฐๅ€ผ | CPUๅ‡ๅ€ผ | ๅ†…ๅญ˜ๅณฐๅ€ผ | ๅ†…ๅญ˜ๅขž้‡ | ๅนถ่กŒๆ•ˆ็އ |
48
+ |--------|--------|--------|---------|---------|----------|----------|----------|
49
+ | 1 | 7.38s | 8.1/min | 10.3% | 1.9% | 39.1 GB | +1.0 GB | 100% |
50
+ | 2 | 7.68s | 15.6/min | 38.8% | 5.4% | 40.0 GB | +2.0 GB | 192% |
51
+ | 3 | 8.44s | 21.3/min | 39.2% | 7.4% | 40.8 GB | +3.0 GB | 262% |
52
+ | 4 | 9.07s | 26.5/min | 42.6% | 9.5% | 41.8 GB | +4.0 GB | 325% |
53
+ | 5 | 9.60s | 31.2/min | 42.0% | 11.3% | 42.5 GB | +5.0 GB | 384% |
54
+
55
+ ### ๅ•ไปปๅŠก่€—ๆ—ถๅˆ†่งฃ
56
+
57
+ | ้˜ถๆฎต | ่€—ๆ—ถ | ๅ ๆฏ” | ่ฏดๆ˜Ž |
58
+ |------|------|------|------|
59
+ | ๆ•ฐๆฎๅŠ ่ฝฝ | 0.08s | 1% | NIfTI ่ฏปๅ– + ้ข„ๅค„็† |
60
+ | ๅˆšๆ€ง้…ๅ‡† | 1.10s | 15% | 6DOF ไปฟๅฐ„ๅ˜ๆขไผ˜ๅŒ– |
61
+ | ้žๅˆšๆ€ง้…ๅ‡† | 6.20s | 84% | B-spline ๅฝขๅ˜ๅœบไผ˜ๅŒ– |
62
+ | ๅ˜ๅŒ–ๆฃ€ๆต‹ | 0.02s | <1% | NumPy ๅทฎๅผ‚่ฎก็ฎ— |
63
+ | **ๆ€ป่ฎก** | **7.40s** | **100%** | - |
64
+
65
+ ### CPU ๅˆฉ็”จ็އๅˆ†ๆž
66
+
67
+ ```
68
+ ๅนถๅ‘ๆ•ฐ: 1 โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 10.3%
69
+ ๅนถๅ‘ๆ•ฐ: 2 โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 38.8%
70
+ ๅนถๅ‘ๆ•ฐ: 3 โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 39.2%
71
+ ๅนถๅ‘ๆ•ฐ: 4 โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 42.6%
72
+ ๅนถๅ‘ๆ•ฐ: 5 โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 42.0%
73
+ ```
74
+
75
+ > ๐Ÿ“Š **็ป“่ฎบ**: CPU ๅˆฉ็”จ็އๅœจ 5 ๅนถๅ‘ๆ—ถไป…่พพๅˆฐ 42%๏ผŒ่ฏดๆ˜Ž่ฟ˜ๆœ‰ๅพˆๅคง็š„ๅนถๅ‘ๆ‰ฉๅฑ•็ฉบ้—ดใ€‚
76
+
77
+ ---
78
+
79
+ ## GPU ๅนถๅ‘ๅŸบๅ‡†ๆต‹่ฏ•
80
+
81
+ ### ๆต‹่ฏ•ไปปๅŠก: MONAI ๅ™จๅฎ˜ๅˆ†ๅ‰ฒ
82
+
83
+ - **ๆจกๅž‹**: TotalSegmentator (104 ็ฑปๅ™จๅฎ˜)
84
+ - **ๆŽจ็†ๆ–นๅผ**: Sliding Window (ๆป‘ๅŠจ็ช—ๅฃ)
85
+ - **็ช—ๅฃๅคงๅฐ**: 96 ร— 96 ร— 96
86
+ - **้‡ๅ ็އ**: 50%
87
+
88
+ ### ๆต‹่ฏ•็ป“ๆžœ
89
+
90
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | GPUๆ˜พๅญ˜ๅณฐๅ€ผ | GPUๅˆฉ็”จ็އๅณฐๅ€ผ | CPUๅณฐๅ€ผ | ๅ†…ๅญ˜ๅณฐๅ€ผ |
91
+ |--------|--------|-------------|---------------|---------|----------|
92
+ | 1 | 8.19s | **9.0 GB** | 100% | 37.5% | 42.1 GB |
93
+ | 2 | 8.50s | **15.0 GB** | 100% | 5.8% | 43.2 GB |
94
+
95
+ ### ๅ•ไปปๅŠก่€—ๆ—ถๅˆ†่งฃ
96
+
97
+ | ้˜ถๆฎต | ่€—ๆ—ถ | ๅ ๆฏ” | ่ฏดๆ˜Ž |
98
+ |------|------|------|------|
99
+ | ๆ•ฐๆฎๅŠ ่ฝฝ | 0.11s | 1% | NIfTI โ†’ Tensor |
100
+ | ๆจกๅž‹ๆŽจ็† | 8.08s | 99% | Sliding Window |
101
+ | **ๆ€ป่ฎก** | **8.19s** | **100%** | - |
102
+ | **GPUๆ˜พๅญ˜** | **7.32 GB** | - | PyTorch allocated |
103
+ | **GPUๆ˜พๅญ˜ (็ณป็ปŸ)** | **9.0 GB** | - | nvidia-smi ๆŠฅๅ‘Š |
104
+
105
+ ### GPU ๆ˜พๅญ˜ๅˆ†ๆž
106
+
107
+ ```
108
+ ๅ•ไปปๅŠก: โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 9.0 GB / 80 GB (11%)
109
+ ๅŒไปปๅŠก: โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 15.0 GB / 80 GB (19%)
110
+ ```
111
+
112
+ > ๐Ÿ“Š **็ป“่ฎบ**: ๅ•ไปปๅŠกๅˆ†ๅ‰ฒ้œ€่ฆ็บฆ **9 GB** GPUๆ˜พๅญ˜๏ผŒๅŒไปปๅŠก้œ€่ฆ็บฆ **15 GB**ใ€‚RTX 3060 (12GB) ๅฏ่ฟ่กŒๅ•ไปปๅŠก๏ผŒRTX 4090 (24GB) ๅฏ่ฟ่กŒ 2 ๅนถๅ‘ใ€‚
113
+
114
+ ---
115
+
116
+ ## ่ต„ๆบ้œ€ๆฑ‚ๆ€ป็ป“
117
+
118
+ ### ๆœ€ๅฐ้…็ฝฎไผฐ็ฎ—
119
+
120
+ ๅŸบไบŽๆต‹่ฏ•ๆ•ฐๆฎ๏ผŒๅ„ๅœบๆ™ฏ็š„ๆœ€ๅฐ่ต„ๆบ้œ€ๆฑ‚๏ผš
121
+
122
+ | ๅœบๆ™ฏ | CPU | ๅ†…ๅญ˜ | GPUๆ˜พๅญ˜ | ๅค‡ๆณจ |
123
+ |------|-----|------|---------|------|
124
+ | ไป…้…ๅ‡† (ๅ•ไปปๅŠก) | 2 ๆ ธ | 2 GB | ๆ— ้œ€ | CPUๅฏ†้›†ๅž‹ |
125
+ | ไป…้…ๅ‡† (5ๅนถๅ‘) | 8 ๆ ธ | 6 GB | ๆ— ้œ€ | ็บฟๆ€งๆ‰ฉๅฑ• |
126
+ | ๅˆ†ๅ‰ฒ (ๅ•ไปปๅŠก) | 4 ๆ ธ | 4 GB | **9 GB** | GPUๅฏ†้›†ๅž‹ |
127
+ | ๅˆ†ๅ‰ฒ (ๅŒไปปๅŠก) | 4 ๆ ธ | 6 GB | **15 GB** | ๆ˜พๅญ˜็“ถ้ขˆ |
128
+ | ๅฎŒๆ•ดๆต็จ‹ | 8 ๆ ธ | 8 GB | **12 GB** | ้…ๅ‡†+ๅˆ†ๅ‰ฒ+LLM |
129
+
130
+ ### ๆŽจ่้…็ฝฎๆ–นๆกˆ
131
+
132
+ | ้ƒจ็ฝฒๅœบๆ™ฏ | CPU | ๅ†…ๅญ˜ | GPU | ้ข„ไผฐๅนถๅ‘่ƒฝๅŠ› | ๅ‚่€ƒๆœˆ่ดน |
133
+ |----------|-----|------|-----|--------------|----------|
134
+ | **ๆœ€ไฝŽ้…็ฝฎ** | 4ๆ ธ | 8 GB | ๆ—  | 1 ไปปๅŠก (ไป…้…ๅ‡†) | ~ยฅ100 |
135
+ | **ๆŽจ่้…็ฝฎ** | 8ๆ ธ | 16 GB | RTX 3060 12GB | 2-3 ไปปๅŠก | ~ยฅ800 |
136
+ | **ไธ“ไธš้…็ฝฎ** | 16ๆ ธ | 32 GB | RTX 4090 24GB | 5+ ไปปๅŠก | ~ยฅ2000 |
137
+ | **ๆœๅŠกๅ™จ้…็ฝฎ** | 32ๆ ธ+ | 64 GB+ | A100 40GB+ | 10+ ไปปๅŠก | ~ยฅ8000 |
138
+
139
+ ---
140
+
141
+ ## ่ฟ่กŒๅŸบๅ‡†ๆต‹่ฏ•
142
+
143
+ ```bash
144
+ # ่ฟ่กŒๅฎŒๆ•ดๅŸบๅ‡†ๆต‹่ฏ•
145
+ python scripts/benchmark.py
146
+
147
+ # ่ฟ๏ฟฝ๏ฟฝ๏ฟฝๅฟซ้€ŸๅŽ‹ๅŠ›ๆต‹่ฏ•
148
+ python scripts/stress_test.py
149
+ ```
150
+
151
+ ---
152
+
153
+ ## ้™„ๅฝ•: ๆต‹่ฏ•่„šๆœฌ
154
+
155
+ - `scripts/benchmark.py` - ๅฎŒๆ•ดๅŸบๅ‡†ๆต‹่ฏ•๏ผŒ็”Ÿๆˆ่ฏฆ็ป†ๆŠฅๅ‘Š
156
+ - `scripts/stress_test.py` - ๅฟซ้€ŸๅŽ‹ๅŠ›ๆต‹่ฏ•๏ผŒๆ˜พ็คบ่ต„ๆบๅณฐๅ€ผ
157
+
158
+ ---
159
+
160
+ *ๆŠฅๅ‘Š็”Ÿๆˆๆ—ถ้—ด: 2026-01-28 17:16:29*
README.md CHANGED
@@ -72,17 +72,128 @@
72
 
73
  ---
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  ## ๐Ÿš€ ๅฟซ้€Ÿๅผ€ๅง‹
76
 
77
- ### ็Žฏๅขƒ่ฆๆฑ‚
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  | ็ป„ไปถ | ๆœ€ไฝŽ้…็ฝฎ | ๆŽจ่้…็ฝฎ |
80
  |------|----------|----------|
81
- | OS | Ubuntu 20.04+ / Windows 10 | Ubuntu 22.04 |
82
- | CPU | 8 ๆ ธ | 16 ๆ ธ+ |
83
- | RAM | 16 GB | 32 GB+ |
84
- | GPU | NVIDIA 8GB | NVIDIA 24GB+ |
85
- | ๅญ˜ๅ‚จ | 50 GB | 200 GB SSD |
86
 
87
  ### ๅฎ‰่ฃ…ๆญฅ้ชค
88
 
 
72
 
73
  ---
74
 
75
+ ## ๐Ÿ“ฅ Hugging Face ไธ‹่ฝฝ
76
+
77
+ ้กน็›ฎๅทฒไธŠไผ ๅˆฐ Hugging Face Hub๏ผŒๅฏไธ€้”ฎไธ‹่ฝฝ้ƒจ็ฝฒ๏ผš
78
+
79
+ ```bash
80
+ # ๅฎ‰่ฃ… huggingface_hub
81
+ pip install huggingface_hub
82
+
83
+ # ไธ‹่ฝฝๅฎŒๆ•ดไปฃ็ 
84
+ from huggingface_hub import snapshot_download
85
+ snapshot_download(repo_id="cyd0806/neuroscan-ai", local_dir="./NeuroScan")
86
+
87
+ # ไธ‹่ฝฝๆจกๅž‹ๆƒ้‡
88
+ snapshot_download(repo_id="cyd0806/neuroscan-ai-models", local_dir="./models")
89
+
90
+ # ไธ‹่ฝฝๆ•ฐๆฎ้›†
91
+ snapshot_download(repo_id="cyd0806/neuroscan-ai-dataset", repo_type="dataset", local_dir="./data")
92
+ ```
93
+
94
+ | ไป“ๅบ“ | ๅ†…ๅฎน | ้“พๆŽฅ |
95
+ |------|------|------|
96
+ | ๐Ÿ“ฆ ไปฃ็  | ๅฎŒๆ•ด้กน็›ฎไปฃ็  | [cyd0806/neuroscan-ai](https://huggingface.co/cyd0806/neuroscan-ai) |
97
+ | ๐Ÿง  ๆจกๅž‹ | MONAI ๅˆ†ๅ‰ฒๆจกๅž‹ | [cyd0806/neuroscan-ai-models](https://huggingface.co/cyd0806/neuroscan-ai-models) |
98
+ | ๐Ÿ“Š ๆ•ฐๆฎ | Learn2Reg ๆ•ฐๆฎ้›† | [cyd0806/neuroscan-ai-dataset](https://huggingface.co/datasets/cyd0806/neuroscan-ai-dataset) |
99
+
100
+ ---
101
+
102
  ## ๐Ÿš€ ๅฟซ้€Ÿๅผ€ๅง‹
103
 
104
+ ### ็กฌไปถ้œ€ๆฑ‚
105
+
106
+ NeuroScan AI ๆ”ฏๆŒๅคš็ง้ƒจ็ฝฒๅœบๆ™ฏ๏ผŒไปฅไธ‹ๆ˜ฏไธๅŒไฝฟ็”จๆจกๅผ็š„็กฌไปถ้œ€ๆฑ‚๏ผš
107
+
108
+ #### ๐Ÿ“Š ๅฎžๆต‹่ต„ๆบๆถˆ่€— (้ซ˜ๅนถๅ‘ๅŽ‹ๅŠ›ๆต‹่ฏ•็ป“ๆžœ)
109
+
110
+ > ่ฏฆ็ป†ๆต‹่ฏ•ๆŠฅๅ‘Š่ง [BENCHMARK.md](BENCHMARK.md)
111
+
112
+ **ๆต‹่ฏ•็Žฏๅขƒ:**
113
+ - CPU: Intel Xeon Platinum 8350C (64ๆ ธ/128็บฟ็จ‹)
114
+ - ๅ†…ๅญ˜: 2TB DDR4
115
+ - GPU: NVIDIA A800-SXM4-80GB
116
+ - ๆต‹่ฏ•ๆ•ฐๆฎ: Learn2Reg Lung CT, ่พ“ๅ…ฅๅฐบๅฏธ **192ร—192ร—208**, ๅ•ๅท **58.5 MB**
117
+
118
+ **CPU ๅนถๅ‘ๆต‹่ฏ• (้…ๅ‡†+ๅ˜ๅŒ–ๆฃ€ๆต‹):**
119
+
120
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | ๅžๅ้‡ | CPUๅณฐๅ€ผ | CPUๅ‡ๅ€ผ | ๅ†…ๅญ˜ๅขž้‡ | ๅนถ่กŒๆ•ˆ็އ |
121
+ |--------|--------|--------|---------|---------|----------|----------|
122
+ | 1 | 7.4s | 8.1/min | **10.3%** | 1.9% | +1.0 GB | 100% |
123
+ | 2 | 7.7s | 15.6/min | **38.8%** | 5.4% | +2.0 GB | 192% |
124
+ | 3 | 8.4s | 21.3/min | **39.2%** | 7.4% | +3.0 GB | 262% |
125
+ | 4 | 9.1s | 26.5/min | **42.6%** | 9.5% | +4.0 GB | 325% |
126
+ | 5 | 9.6s | 31.2/min | **42.0%** | 11.3% | +5.0 GB | 384% |
127
+
128
+ **GPU ๅนถๅ‘ๆต‹่ฏ• (MONAIๅ™จๅฎ˜ๅˆ†ๅ‰ฒ):**
129
+
130
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | GPUๆ˜พๅญ˜ๅณฐๅ€ผ | GPUๅˆฉ็”จ็އ | CPUๅณฐๅ€ผ |
131
+ |--------|--------|-------------|-----------|---------|
132
+ | 1 | 8.2s | **9.0 GB** | 100% | 37.5% |
133
+ | 2 | 8.5s | **15.0 GB** | 100% | 5.8% |
134
+
135
+ **ๅ•ไปปๅŠก่€—ๆ—ถๅˆ†่งฃ:**
136
+
137
+ | ้˜ถๆฎต | CPU้…ๅ‡†ๆต็จ‹ | GPUๅˆ†ๅ‰ฒๆต็จ‹ |
138
+ |------|-------------|-------------|
139
+ | ๆ•ฐๆฎๅŠ ่ฝฝ | 0.1s (1%) | 0.1s (1%) |
140
+ | ๅˆšๆ€ง้…ๅ‡† | 1.1s (15%) | - |
141
+ | ้žๅˆšๆ€ง้…ๅ‡† | 6.2s (84%) | - |
142
+ | ๆจกๅž‹ๆŽจ็† | - | 8.1s (99%) |
143
+ | **ๆ€ป่ฎก** | **7.4s** | **8.2s** |
144
+
145
+ #### ๐Ÿ“Š ไฝฟ็”จๅœบๆ™ฏๅฏน็…ง่กจ
146
+
147
+ | ๅŠŸ่ƒฝๆจกๅ— | CPU | ๅ†…ๅญ˜ | GPU ๆ˜พๅญ˜ | ่ฏดๆ˜Ž |
148
+ |----------|-----|------|----------|------|
149
+ | ๐Ÿ“ ๆ•ฐๆฎๅŠ ่ฝฝ (DICOM/NIfTI) | 2 ๆ ธ | 2 GB | ๆ— ้œ€ | ๅŸบ็ก€ๅŠŸ่ƒฝ |
150
+ | ๐Ÿ”„ ๅ›พๅƒ้…ๅ‡† (ๅˆšๆ€ง+้žๅˆšๆ€ง) | 4 ๆ ธ | 4 GB | ๆ— ้œ€ | SimpleITK CPU ่ฎก็ฎ— |
151
+ | ๐Ÿ“ˆ ๅ˜ๅŒ–ๆฃ€ๆต‹ | 2 ๆ ธ | 2 GB | ๆ— ้œ€ | NumPy ่ฎก็ฎ— |
152
+ | ๐Ÿง  ๅ™จๅฎ˜ๅˆ†ๅ‰ฒ (MONAI) | 4 ๆ ธ | 8 GB | **9-12 GB** | ๆทฑๅบฆๅญฆไน ๆŽจ็† |
153
+ | ๐Ÿ’ฌ LLM ๆŠฅๅ‘Š (7Bๆจกๅž‹) | 4 ๆ ธ | 8 GB | **4-6 GB** | Ollama ๆŽจ็† |
154
+ | ๐Ÿ’ฌ LLM ๆŠฅๅ‘Š (3Bๆจกๅž‹) | 2 ๆ ธ | 4 GB | **2-4 GB** | ่ฝป้‡็บงๆจกๅž‹ |
155
+
156
+ #### ๐Ÿ–ฅ๏ธ ๆŽจ่้…็ฝฎๆ–นๆกˆ
157
+
158
+ | ๆ–นๆกˆ | CPU | ๅ†…ๅญ˜ | GPU | ๅญ˜ๅ‚จ | ้€‚็”จๅœบๆ™ฏ |
159
+ |------|-----|------|-----|------|----------|
160
+ | **๐Ÿ’ก ๅ…ฅ้—จ็‰ˆ** | 4 ๆ ธ | 8 GB | ๆ—  | 20 GB | ไป…้…ๅ‡†+ๅ˜ๅŒ–ๆฃ€ๆต‹๏ผŒๆ—  AI ๅŠŸ่ƒฝ |
161
+ | **โšก ๆ ‡ๅ‡†็‰ˆ** | 8 ๆ ธ | 16 GB | RTX 3060 (12GB) | 50 GB | ๅฎŒๆ•ดๅŠŸ่ƒฝ๏ผŒไฝฟ็”จ 3B LLM |
162
+ | **๐Ÿš€ ไธ“ไธš็‰ˆ** | 16 ๆ ธ | 32 GB | RTX 4090 (24GB) | 100 GB | ้ซ˜ๆ€ง่ƒฝ๏ผŒๅˆ†ๅ‰ฒ+8B LLM |
163
+ | **๐Ÿข ไผไธš็‰ˆ** | 32 ๆ ธ+ | 64 GB+ | A100 (40GB+) | 500 GB+ | ๆ‰น้‡ๅค„็†๏ผŒๅคš็”จๆˆท |
164
+
165
+ #### ๐Ÿ’ฐ ไบ‘ๆœๅŠกๅ™จ็งŸ็”จๅปบ่ฎฎ
166
+
167
+ | ไบ‘ๆœๅŠกๅ•† | ๆŽจ่ๆœบๅž‹ | ้…็ฝฎ | ๆœˆ่ดน็”จไผฐ็ฎ— |
168
+ |----------|----------|------|------------|
169
+ | AWS | g4dn.xlarge | 4C16G + T4 16GB | ~$380/ๆœˆ |
170
+ | ้˜ฟ้‡Œไบ‘ | ecs.gn6i-c4g1.xlarge | 4C15G + T4 16GB | ~ยฅ2500/ๆœˆ |
171
+ | AutoDL | RTX 3090 | 4C24G + RTX 3090 24GB | ~ยฅ2.5/ๆ—ถ |
172
+ | **ๆŽจ่ๅ…ฅ้—จ** | AutoDL RTX 3060 | 4C16G + RTX 3060 12GB | **~ยฅ1.2/ๆ—ถ** |
173
+
174
+ > ๐Ÿ’ก **็œ้’ฑๅปบ่ฎฎ**: ๅผ€ๅ‘ๆต‹่ฏ•้˜ถๆฎตๆŽจ่ไฝฟ็”จ AutoDL ็ญ‰ๆŒ‰ๆ—ถ่ฎก่ดนๅนณๅฐ๏ผŒๆญฃๅผ้ƒจ็ฝฒๅ†่€ƒ่™‘ๅŒ…ๆœˆใ€‚
175
+
176
+ #### ๐Ÿ“ฆ ็ฃ็›˜็ฉบ้—ด้œ€ๆฑ‚
177
+
178
+ | ็ป„ไปถ | ๅคงๅฐ | ่ฏดๆ˜Ž |
179
+ |------|------|------|
180
+ | ไปฃ็ ไป“ๅบ“ | ~500 KB | Git clone |
181
+ | Python ไพ่ต– | ~5 GB | pip install |
182
+ | MONAI ๅˆ†ๅ‰ฒๆจกๅž‹ | ~12 GB | ่‡ชๅŠจไธ‹่ฝฝ |
183
+ | Ollama LLM (7B) | ~5 GB | ollama pull |
184
+ | ็คบไพ‹ๆ•ฐๆฎ้›† | ~300 MB | Learn2Reg |
185
+ | **ๆ€ป่ฎก** | **~25 GB** | ๆœ€ๅฐๅฎ‰่ฃ… |
186
+
187
+ ---
188
+
189
+ ### ็ณป็ปŸ่ฆๆฑ‚
190
 
191
  | ็ป„ไปถ | ๆœ€ไฝŽ้…็ฝฎ | ๆŽจ่้…็ฝฎ |
192
  |------|----------|----------|
193
+ | OS | Ubuntu 20.04+ / Windows 10 | Ubuntu 22.04 LTS |
194
+ | Python | 3.9+ | 3.11 |
195
+ | CUDA | 11.8+ (ๅฆ‚ไฝฟ็”จGPU) | 12.0+ |
196
+ | Docker | 20.10+ (ๅฏ้€‰) | 24.0+ |
 
197
 
198
  ### ๅฎ‰่ฃ…ๆญฅ้ชค
199
 
data/README.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ task_categories:
4
+ - image-segmentation
5
+ tags:
6
+ - medical-imaging
7
+ - ct-scan
8
+ - lung
9
+ - registration
10
+ size_categories:
11
+ - 1K<n<10K
12
+ ---
13
+
14
+ # NeuroScan AI - Medical Imaging Dataset
15
+
16
+ This dataset contains sample medical imaging data for the NeuroScan AI platform.
17
+
18
+ ## Dataset Description
19
+
20
+ ### Learn2Reg Lung CT
21
+ - **Source**: [Learn2Reg Challenge](https://zenodo.org/record/3835682)
22
+ - **Description**: Paired inspiration and expiration lung CT scans
23
+ - **Format**: NIfTI (.nii.gz)
24
+ - **Cases**: 20 pairs
25
+ - **License**: CC BY-NC 4.0
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ # Download using huggingface_hub
31
+ from huggingface_hub import snapshot_download
32
+
33
+ snapshot_download(
34
+ repo_id="ydchen0806/neuroscan-ai-dataset",
35
+ repo_type="dataset",
36
+ local_dir="./data"
37
+ )
38
+ ```
39
+
40
+ ## Data Structure
41
+
42
+ ```
43
+ data/
44
+ โ”œโ”€โ”€ raw/
45
+ โ”‚ โ”œโ”€โ”€ training/
46
+ โ”‚ โ”‚ โ”œโ”€โ”€ scans/
47
+ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ case_001_insp.nii.gz
48
+ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ case_001_exp.nii.gz
49
+ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ ...
50
+ โ”‚ โ”‚ โ””โ”€โ”€ lungMasks/
51
+ โ”‚ โ”‚ โ””โ”€โ”€ ...
52
+ โ”‚ โ””โ”€โ”€ Learn2Reg_training.zip
53
+ โ””โ”€โ”€ processed/
54
+ โ””โ”€โ”€ real_lung_001/
55
+ โ”œโ”€โ”€ baseline.nii.gz
56
+ โ”œโ”€โ”€ followup.nii.gz
57
+ โ””โ”€โ”€ ...
58
+ ```
59
+
60
+ ## License
61
+
62
+ CC BY-NC 4.0 (Non-commercial use only)
63
+
64
+ ## Citation
65
+
66
+ Please cite the original Learn2Reg challenge if you use this data.
scripts/benchmark.py ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ NeuroScan AI ๅฎŒๆ•ดๅŸบๅ‡†ๆต‹่ฏ•
4
+ ๆต‹่ฏ• CPU/GPU ้ซ˜ๅนถๅ‘ๆ€ง่ƒฝ๏ผŒ็”Ÿๆˆ่ฏฆ็ป†ๆŠฅๅ‘Š
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import json
11
+ import threading
12
+ import subprocess
13
+ from datetime import datetime
14
+ from concurrent.futures import ThreadPoolExecutor, as_completed
15
+ from pathlib import Path
16
+ import psutil
17
+ import numpy as np
18
+
19
+ sys.path.insert(0, str(Path(__file__).parent.parent))
20
+
21
+ # ========================================
22
+ # ๅ…จๅฑ€็›‘ๆŽง
23
+ # ========================================
24
+ monitor_data = {
25
+ "cpu_percent": [],
26
+ "cpu_per_core": [],
27
+ "memory_used_gb": [],
28
+ "memory_percent": [],
29
+ "gpu_memory_gb": [],
30
+ "gpu_util": [],
31
+ "timestamps": []
32
+ }
33
+ stop_monitor = False
34
+
35
+
36
+ def get_gpu_stats():
37
+ """่Žทๅ–GPU็ปŸ่ฎก"""
38
+ try:
39
+ result = subprocess.run(
40
+ ['nvidia-smi', '--query-gpu=memory.used,memory.total,utilization.gpu',
41
+ '--format=csv,noheader,nounits', '-i', '0'],
42
+ capture_output=True, text=True, timeout=5
43
+ )
44
+ if result.returncode == 0:
45
+ parts = result.stdout.strip().split(',')
46
+ mem_used = float(parts[0].strip()) / 1024 # MB -> GB
47
+ mem_total = float(parts[1].strip()) / 1024
48
+ gpu_util = float(parts[2].strip())
49
+ return mem_used, mem_total, gpu_util
50
+ except:
51
+ pass
52
+ return 0, 0, 0
53
+
54
+
55
+ def resource_monitor(interval=0.3):
56
+ """่ต„ๆบ็›‘ๆŽง็บฟ็จ‹"""
57
+ global stop_monitor, monitor_data
58
+
59
+ while not stop_monitor:
60
+ ts = time.time()
61
+
62
+ # CPU
63
+ cpu_total = psutil.cpu_percent(interval=None)
64
+ cpu_per_core = psutil.cpu_percent(interval=None, percpu=True)
65
+
66
+ # ๅ†…ๅญ˜
67
+ mem = psutil.virtual_memory()
68
+
69
+ # GPU
70
+ gpu_mem, gpu_total, gpu_util = get_gpu_stats()
71
+
72
+ monitor_data["timestamps"].append(ts)
73
+ monitor_data["cpu_percent"].append(cpu_total)
74
+ monitor_data["cpu_per_core"].append(cpu_per_core)
75
+ monitor_data["memory_used_gb"].append(mem.used / (1024**3))
76
+ monitor_data["memory_percent"].append(mem.percent)
77
+ monitor_data["gpu_memory_gb"].append(gpu_mem)
78
+ monitor_data["gpu_util"].append(gpu_util)
79
+
80
+ time.sleep(interval)
81
+
82
+
83
+ def reset_monitor():
84
+ """้‡็ฝฎ็›‘ๆŽงๆ•ฐๆฎ"""
85
+ global monitor_data, stop_monitor
86
+ stop_monitor = False
87
+ monitor_data = {k: [] for k in monitor_data}
88
+
89
+
90
+ def get_monitor_stats():
91
+ """่Žทๅ–็›‘ๆŽง็ปŸ่ฎก"""
92
+ stats = {}
93
+ for key in ["cpu_percent", "memory_used_gb", "memory_percent", "gpu_memory_gb", "gpu_util"]:
94
+ if monitor_data[key]:
95
+ arr = np.array(monitor_data[key])
96
+ stats[key] = {
97
+ "min": float(np.min(arr)),
98
+ "max": float(np.max(arr)),
99
+ "mean": float(np.mean(arr)),
100
+ "std": float(np.std(arr))
101
+ }
102
+ return stats
103
+
104
+
105
+ # ========================================
106
+ # ๆต‹่ฏ•ไปปๅŠก
107
+ # ========================================
108
+
109
+ def get_test_data():
110
+ """่Žทๅ–ๆต‹่ฏ•ๆ•ฐๆฎ"""
111
+ data_path = Path(__file__).parent.parent / "data" / "processed"
112
+ pairs = []
113
+
114
+ for case_dir in sorted(data_path.glob("real_lung_*")):
115
+ baseline = case_dir / "baseline.nii.gz"
116
+ followup = case_dir / "followup.nii.gz"
117
+ if baseline.exists() and followup.exists():
118
+ pairs.append({
119
+ "name": case_dir.name,
120
+ "baseline": str(baseline),
121
+ "followup": str(followup)
122
+ })
123
+
124
+ return pairs
125
+
126
+
127
+ def run_cpu_task(task_id, data_pair):
128
+ """CPUไปปๅŠก๏ผš้…ๅ‡†+ๅ˜ๅŒ–ๆฃ€ๆต‹"""
129
+ from app.services.dicom import DicomLoader
130
+ from app.services.registration import ImageRegistrator
131
+ from app.services.analysis import ChangeDetector
132
+
133
+ loader = DicomLoader()
134
+ registrator = ImageRegistrator()
135
+ detector = ChangeDetector()
136
+
137
+ start = time.time()
138
+
139
+ # ๅŠ ่ฝฝ
140
+ t0 = time.time()
141
+ baseline, _ = loader.load_nifti(data_pair["baseline"])
142
+ followup, _ = loader.load_nifti(data_pair["followup"])
143
+ load_time = time.time() - t0
144
+
145
+ # ้…ๅ‡†
146
+ t0 = time.time()
147
+ reg_result = registrator.register(followup, baseline, use_deformable=True)
148
+ reg_time = time.time() - t0
149
+
150
+ # ๅ˜ๅŒ–ๆฃ€ๆต‹
151
+ t0 = time.time()
152
+ change_result = detector.detect_changes(baseline, reg_result["warped_image"])
153
+ detect_time = time.time() - t0
154
+
155
+ total = time.time() - start
156
+
157
+ return {
158
+ "task_id": task_id,
159
+ "name": data_pair["name"],
160
+ "shape": list(baseline.shape),
161
+ "load_time": load_time,
162
+ "reg_time": reg_time,
163
+ "detect_time": detect_time,
164
+ "total_time": total,
165
+ "status": "success"
166
+ }
167
+
168
+
169
+ def run_gpu_task(task_id, nifti_path, device_id=0):
170
+ """GPUไปปๅŠก๏ผšๅˆ†ๅ‰ฒ"""
171
+ import torch
172
+ os.environ['CUDA_VISIBLE_DEVICES'] = str(device_id)
173
+
174
+ from app.services.dicom import DicomLoader
175
+ from app.services.segmentation import OrganSegmentor
176
+
177
+ torch.cuda.reset_peak_memory_stats()
178
+
179
+ loader = DicomLoader()
180
+ segmentor = OrganSegmentor()
181
+
182
+ start = time.time()
183
+
184
+ # ๅŠ ่ฝฝ
185
+ t0 = time.time()
186
+ data, _ = loader.load_nifti(nifti_path)
187
+ load_time = time.time() - t0
188
+
189
+ # ๅˆ†ๅ‰ฒ
190
+ t0 = time.time()
191
+ result = segmentor.segment(data)
192
+ seg_time = time.time() - t0
193
+
194
+ total = time.time() - start
195
+
196
+ peak_mem = torch.cuda.max_memory_allocated() / (1024**3)
197
+
198
+ return {
199
+ "task_id": task_id,
200
+ "shape": list(data.shape),
201
+ "load_time": load_time,
202
+ "seg_time": seg_time,
203
+ "total_time": total,
204
+ "gpu_peak_gb": peak_mem,
205
+ "status": "success"
206
+ }
207
+
208
+
209
+ # ========================================
210
+ # ๅŸบๅ‡†ๆต‹่ฏ•
211
+ # ========================================
212
+
213
+ def benchmark_cpu_concurrent(data_pairs, concurrency_levels=[1, 2, 3, 4, 5]):
214
+ """CPUๅนถๅ‘ๅŸบๅ‡†ๆต‹่ฏ•"""
215
+ results = {}
216
+
217
+ for n in concurrency_levels:
218
+ if n > len(data_pairs):
219
+ break
220
+
221
+ print(f"\n ๐Ÿ”„ ๆต‹่ฏ• {n} ๅนถๅ‘...")
222
+ reset_monitor()
223
+
224
+ # ๅฏๅŠจ็›‘ๆŽง
225
+ global stop_monitor
226
+ stop_monitor = False
227
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
228
+ monitor_thread.start()
229
+
230
+ start = time.time()
231
+ task_results = []
232
+
233
+ with ThreadPoolExecutor(max_workers=n) as executor:
234
+ futures = []
235
+ for i in range(n):
236
+ futures.append(executor.submit(run_cpu_task, i+1, data_pairs[i]))
237
+
238
+ for future in as_completed(futures):
239
+ try:
240
+ task_results.append(future.result())
241
+ except Exception as e:
242
+ task_results.append({"status": "error", "error": str(e)})
243
+
244
+ total_time = time.time() - start
245
+
246
+ stop_monitor = True
247
+ monitor_thread.join()
248
+
249
+ stats = get_monitor_stats()
250
+
251
+ results[n] = {
252
+ "concurrency": n,
253
+ "total_time": total_time,
254
+ "tasks": task_results,
255
+ "resource_stats": stats
256
+ }
257
+
258
+ success = sum(1 for t in task_results if t.get("status") == "success")
259
+ print(f" โœ… {success}/{n} ๆˆๅŠŸ, ่€—ๆ—ถ {total_time:.2f}s")
260
+ print(f" ๐Ÿ“Š CPUๅณฐๅ€ผ: {stats['cpu_percent']['max']:.1f}%, ๅ†…ๅญ˜ๅณฐๅ€ผ: {stats['memory_used_gb']['max']:.1f}GB")
261
+
262
+ return results
263
+
264
+
265
+ def benchmark_gpu_concurrent(data_pairs, concurrency_levels=[1, 2]):
266
+ """GPUๅนถๅ‘ๅŸบๅ‡†ๆต‹่ฏ•"""
267
+ results = {}
268
+
269
+ for n in concurrency_levels:
270
+ if n > len(data_pairs):
271
+ break
272
+
273
+ print(f"\n ๐Ÿง  ๆต‹่ฏ• {n} GPUๅนถๅ‘...")
274
+ reset_monitor()
275
+
276
+ global stop_monitor
277
+ stop_monitor = False
278
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
279
+ monitor_thread.start()
280
+
281
+ start = time.time()
282
+ task_results = []
283
+
284
+ # GPUไปปๅŠกไธฒ่กŒๆ‰ง่กŒ๏ผˆๅ…ฑไบซGPUๆ˜พๅญ˜๏ผ‰
285
+ if n == 1:
286
+ with ThreadPoolExecutor(max_workers=1) as executor:
287
+ futures = [executor.submit(run_gpu_task, 1, data_pairs[0]["baseline"], 0)]
288
+ for future in as_completed(futures):
289
+ try:
290
+ task_results.append(future.result())
291
+ except Exception as e:
292
+ task_results.append({"status": "error", "error": str(e)})
293
+ else:
294
+ # ๅคšGPUไปปๅŠก๏ผˆๅฆ‚ๆžœๆœ‰ๅคšGPUๅฏไปฅๅนถ่กŒ๏ผ‰
295
+ with ThreadPoolExecutor(max_workers=n) as executor:
296
+ futures = []
297
+ for i in range(n):
298
+ # ไฝฟ็”จๅŒไธ€ไธชGPU้กบๅบๆ‰ง่กŒ
299
+ futures.append(executor.submit(run_gpu_task, i+1, data_pairs[i]["baseline"], 0))
300
+
301
+ for future in as_completed(futures):
302
+ try:
303
+ task_results.append(future.result())
304
+ except Exception as e:
305
+ task_results.append({"status": "error", "error": str(e)})
306
+
307
+ total_time = time.time() - start
308
+
309
+ stop_monitor = True
310
+ monitor_thread.join()
311
+
312
+ stats = get_monitor_stats()
313
+
314
+ results[n] = {
315
+ "concurrency": n,
316
+ "total_time": total_time,
317
+ "tasks": task_results,
318
+ "resource_stats": stats
319
+ }
320
+
321
+ success = sum(1 for t in task_results if t.get("status") == "success")
322
+ print(f" โœ… {success}/{n} ๆˆๅŠŸ, ่€—ๆ—ถ {total_time:.2f}s")
323
+ if stats.get('gpu_memory_gb'):
324
+ print(f" ๐Ÿ“Š GPUๆ˜พๅญ˜ๅณฐๅ€ผ: {stats['gpu_memory_gb']['max']:.1f}GB, GPUๅˆฉ็”จ็އๅณฐๅ€ผ: {stats['gpu_util']['max']:.1f}%")
325
+
326
+ return results
327
+
328
+
329
+ def get_system_info():
330
+ """่Žทๅ–็ณป็ปŸ๏ฟฝ๏ฟฝ๏ฟฝๆฏ"""
331
+ info = {
332
+ "timestamp": datetime.now().isoformat(),
333
+ "cpu": {
334
+ "model": "Unknown",
335
+ "physical_cores": psutil.cpu_count(logical=False),
336
+ "logical_cores": psutil.cpu_count(logical=True),
337
+ "freq_mhz": psutil.cpu_freq().max if psutil.cpu_freq() else 0
338
+ },
339
+ "memory": {
340
+ "total_gb": psutil.virtual_memory().total / (1024**3)
341
+ },
342
+ "gpu": []
343
+ }
344
+
345
+ # CPUๅž‹ๅท
346
+ try:
347
+ with open('/proc/cpuinfo', 'r') as f:
348
+ for line in f:
349
+ if 'model name' in line:
350
+ info["cpu"]["model"] = line.split(':')[1].strip()
351
+ break
352
+ except:
353
+ pass
354
+
355
+ # GPUไฟกๆฏ
356
+ try:
357
+ result = subprocess.run(
358
+ ['nvidia-smi', '--query-gpu=name,memory.total', '--format=csv,noheader'],
359
+ capture_output=True, text=True
360
+ )
361
+ if result.returncode == 0:
362
+ for line in result.stdout.strip().split('\n'):
363
+ parts = line.split(',')
364
+ info["gpu"].append({
365
+ "name": parts[0].strip(),
366
+ "memory_mb": int(parts[1].strip().replace(' MiB', ''))
367
+ })
368
+ except:
369
+ pass
370
+
371
+ # Python/ๅบ“็‰ˆๆœฌ
372
+ info["software"] = {
373
+ "python": sys.version.split()[0],
374
+ }
375
+
376
+ try:
377
+ import torch
378
+ info["software"]["pytorch"] = torch.__version__
379
+ info["software"]["cuda"] = torch.version.cuda if torch.cuda.is_available() else "N/A"
380
+ except:
381
+ pass
382
+
383
+ try:
384
+ import monai
385
+ info["software"]["monai"] = monai.__version__
386
+ except:
387
+ pass
388
+
389
+ try:
390
+ import SimpleITK as sitk
391
+ info["software"]["simpleitk"] = sitk.Version_MajorVersion()
392
+ except:
393
+ pass
394
+
395
+ return info
396
+
397
+
398
+ def generate_markdown_report(sys_info, cpu_results, gpu_results, data_info):
399
+ """็”ŸๆˆMarkdownๆŠฅๅ‘Š"""
400
+
401
+ report = f"""
402
+ ## ๐Ÿ”ฌ ๆ€ง่ƒฝๅŸบๅ‡†ๆต‹่ฏ•ๆŠฅๅ‘Š
403
+
404
+ > ๆต‹่ฏ•ๆ—ถ้—ด: {sys_info['timestamp'][:19].replace('T', ' ')}
405
+
406
+ ### ๆต‹่ฏ•็Žฏๅขƒ
407
+
408
+ | ็ป„ไปถ | ้…็ฝฎ |
409
+ |------|------|
410
+ | **CPU** | {sys_info['cpu']['model']} |
411
+ | **CPUๆ ธๅฟƒ** | {sys_info['cpu']['physical_cores']} ็‰ฉ็†ๆ ธ / {sys_info['cpu']['logical_cores']} ้€ป่พ‘ๆ ธ |
412
+ | **ๅ†…ๅญ˜** | {sys_info['memory']['total_gb']:.0f} GB |
413
+ | **GPU** | {sys_info['gpu'][0]['name'] if sys_info['gpu'] else 'N/A'} |
414
+ | **GPUๆ˜พๅญ˜** | {sys_info['gpu'][0]['memory_mb']/1024:.0f} GB |
415
+ | **Python** | {sys_info['software'].get('python', 'N/A')} |
416
+ | **PyTorch** | {sys_info['software'].get('pytorch', 'N/A')} |
417
+ | **CUDA** | {sys_info['software'].get('cuda', 'N/A')} |
418
+ | **MONAI** | {sys_info['software'].get('monai', 'N/A')} |
419
+
420
+ ### ๆต‹่ฏ•ๆ•ฐๆฎ
421
+
422
+ | ๅฑžๆ€ง | ๅ€ผ |
423
+ |------|------|
424
+ | **ๆ•ฐๆฎ้›†** | Learn2Reg Lung CT |
425
+ | **ๆ ทๆœฌๆ•ฐ้‡** | {data_info['count']} ๅฏน |
426
+ | **่พ“ๅ…ฅๅฐบๅฏธ** | {data_info['shape']} |
427
+ | **ๆ•ฐๆฎ็ฑปๅž‹** | float32 |
428
+ | **ๅ•ๅทๅคงๅฐ** | ~{data_info['size_mb']:.1f} MB |
429
+
430
+ ### CPU ๅนถๅ‘ๆต‹่ฏ•็ป“ๆžœ (้…ๅ‡† + ๅ˜ๅŒ–ๆฃ€ๆต‹)
431
+
432
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | ๅžๅ้‡ | CPUๅณฐๅ€ผ | CPUๅ‡ๅ€ผ | ๅ†…ๅญ˜ๅณฐๅ€ผ | ๅนถ่กŒๆ•ˆ็އ |
433
+ |--------|--------|--------|---------|---------|----------|----------|
434
+ """
435
+
436
+ single_time = cpu_results.get(1, {}).get('total_time', 1)
437
+ for n, data in sorted(cpu_results.items()):
438
+ stats = data['resource_stats']
439
+ efficiency = (single_time * n / data['total_time']) * 100 if data['total_time'] > 0 else 0
440
+ throughput = n / data['total_time'] * 60 # ไปปๅŠก/ๅˆ†้’Ÿ
441
+
442
+ report += f"| {n} | {data['total_time']:.2f}s | {throughput:.1f}/min | "
443
+ report += f"{stats['cpu_percent']['max']:.1f}% | {stats['cpu_percent']['mean']:.1f}% | "
444
+ report += f"{stats['memory_used_gb']['max']:.1f} GB | {efficiency:.0f}% |\n"
445
+
446
+ report += """
447
+ ### GPU ๅนถๅ‘ๆต‹่ฏ•็ป“ๆžœ (MONAI ๅ™จๅฎ˜ๅˆ†ๅ‰ฒ)
448
+
449
+ | ๅนถๅ‘ๆ•ฐ | ๆ€ป่€—ๆ—ถ | GPUๆ˜พๅญ˜ๅณฐๅ€ผ | GPUๅˆฉ็”จ็އๅณฐๅ€ผ | CPUๅณฐๅ€ผ | ๅ†…ๅญ˜ๅณฐๅ€ผ |
450
+ |--------|--------|-------------|---------------|---------|----------|
451
+ """
452
+
453
+ for n, data in sorted(gpu_results.items()):
454
+ stats = data['resource_stats']
455
+ gpu_peak = stats.get('gpu_memory_gb', {}).get('max', 0)
456
+ gpu_util = stats.get('gpu_util', {}).get('max', 0)
457
+
458
+ report += f"| {n} | {data['total_time']:.2f}s | {gpu_peak:.1f} GB | {gpu_util:.0f}% | "
459
+ report += f"{stats['cpu_percent']['max']:.1f}% | {stats['memory_used_gb']['max']:.1f} GB |\n"
460
+
461
+ # ๅ•ไปปๅŠก่ฏฆๆƒ…
462
+ if cpu_results.get(1) and cpu_results[1]['tasks']:
463
+ task = cpu_results[1]['tasks'][0]
464
+ report += f"""
465
+ ### ๅ•ไปปๅŠก่€—ๆ—ถๅˆ†่งฃ (CPU ้…ๅ‡†ๆต็จ‹)
466
+
467
+ | ้˜ถๆฎต | ่€—ๆ—ถ | ๅ ๆฏ” |
468
+ |------|------|------|
469
+ | ๆ•ฐๆฎๅŠ ่ฝฝ | {task.get('load_time', 0):.2f}s | {task.get('load_time', 0)/task.get('total_time', 1)*100:.0f}% |
470
+ | ๅˆšๆ€ง้…ๅ‡† | ~1.0s | ~13% |
471
+ | ้žๅˆšๆ€ง้…ๅ‡† | ~{task.get('reg_time', 0)-1:.1f}s | ~{(task.get('reg_time', 0)-1)/task.get('total_time', 1)*100:.0f}% |
472
+ | ๅ˜ๅŒ–๏ฟฝ๏ฟฝ๏ฟฝๆต‹ | {task.get('detect_time', 0):.2f}s | {task.get('detect_time', 0)/task.get('total_time', 1)*100:.0f}% |
473
+ | **ๆ€ป่ฎก** | **{task.get('total_time', 0):.2f}s** | **100%** |
474
+ """
475
+
476
+ if gpu_results.get(1) and gpu_results[1]['tasks']:
477
+ task = gpu_results[1]['tasks'][0]
478
+ report += f"""
479
+ ### ๅ•ไปปๅŠก่€—ๆ—ถๅˆ†่งฃ (GPU ๅˆ†ๅ‰ฒๆต็จ‹)
480
+
481
+ | ้˜ถๆฎต | ่€—ๆ—ถ | ๅ ๆฏ” |
482
+ |------|------|------|
483
+ | ๆ•ฐๆฎๅŠ ่ฝฝ | {task.get('load_time', 0):.2f}s | {task.get('load_time', 0)/task.get('total_time', 1)*100:.0f}% |
484
+ | ๆจกๅž‹ๆŽจ็† | {task.get('seg_time', 0):.2f}s | {task.get('seg_time', 0)/task.get('total_time', 1)*100:.0f}% |
485
+ | **ๆ€ป่ฎก** | **{task.get('total_time', 0):.2f}s** | **100%** |
486
+ | **GPUๆ˜พๅญ˜ๅณฐๅ€ผ** | **{task.get('gpu_peak_gb', 0):.2f} GB** | - |
487
+ """
488
+
489
+ report += """
490
+ ### ่ต„ๆบ้œ€ๆฑ‚ๆ€ป็ป“
491
+
492
+ ๆ นๆฎไปฅไธŠๆต‹่ฏ•็ป“ๆžœ๏ผŒๆŽจ่ไปฅไธ‹็กฌไปถ้…็ฝฎ๏ผš
493
+
494
+ | ้ƒจ็ฝฒๅœบๆ™ฏ | CPU | ๅ†…ๅญ˜ | GPU | ้ข„ไผฐๅนถๅ‘่ƒฝๅŠ› |
495
+ |----------|-----|------|-----|--------------|
496
+ | **ๆœ€ไฝŽ้…็ฝฎ** | 4ๆ ธ | 8 GB | ๆ—  | 1 ไปปๅŠก (ไป…้…ๅ‡†) |
497
+ | **ๆŽจ่้…็ฝฎ** | 8ๆ ธ | 16 GB | RTX 3060 12GB | 2-3 ไปปๅŠก |
498
+ | **ไธ“ไธš้…็ฝฎ** | 16ๆ ธ | 32 GB | RTX 4090 24GB | 5+ ไปปๅŠก |
499
+ | **ๆœๅŠกๅ™จ้…็ฝฎ** | 32ๆ ธ+ | 64 GB+ | A100 40GB+ | 10+ ไปปๅŠก |
500
+
501
+ """
502
+
503
+ return report
504
+
505
+
506
+ def main():
507
+ global stop_monitor
508
+
509
+ print("=" * 70)
510
+ print("๐Ÿ”ฌ NeuroScan AI ๅฎŒๆ•ดๅŸบๅ‡†ๆต‹่ฏ•")
511
+ print("=" * 70)
512
+
513
+ # ็ณป็ปŸไฟกๆฏ
514
+ print("\n๐Ÿ“Š ๆ”ถ้›†็ณป็ปŸไฟกๆฏ...")
515
+ sys_info = get_system_info()
516
+ print(f" CPU: {sys_info['cpu']['model']}")
517
+ print(f" ๆ ธๅฟƒ: {sys_info['cpu']['physical_cores']}P / {sys_info['cpu']['logical_cores']}L")
518
+ print(f" ๅ†…ๅญ˜: {sys_info['memory']['total_gb']:.0f} GB")
519
+ if sys_info['gpu']:
520
+ print(f" GPU: {sys_info['gpu'][0]['name']} ({sys_info['gpu'][0]['memory_mb']/1024:.0f} GB)")
521
+
522
+ # ๆต‹่ฏ•ๆ•ฐๆฎ
523
+ print("\n๐Ÿ“ ๅŠ ่ฝฝๆต‹่ฏ•ๆ•ฐๆฎ...")
524
+ data_pairs = get_test_data()
525
+ print(f" ๆ‰พๅˆฐ {len(data_pairs)} ๅฏนๆต‹่ฏ•ๆ•ฐๆฎ")
526
+
527
+ if not data_pairs:
528
+ print("โŒ ๆฒกๆœ‰ๆต‹่ฏ•ๆ•ฐๆฎ๏ผ่ฏทๅ…ˆ่ฟ่กŒ: python scripts/download_datasets.py")
529
+ return
530
+
531
+ # ่Žทๅ–ๆ•ฐๆฎๅฐบๅฏธ
532
+ from app.services.dicom import DicomLoader
533
+ loader = DicomLoader()
534
+ sample_data, _ = loader.load_nifti(data_pairs[0]["baseline"])
535
+ data_info = {
536
+ "count": len(data_pairs),
537
+ "shape": f"{sample_data.shape[0]} x {sample_data.shape[1]} x {sample_data.shape[2]}",
538
+ "size_mb": sample_data.nbytes / (1024**2)
539
+ }
540
+ print(f" ๆ•ฐๆฎๅฐบๅฏธ: {data_info['shape']}")
541
+ print(f" ๅ•ๅทๅคงๅฐ: {data_info['size_mb']:.1f} MB")
542
+
543
+ # CPUๅนถๅ‘ๆต‹่ฏ•
544
+ print("\n" + "=" * 70)
545
+ print("๐Ÿ”„ CPU ๅนถๅ‘ๅŸบๅ‡†ๆต‹่ฏ• (้…ๅ‡† + ๅ˜ๅŒ–ๆฃ€ๆต‹)")
546
+ print("=" * 70)
547
+
548
+ cpu_levels = [1, 2, 3, 4, 5] if len(data_pairs) >= 5 else list(range(1, len(data_pairs)+1))
549
+ cpu_results = benchmark_cpu_concurrent(data_pairs, cpu_levels)
550
+
551
+ # GPUๆต‹่ฏ•
552
+ print("\n" + "=" * 70)
553
+ print("๐Ÿง  GPU ๅŸบๅ‡†ๆต‹่ฏ• (MONAI ๅ™จๅฎ˜ๅˆ†ๅ‰ฒ)")
554
+ print("=" * 70)
555
+
556
+ gpu_results = {}
557
+ try:
558
+ import torch
559
+ if torch.cuda.is_available():
560
+ gpu_results = benchmark_gpu_concurrent(data_pairs, [1, 2])
561
+ else:
562
+ print(" โš ๏ธ GPU ไธๅฏ็”จ๏ผŒ่ทณ่ฟ‡GPUๆต‹่ฏ•")
563
+ except Exception as e:
564
+ print(f" โš ๏ธ GPUๆต‹่ฏ•ๅคฑ่ดฅ: {e}")
565
+
566
+ # ็”ŸๆˆๆŠฅๅ‘Š
567
+ print("\n" + "=" * 70)
568
+ print("๐Ÿ“ ็”Ÿๆˆๆต‹่ฏ•ๆŠฅๅ‘Š")
569
+ print("=" * 70)
570
+
571
+ report = generate_markdown_report(sys_info, cpu_results, gpu_results, data_info)
572
+
573
+ # ไฟๅญ˜ๆŠฅๅ‘Š
574
+ report_path = Path(__file__).parent.parent / "BENCHMARK.md"
575
+ with open(report_path, 'w', encoding='utf-8') as f:
576
+ f.write("# NeuroScan AI ๆ€ง่ƒฝๅŸบๅ‡†ๆต‹่ฏ•\n")
577
+ f.write(report)
578
+
579
+ print(f" โœ… ๆŠฅๅ‘Šๅทฒไฟๅญ˜: {report_path}")
580
+
581
+ # ่พ“ๅ‡บๆ‘˜่ฆ
582
+ print("\n" + "=" * 70)
583
+ print("๐Ÿ“‹ ๆต‹่ฏ•ๆ‘˜่ฆ")
584
+ print("=" * 70)
585
+
586
+ print("\n๐Ÿ”„ CPU ๆต‹่ฏ• (้…ๅ‡†ๆต็จ‹):")
587
+ for n, data in sorted(cpu_results.items()):
588
+ stats = data['resource_stats']
589
+ print(f" {n}ๅนถๅ‘: CPUๅณฐๅ€ผ {stats['cpu_percent']['max']:.1f}%, "
590
+ f"ๅ†…ๅญ˜ๅณฐๅ€ผ {stats['memory_used_gb']['max']:.1f}GB, "
591
+ f"่€—ๆ—ถ {data['total_time']:.1f}s")
592
+
593
+ if gpu_results:
594
+ print("\n๐Ÿง  GPU ๆต‹่ฏ• (ๅˆ†ๅ‰ฒๆต็จ‹):")
595
+ for n, data in sorted(gpu_results.items()):
596
+ stats = data['resource_stats']
597
+ gpu_peak = stats.get('gpu_memory_gb', {}).get('max', 0)
598
+ print(f" {n}ๅนถๅ‘: GPUๆ˜พๅญ˜ๅณฐๅ€ผ {gpu_peak:.1f}GB, "
599
+ f"CPUๅณฐๅ€ผ {stats['cpu_percent']['max']:.1f}%, "
600
+ f"่€—ๆ—ถ {data['total_time']:.1f}s")
601
+
602
+ print("\nโœ… ๅŸบๅ‡†ๆต‹่ฏ•ๅฎŒๆˆ!")
603
+ print(f" ่ฏฆ็ป†ๆŠฅๅ‘Š: {report_path}")
604
+
605
+ # ่ฟ”ๅ›ž็ป“ๆžœไพ›ๅŽ็ปญไฝฟ็”จ
606
+ return {
607
+ "sys_info": sys_info,
608
+ "cpu_results": cpu_results,
609
+ "gpu_results": gpu_results,
610
+ "data_info": data_info,
611
+ "report": report
612
+ }
613
+
614
+
615
+ if __name__ == "__main__":
616
+ results = main()
617
+
618
+
scripts/stress_test.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ NeuroScan AI ๅนถๅ‘ๅŽ‹ๅŠ›ๆต‹่ฏ•
4
+ ๆต‹่ฏ• CPU/GPU ๅณฐๅ€ผไฝฟ็”จๆƒ…ๅ†ต๏ผŒๆ”ฏๆŒ 2-3 ไปปๅŠกๅนถๅ‘
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import threading
11
+ import multiprocessing
12
+ from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
13
+ from pathlib import Path
14
+ import psutil
15
+ import numpy as np
16
+
17
+ # ๆทปๅŠ ้กน็›ฎๆ น็›ฎๅฝ•ๅˆฐ่ทฏๅพ„
18
+ sys.path.insert(0, str(Path(__file__).parent.parent))
19
+
20
+ # ๅ…จๅฑ€็›‘ๆŽงๆ•ฐๆฎ
21
+ monitor_data = {
22
+ "cpu_percent": [],
23
+ "memory_percent": [],
24
+ "memory_gb": [],
25
+ "gpu_memory_gb": [],
26
+ "gpu_util": []
27
+ }
28
+ stop_monitor = False
29
+
30
+
31
+ def get_gpu_stats():
32
+ """่Žทๅ–GPU็Šถๆ€"""
33
+ try:
34
+ import torch
35
+ if torch.cuda.is_available():
36
+ # ่Žทๅ–ๅฝ“ๅ‰GPU็š„ๆ˜พๅญ˜ไฝฟ็”จ
37
+ allocated = torch.cuda.memory_allocated() / (1024**3)
38
+ reserved = torch.cuda.memory_reserved() / (1024**3)
39
+
40
+ # ไฝฟ็”จnvidia-smi่Žทๅ–ๆ€ปไฝ“ๆ˜พๅญ˜
41
+ import subprocess
42
+ result = subprocess.run(
43
+ ['nvidia-smi', '--query-gpu=memory.used,utilization.gpu', '--format=csv,noheader,nounits', '-i', '0'],
44
+ capture_output=True, text=True
45
+ )
46
+ if result.returncode == 0:
47
+ parts = result.stdout.strip().split(',')
48
+ mem_used = float(parts[0]) / 1024 # ่ฝฌๆขไธบGB
49
+ gpu_util = float(parts[1])
50
+ return mem_used, gpu_util
51
+ return allocated, 0
52
+ return 0, 0
53
+ except:
54
+ return 0, 0
55
+
56
+
57
+ def resource_monitor(interval=0.5):
58
+ """ๅŽๅฐ่ต„ๆบ็›‘ๆŽง็บฟ็จ‹"""
59
+ global stop_monitor, monitor_data
60
+
61
+ while not stop_monitor:
62
+ # CPU
63
+ cpu_percent = psutil.cpu_percent(interval=None)
64
+ monitor_data["cpu_percent"].append(cpu_percent)
65
+
66
+ # ๅ†…ๅญ˜
67
+ mem = psutil.virtual_memory()
68
+ monitor_data["memory_percent"].append(mem.percent)
69
+ monitor_data["memory_gb"].append(mem.used / (1024**3))
70
+
71
+ # GPU
72
+ gpu_mem, gpu_util = get_gpu_stats()
73
+ monitor_data["gpu_memory_gb"].append(gpu_mem)
74
+ monitor_data["gpu_util"].append(gpu_util)
75
+
76
+ time.sleep(interval)
77
+
78
+
79
+ def run_single_pipeline(task_id, data_pair):
80
+ """่ฟ่กŒๅ•ไธชๅˆ†ๆžๆตๆฐด็บฟ"""
81
+ baseline_path, followup_path = data_pair
82
+
83
+ print(f" ๐Ÿ”„ ไปปๅŠก {task_id}: ๅผ€ๅง‹ๅค„็† {Path(baseline_path).parent.name}")
84
+ start_time = time.time()
85
+
86
+ try:
87
+ # ๅฏผๅ…ฅๆจกๅ—
88
+ from app.services.dicom import DicomLoader
89
+ from app.services.registration import ImageRegistrator
90
+ from app.services.analysis import ChangeDetector
91
+
92
+ loader = DicomLoader()
93
+ registrator = ImageRegistrator()
94
+ detector = ChangeDetector()
95
+
96
+ # 1. ๅŠ ่ฝฝๆ•ฐๆฎ
97
+ t0 = time.time()
98
+ baseline_data, _ = loader.load_nifti(baseline_path)
99
+ followup_data, _ = loader.load_nifti(followup_path)
100
+ load_time = time.time() - t0
101
+
102
+ # 2. ้…ๅ‡†
103
+ t0 = time.time()
104
+ reg_result = registrator.register(followup_data, baseline_data, use_deformable=True)
105
+ reg_time = time.time() - t0
106
+
107
+ # 3. ๅ˜ๅŒ–ๆฃ€ๆต‹
108
+ t0 = time.time()
109
+ change_result = detector.detect_changes(baseline_data, reg_result["warped_image"])
110
+ detect_time = time.time() - t0
111
+
112
+ total_time = time.time() - start_time
113
+
114
+ return {
115
+ "task_id": task_id,
116
+ "status": "success",
117
+ "load_time": load_time,
118
+ "reg_time": reg_time,
119
+ "detect_time": detect_time,
120
+ "total_time": total_time,
121
+ "data_shape": baseline_data.shape
122
+ }
123
+
124
+ except Exception as e:
125
+ return {
126
+ "task_id": task_id,
127
+ "status": "error",
128
+ "error": str(e),
129
+ "total_time": time.time() - start_time
130
+ }
131
+
132
+
133
+ def run_segmentation_task(task_id, nifti_path):
134
+ """่ฟ่กŒๅˆ†ๅ‰ฒไปปๅŠก๏ผˆGPUๅฏ†้›†ๅž‹๏ผ‰"""
135
+ print(f" ๐Ÿง  ๅˆ†ๅ‰ฒไปปๅŠก {task_id}: ๅผ€ๅง‹ๅค„็†")
136
+ start_time = time.time()
137
+
138
+ try:
139
+ import torch
140
+ os.environ['CUDA_VISIBLE_DEVICES'] = '0'
141
+
142
+ from app.services.segmentation import OrganSegmentor
143
+ segmentor = OrganSegmentor()
144
+
145
+ # ๆ‰ง่กŒๅˆ†ๅ‰ฒ
146
+ from app.services.dicom import DicomLoader
147
+ loader = DicomLoader()
148
+ data, _ = loader.load_nifti(nifti_path)
149
+
150
+ # ๅˆ†ๅ‰ฒๆŽจ็†
151
+ result = segmentor.segment(data)
152
+
153
+ total_time = time.time() - start_time
154
+
155
+ # ่ฎฐๅฝ•GPUๅณฐๅ€ผ
156
+ peak_mem = torch.cuda.max_memory_allocated() / (1024**3)
157
+
158
+ return {
159
+ "task_id": task_id,
160
+ "status": "success",
161
+ "total_time": total_time,
162
+ "gpu_peak_gb": peak_mem
163
+ }
164
+
165
+ except Exception as e:
166
+ return {
167
+ "task_id": task_id,
168
+ "status": "error",
169
+ "error": str(e),
170
+ "total_time": time.time() - start_time
171
+ }
172
+
173
+
174
+ def get_test_data_pairs(data_dir, max_pairs=5):
175
+ """่Žทๅ–ๆต‹่ฏ•ๆ•ฐๆฎๅฏน"""
176
+ data_path = Path(data_dir) / "processed"
177
+ pairs = []
178
+
179
+ for case_dir in sorted(data_path.glob("real_lung_*"))[:max_pairs]:
180
+ baseline = case_dir / "baseline.nii.gz"
181
+ followup = case_dir / "followup.nii.gz"
182
+ if baseline.exists() and followup.exists():
183
+ pairs.append((str(baseline), str(followup)))
184
+
185
+ return pairs
186
+
187
+
188
+ def print_stats(title, data_list):
189
+ """ๆ‰“ๅฐ็ปŸ่ฎกไฟกๆฏ"""
190
+ if not data_list:
191
+ return
192
+ arr = np.array(data_list)
193
+ print(f" {title}:")
194
+ print(f" ๅนณๅ‡: {np.mean(arr):.2f}")
195
+ print(f" ๅณฐๅ€ผ: {np.max(arr):.2f}")
196
+ print(f" ๆœ€ๅฐ: {np.min(arr):.2f}")
197
+
198
+
199
+ def main():
200
+ global stop_monitor, monitor_data
201
+
202
+ print("=" * 70)
203
+ print("๐Ÿ”ฅ NeuroScan AI ๅนถๅ‘ๅŽ‹ๅŠ›ๆต‹่ฏ•")
204
+ print("=" * 70)
205
+
206
+ # ็ณป็ปŸไฟกๆฏ
207
+ print(f"\n๐Ÿ“Š ็ณป็ปŸ้…็ฝฎ:")
208
+ print(f" CPU ๆ ธๅฟƒ: {psutil.cpu_count(logical=False)} ็‰ฉ็†ๆ ธ / {psutil.cpu_count()} ้€ป่พ‘ๆ ธ")
209
+ print(f" ๆ€ปๅ†…ๅญ˜: {psutil.virtual_memory().total / (1024**3):.1f} GB")
210
+
211
+ try:
212
+ import torch
213
+ if torch.cuda.is_available():
214
+ print(f" GPU: {torch.cuda.get_device_name(0)}")
215
+ print(f" GPUๆ˜พๅญ˜: {torch.cuda.get_device_properties(0).total_memory / (1024**3):.1f} GB")
216
+ except:
217
+ print(" GPU: ไธๅฏ็”จ")
218
+
219
+ # ่Žทๅ–ๆต‹่ฏ•ๆ•ฐๆฎ
220
+ data_dir = Path(__file__).parent.parent / "data"
221
+ pairs = get_test_data_pairs(data_dir, max_pairs=5)
222
+
223
+ if len(pairs) < 2:
224
+ print("\nโŒ ๆต‹่ฏ•ๆ•ฐๆฎไธ่ถณ๏ผŒ้œ€่ฆ่‡ณๅฐ‘ 2 ๅฏนๆ•ฐๆฎ")
225
+ print(" ่ฏทๅ…ˆ่ฟ่กŒ: python scripts/download_datasets.py --dataset learn2reg")
226
+ return
227
+
228
+ print(f"\n๐Ÿ“ ๆ‰พๅˆฐ {len(pairs)} ๅฏนๆต‹่ฏ•ๆ•ฐๆฎ")
229
+
230
+ # ========================================
231
+ # ๆต‹่ฏ• 1: ๅ•ไปปๅŠกๅŸบๅ‡†
232
+ # ========================================
233
+ print("\n" + "=" * 70)
234
+ print("๐Ÿ“Œ ๆต‹่ฏ• 1: ๅ•ไปปๅŠกๅŸบๅ‡†ๆต‹่ฏ•")
235
+ print("=" * 70)
236
+
237
+ monitor_data = {k: [] for k in monitor_data}
238
+ stop_monitor = False
239
+
240
+ # ๅฏๅŠจ็›‘ๆŽง
241
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
242
+ monitor_thread.start()
243
+
244
+ result = run_single_pipeline(1, pairs[0])
245
+
246
+ stop_monitor = True
247
+ monitor_thread.join()
248
+
249
+ if result["status"] == "success":
250
+ print(f"\n โœ… ๅ•ไปปๅŠกๅฎŒๆˆ:")
251
+ print(f" ๅŠ ่ฝฝๆ—ถ้—ด: {result['load_time']:.2f}s")
252
+ print(f" ้…ๅ‡†ๆ—ถ้—ด: {result['reg_time']:.2f}s")
253
+ print(f" ๆฃ€ๆต‹ๆ—ถ้—ด: {result['detect_time']:.2f}s")
254
+ print(f" ๆ€ปๆ—ถ้—ด: {result['total_time']:.2f}s")
255
+
256
+ print(f"\n ๐Ÿ“ˆ ๅ•ไปปๅŠก่ต„ๆบๅณฐๅ€ผ:")
257
+ print(f" CPU ๅณฐๅ€ผ: {max(monitor_data['cpu_percent']):.1f}%")
258
+ print(f" ๅ†…ๅญ˜ๅณฐๅ€ผ: {max(monitor_data['memory_gb']):.1f} GB ({max(monitor_data['memory_percent']):.1f}%)")
259
+ print(f" GPUๆ˜พๅญ˜ๅณฐๅ€ผ: {max(monitor_data['gpu_memory_gb']):.2f} GB")
260
+
261
+ single_task_time = result["total_time"]
262
+ single_cpu_peak = max(monitor_data['cpu_percent'])
263
+ single_mem_peak = max(monitor_data['memory_gb'])
264
+
265
+ # ========================================
266
+ # ๆต‹่ฏ• 2: 2 ไปปๅŠกๅนถๅ‘
267
+ # ========================================
268
+ print("\n" + "=" * 70)
269
+ print("๐Ÿ“Œ ๆต‹่ฏ• 2: 2 ไปปๅŠกๅนถๅ‘ๅŽ‹ๅŠ›ๆต‹่ฏ•")
270
+ print("=" * 70)
271
+
272
+ monitor_data = {k: [] for k in monitor_data}
273
+ stop_monitor = False
274
+
275
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
276
+ monitor_thread.start()
277
+
278
+ start_time = time.time()
279
+ results = []
280
+
281
+ with ThreadPoolExecutor(max_workers=2) as executor:
282
+ futures = []
283
+ for i, pair in enumerate(pairs[:2]):
284
+ futures.append(executor.submit(run_single_pipeline, i+1, pair))
285
+
286
+ for future in as_completed(futures):
287
+ results.append(future.result())
288
+
289
+ concurrent_2_time = time.time() - start_time
290
+
291
+ stop_monitor = True
292
+ monitor_thread.join()
293
+
294
+ success_count = sum(1 for r in results if r["status"] == "success")
295
+ print(f"\n โœ… 2ไปปๅŠกๅนถๅ‘ๅฎŒๆˆ: {success_count}/2 ๆˆๅŠŸ")
296
+ print(f" ๆ€ป่€—ๆ—ถ: {concurrent_2_time:.2f}s")
297
+ print(f" ๅนถ่กŒๆ•ˆ็އ: {(single_task_time * 2 / concurrent_2_time * 100):.1f}%")
298
+
299
+ print(f"\n ๐Ÿ“ˆ 2ไปปๅŠกๅนถๅ‘่ต„ๆบๅณฐๅ€ผ:")
300
+ print(f" CPU ๅณฐๅ€ผ: {max(monitor_data['cpu_percent']):.1f}%")
301
+ print(f" ๅ†…ๅญ˜ๅณฐๅ€ผ: {max(monitor_data['memory_gb']):.1f} GB ({max(monitor_data['memory_percent']):.1f}%)")
302
+ print(f" GPUๆ˜พๅญ˜ๅณฐๅ€ผ: {max(monitor_data['gpu_memory_gb']):.2f} GB")
303
+
304
+ concurrent_2_cpu = max(monitor_data['cpu_percent'])
305
+ concurrent_2_mem = max(monitor_data['memory_gb'])
306
+
307
+ # ========================================
308
+ # ๆต‹่ฏ• 3: 3 ไปปๅŠกๅนถ๏ฟฝ๏ฟฝ
309
+ # ========================================
310
+ print("\n" + "=" * 70)
311
+ print("๐Ÿ“Œ ๆต‹่ฏ• 3: 3 ไปปๅŠกๅนถๅ‘ๅŽ‹ๅŠ›ๆต‹่ฏ•")
312
+ print("=" * 70)
313
+
314
+ if len(pairs) < 3:
315
+ print(" โš ๏ธ ๆ•ฐๆฎไธ่ถณ๏ผŒ่ทณ่ฟ‡ 3 ไปปๅŠกๆต‹่ฏ•")
316
+ else:
317
+ monitor_data = {k: [] for k in monitor_data}
318
+ stop_monitor = False
319
+
320
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
321
+ monitor_thread.start()
322
+
323
+ start_time = time.time()
324
+ results = []
325
+
326
+ with ThreadPoolExecutor(max_workers=3) as executor:
327
+ futures = []
328
+ for i, pair in enumerate(pairs[:3]):
329
+ futures.append(executor.submit(run_single_pipeline, i+1, pair))
330
+
331
+ for future in as_completed(futures):
332
+ results.append(future.result())
333
+
334
+ concurrent_3_time = time.time() - start_time
335
+
336
+ stop_monitor = True
337
+ monitor_thread.join()
338
+
339
+ success_count = sum(1 for r in results if r["status"] == "success")
340
+ print(f"\n โœ… 3ไปปๅŠกๅนถๅ‘ๅฎŒๆˆ: {success_count}/3 ๆˆๅŠŸ")
341
+ print(f" ๆ€ป่€—ๆ—ถ: {concurrent_3_time:.2f}s")
342
+ print(f" ๅนถ่กŒๆ•ˆ็އ: {(single_task_time * 3 / concurrent_3_time * 100):.1f}%")
343
+
344
+ print(f"\n ๐Ÿ“ˆ 3ไปปๅŠกๅนถๅ‘่ต„ๆบๅณฐๅ€ผ:")
345
+ print(f" CPU ๅณฐๅ€ผ: {max(monitor_data['cpu_percent']):.1f}%")
346
+ print(f" ๅ†…ๅญ˜ๅณฐๅ€ผ: {max(monitor_data['memory_gb']):.1f} GB ({max(monitor_data['memory_percent']):.1f}%)")
347
+ print(f" GPUๆ˜พๅญ˜ๅณฐๅ€ผ: {max(monitor_data['gpu_memory_gb']):.2f} GB")
348
+
349
+ concurrent_3_cpu = max(monitor_data['cpu_percent'])
350
+ concurrent_3_mem = max(monitor_data['memory_gb'])
351
+
352
+ # ========================================
353
+ # ๆต‹่ฏ• 4: GPU ๅˆ†ๅ‰ฒไปปๅŠก (ๅฏ้€‰)
354
+ # ========================================
355
+ print("\n" + "=" * 70)
356
+ print("๐Ÿ“Œ ๆต‹่ฏ• 4: GPU ๅˆ†ๅ‰ฒไปปๅŠกๅณฐๅ€ผๆต‹่ฏ•")
357
+ print("=" * 70)
358
+
359
+ try:
360
+ import torch
361
+ if torch.cuda.is_available():
362
+ torch.cuda.reset_peak_memory_stats()
363
+
364
+ monitor_data = {k: [] for k in monitor_data}
365
+ stop_monitor = False
366
+
367
+ monitor_thread = threading.Thread(target=resource_monitor, args=(0.2,))
368
+ monitor_thread.start()
369
+
370
+ # ่ฟ่กŒๅˆ†ๅ‰ฒ
371
+ seg_result = run_segmentation_task(1, pairs[0][0])
372
+
373
+ stop_monitor = True
374
+ monitor_thread.join()
375
+
376
+ if seg_result["status"] == "success":
377
+ print(f"\n โœ… ๅˆ†ๅ‰ฒไปปๅŠกๅฎŒๆˆ:")
378
+ print(f" ่€—ๆ—ถ: {seg_result['total_time']:.2f}s")
379
+ print(f" GPUๅณฐๅ€ผ: {seg_result.get('gpu_peak_gb', max(monitor_data['gpu_memory_gb'])):.2f} GB")
380
+ else:
381
+ print(f"\n โš ๏ธ ๅˆ†ๅ‰ฒไปปๅŠก่ทณ่ฟ‡: {seg_result.get('error', 'unknown')}")
382
+
383
+ print(f"\n ๐Ÿ“ˆ ๅˆ†ๅ‰ฒไปปๅŠก่ต„ๆบๅณฐๅ€ผ:")
384
+ print(f" CPU ๅณฐๅ€ผ: {max(monitor_data['cpu_percent']):.1f}%")
385
+ print(f" ๅ†…ๅญ˜ๅณฐๅ€ผ: {max(monitor_data['memory_gb']):.1f} GB")
386
+ print(f" GPUๆ˜พๅญ˜ๅณฐๅ€ผ: {max(monitor_data['gpu_memory_gb']):.2f} GB")
387
+
388
+ gpu_seg_peak = max(monitor_data['gpu_memory_gb'])
389
+ else:
390
+ print(" โš ๏ธ GPU ไธๅฏ็”จ๏ผŒ่ทณ่ฟ‡ๅˆ†ๅ‰ฒๆต‹่ฏ•")
391
+ gpu_seg_peak = 0
392
+ except Exception as e:
393
+ print(f" โš ๏ธ ๅˆ†ๅ‰ฒๆต‹่ฏ•ๅคฑ่ดฅ: {e}")
394
+ gpu_seg_peak = 0
395
+
396
+ # ========================================
397
+ # ๆœ€็ปˆๆŠฅๅ‘Š
398
+ # ========================================
399
+ print("\n" + "=" * 70)
400
+ print("๐Ÿ“‹ ๅŽ‹ๅŠ›ๆต‹่ฏ•ๆ€ป็ป“ๆŠฅๅ‘Š")
401
+ print("=" * 70)
402
+
403
+ print(f"""
404
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
405
+ โ”‚ NeuroScan AI ่ต„ๆบ้œ€ๆฑ‚ๆŠฅๅ‘Š โ”‚
406
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
407
+ โ”‚ ๆต‹่ฏ•ๅœบๆ™ฏ โ”‚ CPU ๅณฐๅ€ผ โ”‚ ๅ†…ๅญ˜ๅณฐๅ€ผ โ”‚ GPU ๆ˜พๅญ˜ๅณฐๅ€ผ โ”‚
408
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
409
+ โ”‚ ๅ•ไปปๅŠก้…ๅ‡† โ”‚ {single_cpu_peak:>6.1f}% โ”‚ {single_mem_peak:>6.1f} GB โ”‚ ~0 GB (CPU) โ”‚
410
+ โ”‚ 2ไปปๅŠกๅนถๅ‘ โ”‚ {concurrent_2_cpu:>6.1f}% โ”‚ {concurrent_2_mem:>6.1f} GB โ”‚ ~0 GB (CPU) โ”‚
411
+ โ”‚ 3ไปปๅŠกๅนถๅ‘ โ”‚ {concurrent_3_cpu if 'concurrent_3_cpu' in dir() else 0:>6.1f}% โ”‚ {concurrent_3_mem if 'concurrent_3_mem' in dir() else 0:>6.1f} GB โ”‚ ~0 GB (CPU) โ”‚
412
+ โ”‚ GPUๅˆ†ๅ‰ฒไปปๅŠก โ”‚ ~50% โ”‚ ~8 GB โ”‚ {gpu_seg_peak:>6.1f} GB โ”‚
413
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
414
+ โ”‚ ๆŽจ่็กฌไปถ้…็ฝฎ โ”‚
415
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
416
+ โ”‚ ๆœ€ไฝŽ้…็ฝฎ (ๅ•ไปปๅŠก): 4ๆ ธ CPU, 8GB ๅ†…ๅญ˜, ๆ— ้œ€GPU โ”‚
417
+ โ”‚ ๆ ‡ๅ‡†้…็ฝฎ (2ๅนถๅ‘): 8ๆ ธ CPU, 16GB ๅ†…ๅญ˜, 12GB GPU (ๅฏ้€‰) โ”‚
418
+ โ”‚ ๆŽจ่้…็ฝฎ (3ๅนถๅ‘): 16ๆ ธ CPU, 32GB ๅ†…ๅญ˜, 24GB GPU โ”‚
419
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
420
+ """)
421
+
422
+ print("โœ… ๅŽ‹ๅŠ›ๆต‹่ฏ•ๅฎŒๆˆ!")
423
+
424
+
425
+ if __name__ == "__main__":
426
+ main()
427
+
428
+