cmpl 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmpl-0.1.0/LICENSE +21 -0
- cmpl-0.1.0/PKG-INFO +55 -0
- cmpl-0.1.0/README.md +20 -0
- cmpl-0.1.0/pyproject.toml +16 -0
- cmpl-0.1.0/setup.cfg +4 -0
- cmpl-0.1.0/src/cmpl/__init__.py +20 -0
- cmpl-0.1.0/src/cmpl/_version.py +1 -0
- cmpl-0.1.0/src/cmpl/quantitative_MRI/__init__.py +8 -0
- cmpl-0.1.0/src/cmpl/quantitative_MRI/mapping.py +474 -0
- cmpl-0.1.0/src/cmpl/reconstruction/__init__.py +6 -0
- cmpl-0.1.0/src/cmpl/reconstruction/grappa/__init__.py +8 -0
- cmpl-0.1.0/src/cmpl/reconstruction/grappa/grappa_1D.py +175 -0
- cmpl-0.1.0/src/cmpl/reconstruction/grappa/grappa_2D.py +228 -0
- cmpl-0.1.0/src/cmpl/reconstruction/grappa/utils.py +31 -0
- cmpl-0.1.0/src/cmpl/reconstruction/sense/__init__.py +3 -0
- cmpl-0.1.0/src/cmpl/reconstruction/sense/cg.py +48 -0
- cmpl-0.1.0/src/cmpl/segmentation/MRISegmentationTool.py +167 -0
- cmpl-0.1.0/src/cmpl/segmentation/__init__.py +6 -0
- cmpl-0.1.0/src/cmpl/segmentation/tools.py +104 -0
- cmpl-0.1.0/src/cmpl/utilities/__init__.py +10 -0
- cmpl-0.1.0/src/cmpl/utilities/df_build.py +78 -0
- cmpl-0.1.0/src/cmpl/utilities/io.py +390 -0
- cmpl-0.1.0/src/cmpl/utilities/utils.py +375 -0
- cmpl-0.1.0/src/cmpl/visualization/__init__.py +6 -0
- cmpl-0.1.0/src/cmpl/visualization/visualization.py +236 -0
- cmpl-0.1.0/src/cmpl.egg-info/PKG-INFO +55 -0
- cmpl-0.1.0/src/cmpl.egg-info/SOURCES.txt +27 -0
- cmpl-0.1.0/src/cmpl.egg-info/dependency_links.txt +1 -0
- cmpl-0.1.0/src/cmpl.egg-info/top_level.txt +1 -0
cmpl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2025 Eisa Hedayati
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the “Software”), to use,
|
|
5
|
+
copy, modify, merge, publish, distribute, and sublicense copies of the Software,
|
|
6
|
+
subject to the following conditions:
|
|
7
|
+
|
|
8
|
+
1. The Software may **not** be used, in whole or in part, for any purpose that is
|
|
9
|
+
primarily intended for or directed toward commercial advantage or monetary
|
|
10
|
+
compensation.
|
|
11
|
+
|
|
12
|
+
2. The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
cmpl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmpl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CMRR MRI Processing Libraries
|
|
5
|
+
Author: Eisa Hedayati
|
|
6
|
+
License: Copyright (c) 2025 Eisa Hedayati
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the “Software”), to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, and sublicense copies of the Software,
|
|
11
|
+
subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
1. The Software may **not** be used, in whole or in part, for any purpose that is
|
|
14
|
+
primarily intended for or directed toward commercial advantage or monetary
|
|
15
|
+
compensation.
|
|
16
|
+
|
|
17
|
+
2. The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
22
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
23
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
|
|
24
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
25
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Classifier: Programming Language :: Python :: 3
|
|
29
|
+
Classifier: Operating System :: OS Independent
|
|
30
|
+
Classifier: License :: Other/Proprietary License
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# CMPL - CMRR MRI Processing Libraries
|
|
37
|
+
|
|
38
|
+
## Introduction
|
|
39
|
+
Welcome to CMPL (CMRR MRI Processing Libraries), a specialized Python toolkit tailored for MRI data conversion, visualization, and filtering. CMPL is designed to assist researchers and practitioners in the field of MRI by offering efficient and user-friendly tools for specific MRI image processing tasks.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
- **K-space Reconstruction:**
|
|
43
|
+
- **Under sampling treatments**:
|
|
44
|
+
- 1D Grappa
|
|
45
|
+
- 2D Grappa
|
|
46
|
+
-
|
|
47
|
+
- **Data Conversion:** Robust tools to convert MRI data into various formats for compatibility and ease of use. (partly implemented)
|
|
48
|
+
- **3D Visualization:** Advanced capabilities for generating and manipulating 3D visualizations from MRI datasets.(tentative)
|
|
49
|
+
- **Filtering Functions:** A suite of filters for enhancing image quality, including noise reduction and edge enhancement.(tentative)
|
|
50
|
+
|
|
51
|
+
## Installation(future)
|
|
52
|
+
Install CMPL using pip:
|
|
53
|
+
```bash
|
|
54
|
+
pip install cmpl
|
|
55
|
+
```
|
cmpl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# CMPL - CMRR MRI Processing Libraries
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
Welcome to CMPL (CMRR MRI Processing Libraries), a specialized Python toolkit tailored for MRI data conversion, visualization, and filtering. CMPL is designed to assist researchers and practitioners in the field of MRI by offering efficient and user-friendly tools for specific MRI image processing tasks.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
- **K-space Reconstruction:**
|
|
8
|
+
- **Under sampling treatments**:
|
|
9
|
+
- 1D Grappa
|
|
10
|
+
- 2D Grappa
|
|
11
|
+
-
|
|
12
|
+
- **Data Conversion:** Robust tools to convert MRI data into various formats for compatibility and ease of use. (partly implemented)
|
|
13
|
+
- **3D Visualization:** Advanced capabilities for generating and manipulating 3D visualizations from MRI datasets.(tentative)
|
|
14
|
+
- **Filtering Functions:** A suite of filters for enhancing image quality, including noise reduction and edge enhancement.(tentative)
|
|
15
|
+
|
|
16
|
+
## Installation(future)
|
|
17
|
+
Install CMPL using pip:
|
|
18
|
+
```bash
|
|
19
|
+
pip install cmpl
|
|
20
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cmpl"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [{ name = "Eisa Hedayati" }]
|
|
5
|
+
description = "CMRR MRI Processing Libraries"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Programming Language :: Python :: 3",
|
|
10
|
+
"Operating System :: OS Independent",
|
|
11
|
+
"License :: Other/Proprietary License",
|
|
12
|
+
]
|
|
13
|
+
license = { file = "LICENSE" }
|
|
14
|
+
|
|
15
|
+
[tool.setuptools]
|
|
16
|
+
license-files = ["LICENSE"]
|
cmpl-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# File created by: Eisa Hedayati
|
|
2
|
+
# Date: 12/29/2023
|
|
3
|
+
# Description: This file is developed at CMRR
|
|
4
|
+
|
|
5
|
+
__author__ = 'Eisa Hedayati'
|
|
6
|
+
|
|
7
|
+
from . import utilities
|
|
8
|
+
from . import utilities as utils
|
|
9
|
+
from .utilities import io
|
|
10
|
+
from . import visualization
|
|
11
|
+
from . import visualization as vis
|
|
12
|
+
from . import segmentation
|
|
13
|
+
from . import segmentation as seg
|
|
14
|
+
from . import quantitative_MRI
|
|
15
|
+
from . import quantitative_MRI as qmr
|
|
16
|
+
from . import reconstruction
|
|
17
|
+
from . import reconstruction as recon
|
|
18
|
+
|
|
19
|
+
from ._version import __version__
|
|
20
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.6.0'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# File created by: Eisa Hedayati
|
|
2
|
+
# Date: 8/30/2024
|
|
3
|
+
# Description: This file is developed at CMRR
|
|
4
|
+
|
|
5
|
+
from . import mapping
|
|
6
|
+
from .mapping import (t2_star_two_parametric_2D, t2_star_two_parametric_3D, t2_star_three_parametric_2D,
|
|
7
|
+
t2_star_three_parametric_3D, calculate_rmse_percentage_s0,
|
|
8
|
+
reconstruct_images)
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import torch
|
|
4
|
+
import math
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
torch.set_grad_enabled(True)
|
|
7
|
+
|
|
8
|
+
def t2_star_two_parametric_2D(TE_all, images, num_iterations=10000, initial_lr=0.01, lr_decay_factor=0.1, patience=100, initial_T2_star=20.0):
|
|
9
|
+
"""
|
|
10
|
+
Computes the T2* and S0 maps from MRI images using an exponential decay __private_model.
|
|
11
|
+
Also tracks and plots the loss during optimization, with learning rate adjustment.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
- TE_all: A list or numpy array of echo times (TE) in milliseconds.
|
|
15
|
+
- images: A numpy array of shape (x, y, TE) containing the MRI images.
|
|
16
|
+
- num_iterations: Number of iterations for the optimizer (default: 10000).
|
|
17
|
+
- initial_lr: Initial learning rate for the optimizer (default: 0.01).
|
|
18
|
+
- lr_decay_factor: Factor by which the learning rate will be reduced (default: 0.1).
|
|
19
|
+
- patience: Number of iterations to wait before reducing the learning rate (default: 100).
|
|
20
|
+
- initial_T2_star: Initial guess for T2* for all voxels (default: 20.0).
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
- T2_star_map: A numpy array containing the T2* values for each voxel.
|
|
24
|
+
- S0_map: A numpy array containing the S0 values for each voxel.
|
|
25
|
+
"""
|
|
26
|
+
torch.set_grad_enabled(True)
|
|
27
|
+
# Convert echo times to a torch tensor and move to GPU
|
|
28
|
+
TE = torch.tensor(TE_all, dtype=torch.float32).cuda()
|
|
29
|
+
|
|
30
|
+
# Convert images to torch tensor and move to GPU
|
|
31
|
+
images = torch.tensor(images, dtype=torch.float32).cuda()
|
|
32
|
+
|
|
33
|
+
# Define the exponential decay function
|
|
34
|
+
def exp_decay(TE, S0, T2_star):
|
|
35
|
+
return S0[..., None] * torch.exp(-TE[None, None, :] / T2_star[..., None])
|
|
36
|
+
|
|
37
|
+
# Prepare initial guesses for S0 and T2* for all voxels
|
|
38
|
+
S0_init = images[..., 0]
|
|
39
|
+
T2_star_init = torch.full(S0_init.shape, initial_T2_star, dtype=torch.float32).cuda()
|
|
40
|
+
|
|
41
|
+
# Parameters to be optimized: S0 and T2* for all voxels
|
|
42
|
+
params = torch.stack([S0_init, T2_star_init], dim=-1)
|
|
43
|
+
params = params.view(-1, 2)
|
|
44
|
+
params.requires_grad = True
|
|
45
|
+
|
|
46
|
+
# Optimizer
|
|
47
|
+
optimizer = torch.optim.Adam([params], lr=initial_lr)
|
|
48
|
+
|
|
49
|
+
# Learning rate scheduler that reduces LR when loss stops improving
|
|
50
|
+
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=lr_decay_factor, patience=patience, verbose=True)
|
|
51
|
+
|
|
52
|
+
# Loss function
|
|
53
|
+
def loss_function(params, TE, signal):
|
|
54
|
+
S0, T2_star = params[:, 0], params[:, 1]
|
|
55
|
+
predicted_signal = exp_decay(TE, S0, T2_star)
|
|
56
|
+
return torch.mean((signal - predicted_signal) ** 2)
|
|
57
|
+
|
|
58
|
+
# Flatten the images to match the flattened params
|
|
59
|
+
signal = images.view(-1, images.shape[-1])
|
|
60
|
+
|
|
61
|
+
# List to store loss values for each iteration
|
|
62
|
+
loss_values = []
|
|
63
|
+
|
|
64
|
+
# Optimization loop
|
|
65
|
+
for _ in tqdm(range(num_iterations)): # Adjust the number of iterations as needed
|
|
66
|
+
optimizer.zero_grad()
|
|
67
|
+
loss = loss_function(params, TE, signal)
|
|
68
|
+
loss.backward()
|
|
69
|
+
optimizer.step()
|
|
70
|
+
# Store the current loss value
|
|
71
|
+
loss_values.append(loss.item())
|
|
72
|
+
# Step the scheduler with the current loss
|
|
73
|
+
scheduler.step(loss)
|
|
74
|
+
|
|
75
|
+
# Reshape the parameters back to the original image shape
|
|
76
|
+
S0_map, T2_star_map = params[:, 0].view(images.shape[:-1]), params[:, 1].view(images.shape[:-1])
|
|
77
|
+
|
|
78
|
+
# Convert the results back to CPU and numpy arrays for returning
|
|
79
|
+
T2_star_map = T2_star_map.detach().cpu().numpy()
|
|
80
|
+
S0_map = S0_map.detach().cpu().numpy()
|
|
81
|
+
|
|
82
|
+
# Plot the loss values over iterations
|
|
83
|
+
plt.figure(figsize=(10, 6))
|
|
84
|
+
plt.plot(loss_values, label='Loss')
|
|
85
|
+
plt.xlabel('Iteration')
|
|
86
|
+
plt.ylabel('Loss')
|
|
87
|
+
plt.title('Loss During Optimization with Learning Rate Adjustment')
|
|
88
|
+
plt.grid(True)
|
|
89
|
+
plt.legend()
|
|
90
|
+
plt.show()
|
|
91
|
+
print(loss_values[-1])
|
|
92
|
+
return T2_star_map, S0_map
|
|
93
|
+
|
|
94
|
+
def t2_star_three_parametric_2D(TE_all, images, num_iterations=10000, initial_lr=0.01, lr_decay_factor=0.1,
|
|
95
|
+
patience=100, initial_T2_star=20.0, initial_C=0.0):
|
|
96
|
+
"""
|
|
97
|
+
Computes the T2*, S0, and C (noise) maps from MRI images using an exponential decay __private_model.
|
|
98
|
+
Also tracks and plots the loss during optimization, with learning rate adjustment.
|
|
99
|
+
|
|
100
|
+
Parameters:
|
|
101
|
+
- TE_all: A list or numpy array of echo times (TE) in milliseconds.
|
|
102
|
+
- images: A numpy array of shape (x, y, TE) containing the MRI images.
|
|
103
|
+
- num_iterations: Number of iterations for the optimizer (default: 10000).
|
|
104
|
+
- initial_lr: Initial learning rate for the optimizer (default: 0.01).
|
|
105
|
+
- lr_decay_factor: Factor by which the learning rate will be reduced (default: 0.1).
|
|
106
|
+
- patience: Number of iterations to wait before reducing the learning rate (default: 100).
|
|
107
|
+
- initial_T2_star: Initial guess for T2* for all voxels (default: 20.0).
|
|
108
|
+
- initial_C: Initial guess for the noise parameter C for all voxels (default: 0.0).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
- T2_star_map: A numpy array containing the T2* values for each voxel.
|
|
112
|
+
- S0_map: A numpy array containing the S0 values for each voxel.
|
|
113
|
+
- C_map: A numpy array containing the C (noise) values for each voxel.
|
|
114
|
+
"""
|
|
115
|
+
torch.set_grad_enabled(True)
|
|
116
|
+
# Convert echo times to a torch tensor and move to GPU
|
|
117
|
+
TE = torch.tensor(TE_all, dtype=torch.float32).cuda()
|
|
118
|
+
|
|
119
|
+
# Convert images to torch tensor and move to GPU
|
|
120
|
+
images = torch.tensor(images, dtype=torch.float32).cuda()
|
|
121
|
+
|
|
122
|
+
def exp_decay(TE, S0, T2_star, C_prime):
|
|
123
|
+
# Reparameterize C as the square of C_prime to ensure non-negativity
|
|
124
|
+
C = torch.abs(C_prime)
|
|
125
|
+
return S0[..., None] * torch.exp(-TE[None, None, :] / (T2_star[..., None] + 1e-6)) + C[..., None]
|
|
126
|
+
|
|
127
|
+
# Prepare initial guesses for S0, T2*, and C_prime for all voxels
|
|
128
|
+
S0_init = images[..., 0]
|
|
129
|
+
T2_star_init = torch.full(S0_init.shape, initial_T2_star, dtype=torch.float32).cuda()
|
|
130
|
+
C_prime_init = torch.sqrt(torch.full(S0_init.shape, initial_C, dtype=torch.float32) + 1.0).cuda()
|
|
131
|
+
|
|
132
|
+
# Parameters to be optimized: S0, T2*, and C_prime for all voxels
|
|
133
|
+
params = torch.stack([S0_init, T2_star_init, C_prime_init], dim=-1)
|
|
134
|
+
params = params.view(-1, 3)
|
|
135
|
+
params.requires_grad = True
|
|
136
|
+
|
|
137
|
+
# Optimizer
|
|
138
|
+
optimizer = torch.optim.Adam([params], lr=initial_lr)
|
|
139
|
+
|
|
140
|
+
# Learning rate scheduler that reduces LR when loss stops improving
|
|
141
|
+
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=lr_decay_factor, patience=patience,
|
|
142
|
+
verbose=True)
|
|
143
|
+
|
|
144
|
+
def loss_function(params, TE, signal):
|
|
145
|
+
S0, T2_star, C = params[:, 0], params[:, 1], params[:, 2]
|
|
146
|
+
predicted_signal = exp_decay(TE, S0, T2_star, C)
|
|
147
|
+
return torch.mean((signal - predicted_signal) ** 2)
|
|
148
|
+
|
|
149
|
+
# Flatten the images to match the flattened params
|
|
150
|
+
|
|
151
|
+
signal = images.view(-1, images.shape[-1])
|
|
152
|
+
|
|
153
|
+
# List to store loss values for each iteration
|
|
154
|
+
loss_values = []
|
|
155
|
+
S0_values = []
|
|
156
|
+
T2_star_values = []
|
|
157
|
+
C_values = []
|
|
158
|
+
|
|
159
|
+
# Optimization loop
|
|
160
|
+
for _ in tqdm(range(num_iterations)): # Adjust the number of iterations as needed
|
|
161
|
+
optimizer.zero_grad()
|
|
162
|
+
loss = loss_function(params, TE, signal)
|
|
163
|
+
# loss = loss_function(params, TE, signal, optimizer)
|
|
164
|
+
loss.backward()
|
|
165
|
+
torch.nn.utils.clip_grad_norm_(params, max_norm=1.0)
|
|
166
|
+
optimizer.step()
|
|
167
|
+
# Store the current loss value
|
|
168
|
+
loss_values.append(loss.item())
|
|
169
|
+
|
|
170
|
+
S0, T2_star, C = params[:, 0].clone().detach().cpu().numpy(), params[:,
|
|
171
|
+
1].clone().detach().cpu().numpy(), params[:,
|
|
172
|
+
2].clone().detach().cpu().numpy()
|
|
173
|
+
S0_values.append(S0.mean())
|
|
174
|
+
T2_star_values.append(T2_star.mean())
|
|
175
|
+
C_values.append(C.mean())
|
|
176
|
+
# Step the scheduler with the current loss
|
|
177
|
+
scheduler.step(loss)
|
|
178
|
+
|
|
179
|
+
# Reshape the parameters back to the original image shape
|
|
180
|
+
S0_map, T2_star_map, C_map = params[:, 0].view(images.shape[:-1]), params[:, 1].view(images.shape[:-1]), params[:,
|
|
181
|
+
2].view(
|
|
182
|
+
images.shape[:-1])
|
|
183
|
+
|
|
184
|
+
# Convert the results back to CPU and numpy arrays for returning
|
|
185
|
+
T2_star_map = T2_star_map.detach().cpu().numpy()
|
|
186
|
+
S0_map = S0_map.detach().cpu().numpy()
|
|
187
|
+
C_map = C_map.detach().cpu().numpy()
|
|
188
|
+
|
|
189
|
+
# Plot the loss values over iterations
|
|
190
|
+
plt.figure(figsize=(10, 6))
|
|
191
|
+
plt.plot(loss_values, label='Loss')
|
|
192
|
+
plt.xlabel('Iteration')
|
|
193
|
+
plt.ylabel('Loss')
|
|
194
|
+
plt.title('Loss During Optimization with Learning Rate Adjustment')
|
|
195
|
+
plt.grid(True)
|
|
196
|
+
plt.legend()
|
|
197
|
+
plt.show()
|
|
198
|
+
plt.figure(figsize=(14, 7))
|
|
199
|
+
|
|
200
|
+
plt.subplot(3, 1, 1)
|
|
201
|
+
plt.plot(S0_values, label='S0')
|
|
202
|
+
plt.xlabel('Iteration')
|
|
203
|
+
plt.ylabel('Mean S0 Value')
|
|
204
|
+
plt.title('Mean S0 Value During Training')
|
|
205
|
+
plt.grid(True)
|
|
206
|
+
plt.legend()
|
|
207
|
+
|
|
208
|
+
plt.subplot(3, 1, 2)
|
|
209
|
+
plt.plot(T2_star_values, label='T2*')
|
|
210
|
+
plt.xlabel('Iteration')
|
|
211
|
+
plt.ylabel('Mean T2* Value')
|
|
212
|
+
plt.title('Mean T2* Value During Training')
|
|
213
|
+
plt.grid(True)
|
|
214
|
+
plt.legend()
|
|
215
|
+
|
|
216
|
+
plt.subplot(3, 1, 3)
|
|
217
|
+
plt.plot(C_values, label='C')
|
|
218
|
+
plt.xlabel('Iteration')
|
|
219
|
+
plt.ylabel('Mean C Value')
|
|
220
|
+
plt.title('Mean C Value During Training')
|
|
221
|
+
plt.grid(True)
|
|
222
|
+
plt.legend()
|
|
223
|
+
|
|
224
|
+
plt.tight_layout()
|
|
225
|
+
plt.show()
|
|
226
|
+
_print_last_non_nan(loss_values)
|
|
227
|
+
|
|
228
|
+
return T2_star_map, S0_map, C_map, loss_values
|
|
229
|
+
|
|
230
|
+
def t2_star_two_parametric_3D(TE_all, images, num_iterations=10000, initial_lr=0.01,
|
|
231
|
+
lr_decay_factor=0.1, patience=100, initial_T2_star=20.0, plot_error=True):
|
|
232
|
+
"""
|
|
233
|
+
Computes the T2* and S0 maps from MRI images using an exponential decay __private_model.
|
|
234
|
+
Also tracks and plots the loss during optimization, with learning rate adjustment.
|
|
235
|
+
|
|
236
|
+
Parameters:
|
|
237
|
+
- TE_all: A list or numpy array of echo times (TE) in milliseconds.
|
|
238
|
+
- images: A numpy array of shape (x, y, z, TE) containing the MRI images.
|
|
239
|
+
- num_iterations: Number of iterations for the optimizer (default: 10000).
|
|
240
|
+
- initial_lr: Initial learning rate for the optimizer (default: 0.01).
|
|
241
|
+
- lr_decay_factor: Factor by which the learning rate will be reduced (default: 0.1).
|
|
242
|
+
- patience: Number of iterations to wait before reducing the learning rate (default: 100).
|
|
243
|
+
- initial_T2_star: Initial guess for T2* for all voxels (default: 20.0).
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
- T2_star_map: A numpy array containing the T2* values for each voxel (x, y, z).
|
|
247
|
+
- S0_map: A numpy array containing the S0 values for each voxel (x, y, z).
|
|
248
|
+
"""
|
|
249
|
+
torch.set_grad_enabled(True)
|
|
250
|
+
# Convert echo times to a torch tensor and move to GPU
|
|
251
|
+
TE = torch.tensor(TE_all, dtype=torch.float32).cuda()
|
|
252
|
+
|
|
253
|
+
# Convert images to torch tensor and move to GPU
|
|
254
|
+
images = torch.tensor(images, dtype=torch.float32).cuda()
|
|
255
|
+
|
|
256
|
+
# Define the exponential decay function
|
|
257
|
+
def exp_decay(TE, S0, T2_star):
|
|
258
|
+
return S0[..., None] * torch.exp(-TE[None, None, None, :] / T2_star[..., None])
|
|
259
|
+
|
|
260
|
+
# Prepare initial guesses for S0 and T2* for all voxels
|
|
261
|
+
S0_init = images[..., 0]
|
|
262
|
+
T2_star_init = torch.full(S0_init.shape, initial_T2_star, dtype=torch.float32).cuda()
|
|
263
|
+
|
|
264
|
+
# Parameters to be optimized: S0 and T2* for all voxels
|
|
265
|
+
params = torch.stack([S0_init, T2_star_init], dim=-1)
|
|
266
|
+
params = params.reshape(-1, 2)
|
|
267
|
+
params.requires_grad = True
|
|
268
|
+
|
|
269
|
+
# Optimizer
|
|
270
|
+
optimizer = torch.optim.Adam([params], lr=initial_lr)
|
|
271
|
+
|
|
272
|
+
# Learning rate scheduler that reduces LR when loss stops improving
|
|
273
|
+
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=lr_decay_factor, patience=patience, verbose=True)
|
|
274
|
+
|
|
275
|
+
# Loss function
|
|
276
|
+
def loss_function(params, TE, signal):
|
|
277
|
+
S0, T2_star = params[:, 0], params[:, 1]
|
|
278
|
+
predicted_signal = exp_decay(TE, S0, T2_star)
|
|
279
|
+
return torch.mean((signal - predicted_signal) ** 2)
|
|
280
|
+
|
|
281
|
+
# Flatten the images to match the flattened params
|
|
282
|
+
signal = images.reshape(-1, images.shape[-1])
|
|
283
|
+
|
|
284
|
+
# List to store loss values for each iteration
|
|
285
|
+
loss_values = []
|
|
286
|
+
|
|
287
|
+
# Optimization loop
|
|
288
|
+
for _ in tqdm(range(num_iterations)): # Adjust the number of iterations as needed
|
|
289
|
+
optimizer.zero_grad()
|
|
290
|
+
loss = loss_function(params, TE, signal)
|
|
291
|
+
loss.backward()
|
|
292
|
+
optimizer.step()
|
|
293
|
+
# Store the current loss value
|
|
294
|
+
loss_values.append(loss.item())
|
|
295
|
+
# Step the scheduler with the current loss
|
|
296
|
+
scheduler.step(loss)
|
|
297
|
+
|
|
298
|
+
# Reshape the parameters back to the original image shape
|
|
299
|
+
S0_map, T2_star_map = params[:, 0].reshape(images.shape[:-1]), params[:, 1].reshape(images.shape[:-1])
|
|
300
|
+
|
|
301
|
+
# Convert the results back to CPU and numpy arrays for returning
|
|
302
|
+
T2_star_map = T2_star_map.detach().cpu().numpy()
|
|
303
|
+
S0_map = S0_map.detach().cpu().numpy()
|
|
304
|
+
|
|
305
|
+
# Plot the loss values over iterations
|
|
306
|
+
if plot_error:
|
|
307
|
+
plt.figure(figsize=(10, 6))
|
|
308
|
+
plt.plot(loss_values, label='Loss')
|
|
309
|
+
plt.xlabel('Iteration')
|
|
310
|
+
plt.ylabel('Loss')
|
|
311
|
+
plt.title('Loss During Optimization with Learning Rate Adjustment')
|
|
312
|
+
plt.grid(True)
|
|
313
|
+
plt.legend()
|
|
314
|
+
plt.show()
|
|
315
|
+
|
|
316
|
+
print(f"Final loss: {loss_values[-1]}")
|
|
317
|
+
|
|
318
|
+
return T2_star_map, S0_map
|
|
319
|
+
|
|
320
|
+
def t2_star_three_parametric_3D(TE_all, images, num_iterations=10000, initial_lr=0.01, lr_decay_factor=0.1, patience=100, initial_T2_star=20.0):
|
|
321
|
+
"""
|
|
322
|
+
Computes the T2* and S0 maps from MRI images using an exponential decay __private_model.
|
|
323
|
+
Also tracks and plots the loss during optimization, with learning rate adjustment.
|
|
324
|
+
|
|
325
|
+
Parameters:
|
|
326
|
+
- TE_all: A list or numpy array of echo times (TE) in milliseconds.
|
|
327
|
+
- images: A numpy array of shape (x, y, z, TE) containing the MRI images.
|
|
328
|
+
- num_iterations: Number of iterations for the optimizer (default: 10000).
|
|
329
|
+
- initial_lr: Initial learning rate for the optimizer (default: 0.01).
|
|
330
|
+
- lr_decay_factor: Factor by which the learning rate will be reduced (default: 0.1).
|
|
331
|
+
- patience: Number of iterations to wait before reducing the learning rate (default: 100).
|
|
332
|
+
- initial_T2_star: Initial guess for T2* for all voxels (default: 20.0).
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
- T2_star_map: A numpy array containing the T2* values for each voxel (x, y, z).
|
|
336
|
+
- S0_map: A numpy array containing the S0 values for each voxel (x, y, z).
|
|
337
|
+
"""
|
|
338
|
+
torch.set_grad_enabled(True)
|
|
339
|
+
# Convert echo times to a torch tensor and move to GPU
|
|
340
|
+
TE = torch.tensor(TE_all, dtype=torch.float32).cuda()
|
|
341
|
+
|
|
342
|
+
# Convert images to torch tensor and move to GPU
|
|
343
|
+
images = torch.tensor(images, dtype=torch.float32).cuda()
|
|
344
|
+
|
|
345
|
+
# Define the exponential decay function
|
|
346
|
+
def exp_decay(TE, S0, T2_star):
|
|
347
|
+
return S0[..., None] * torch.exp(-TE[None, None, None, :] / T2_star[..., None])
|
|
348
|
+
|
|
349
|
+
# Prepare initial guesses for S0 and T2* for all voxels
|
|
350
|
+
S0_init = images[..., 0]
|
|
351
|
+
T2_star_init = torch.full(S0_init.shape, initial_T2_star, dtype=torch.float32).cuda()
|
|
352
|
+
|
|
353
|
+
# Parameters to be optimized: S0 and T2* for all voxels
|
|
354
|
+
params = torch.stack([S0_init, T2_star_init], dim=-1)
|
|
355
|
+
params = params.reshape(-1, 2)
|
|
356
|
+
params.requires_grad = True
|
|
357
|
+
|
|
358
|
+
# Optimizer
|
|
359
|
+
optimizer = torch.optim.Adam([params], lr=initial_lr)
|
|
360
|
+
|
|
361
|
+
# Learning rate scheduler that reduces LR when loss stops improving
|
|
362
|
+
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=lr_decay_factor, patience=patience, verbose=True)
|
|
363
|
+
|
|
364
|
+
# Loss function
|
|
365
|
+
def loss_function(params, TE, signal):
|
|
366
|
+
S0, T2_star = params[:, 0], params[:, 1]
|
|
367
|
+
predicted_signal = exp_decay(TE, S0, T2_star)
|
|
368
|
+
return torch.mean((signal - predicted_signal) ** 2)
|
|
369
|
+
|
|
370
|
+
# Flatten the images to match the flattened params
|
|
371
|
+
signal = images.reshape(-1, images.shape[-1])
|
|
372
|
+
|
|
373
|
+
# List to store loss values for each iteration
|
|
374
|
+
loss_values = []
|
|
375
|
+
|
|
376
|
+
# Optimization loop
|
|
377
|
+
for _ in tqdm(range(num_iterations)): # Adjust the number of iterations as needed
|
|
378
|
+
optimizer.zero_grad()
|
|
379
|
+
loss = loss_function(params, TE, signal)
|
|
380
|
+
loss.backward()
|
|
381
|
+
optimizer.step()
|
|
382
|
+
# Store the current loss value
|
|
383
|
+
loss_values.append(loss.item())
|
|
384
|
+
# Step the scheduler with the current loss
|
|
385
|
+
scheduler.step(loss)
|
|
386
|
+
|
|
387
|
+
# Reshape the parameters back to the original image shape
|
|
388
|
+
S0_map, T2_star_map = params[:, 0].reshape(images.shape[:-1]), params[:, 1].reshape(images.shape[:-1])
|
|
389
|
+
|
|
390
|
+
# Convert the results back to CPU and numpy arrays for returning
|
|
391
|
+
T2_star_map = T2_star_map.detach().cpu().numpy()
|
|
392
|
+
S0_map = S0_map.detach().cpu().numpy()
|
|
393
|
+
|
|
394
|
+
# Plot the loss values over iterations
|
|
395
|
+
plt.figure(figsize=(10, 6))
|
|
396
|
+
plt.plot(loss_values, label='Loss')
|
|
397
|
+
plt.xlabel('Iteration')
|
|
398
|
+
plt.ylabel('Loss')
|
|
399
|
+
plt.title('Loss During Optimization with Learning Rate Adjustment')
|
|
400
|
+
plt.grid(True)
|
|
401
|
+
plt.legend()
|
|
402
|
+
plt.show()
|
|
403
|
+
|
|
404
|
+
print(f"Final loss: {loss_values[-1]}")
|
|
405
|
+
|
|
406
|
+
return T2_star_map, S0_map
|
|
407
|
+
|
|
408
|
+
def reconstruct_images(T2_star_map, S0_map, TE_all):
|
|
409
|
+
"""
|
|
410
|
+
Reconstructs the images using the T2_star_map and S0_map.
|
|
411
|
+
|
|
412
|
+
Parameters:
|
|
413
|
+
- T2_star_map: A numpy array containing the T2* values for each voxel.
|
|
414
|
+
- S0_map: A numpy array containing the S0 values for each voxel.
|
|
415
|
+
- TE_all: A list or numpy array of echo times (TE) in milliseconds.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
- reconstructed_images: A numpy array containing the reconstructed images.
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
# Convert to torch tensors and move to GPU if available
|
|
422
|
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
423
|
+
T2_star_map = torch.tensor(T2_star_map, dtype=torch.float32).to(device)
|
|
424
|
+
S0_map = torch.tensor(S0_map, dtype=torch.float32).to(device)
|
|
425
|
+
TE_all = torch.tensor(TE_all, dtype=torch.float32).to(device)
|
|
426
|
+
|
|
427
|
+
# Reconstruct the images using the exponential decay __private_model
|
|
428
|
+
try:
|
|
429
|
+
reconstructed_images = S0_map[..., None] * torch.exp(-TE_all[None, None, :] / T2_star_map[..., None])
|
|
430
|
+
except:
|
|
431
|
+
return S0_map[..., None] * torch.exp(-TE_all[None, None, None, :] / T2_star_map[..., None])
|
|
432
|
+
|
|
433
|
+
# Move back to CPU and convert to numpy array
|
|
434
|
+
reconstructed_images = reconstructed_images.cpu().numpy()
|
|
435
|
+
|
|
436
|
+
return reconstructed_images
|
|
437
|
+
|
|
438
|
+
def calculate_rmse_percentage_s0(original_images, reconstructed_images, S0_map):
|
|
439
|
+
"""
|
|
440
|
+
Calculates the RMSE in percentage of S0 between the original and reconstructed images.
|
|
441
|
+
|
|
442
|
+
Parameters:
|
|
443
|
+
- original_images: A numpy array containing the original images.
|
|
444
|
+
- reconstructed_images: A numpy array containing the reconstructed images.
|
|
445
|
+
- S0_map: A numpy array containing the S0 values for each voxel.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
- rmse_percentage: RMSE as a percentage of S0.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
# Calculate the squared error
|
|
452
|
+
squared_error = (original_images - reconstructed_images) ** 2
|
|
453
|
+
|
|
454
|
+
# Calculate the mean squared error (MSE) across the TE dimension
|
|
455
|
+
mse = np.mean(squared_error, axis=-1)
|
|
456
|
+
|
|
457
|
+
# Calculate the RMSE
|
|
458
|
+
rmse = np.sqrt(mse)
|
|
459
|
+
|
|
460
|
+
# Calculate RMSE as a percentage of S0
|
|
461
|
+
rmse_percentage = 100 * (rmse / S0_map)
|
|
462
|
+
|
|
463
|
+
return rmse_percentage
|
|
464
|
+
|
|
465
|
+
def _print_last_non_nan(lst):
|
|
466
|
+
# Iterate over the list in reverse order
|
|
467
|
+
for i in range(len(lst) - 1, -1, -1):
|
|
468
|
+
# Check if the item is not NaN
|
|
469
|
+
if not math.isnan(lst[i]):
|
|
470
|
+
# Print the last non-NaN item and its index
|
|
471
|
+
print(f"Last non-NaN item: {lst[i]}, at index: {i}")
|
|
472
|
+
return lst[i], i # Return the item and its index
|
|
473
|
+
print("No non-NaN items found.")
|
|
474
|
+
return None, None
|