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 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
@@ -0,0 +1,6 @@
1
+ # File created by: Eisa Hedayati
2
+ # Date: 9/5/2024
3
+ # Description: This file is developed at CMRR
4
+
5
+ from . import grappa
6
+ from . import sense
@@ -0,0 +1,8 @@
1
+ # File created by: Eisa Hedayati
2
+ # Date: 2/15/2024
3
+ # Description: This file is developed at CMRR
4
+
5
+ from . import grappa_1D
6
+ from . import grappa_2D
7
+ from .grappa_1D import *
8
+ from .grappa_2D import *