Skip to content

Commit 2a94ac8

Browse files
authored
Merge pull request #37 from LOAMRI/copilot/fix-4
Enhanced documentation with comprehensive examples for ASL reconstruction classes and utilities
2 parents 65c8a5a + 7545f48 commit 2a94ac8

File tree

15 files changed

+990
-77
lines changed

15 files changed

+990
-77
lines changed

asltk/reconstruction/cbf_mapping.py

Lines changed: 129 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,31 +60,98 @@ def __init__(self, asl_data: ASLData) -> None:
6060
self._att_map = np.zeros(self._asl_data('m0').shape)
6161

6262
def set_brain_mask(self, brain_mask: np.ndarray, label: int = 1):
63-
"""Defines whether a brain a mask is applied to the CBFMapping
64-
calculation
63+
"""Defines a brain mask to limit CBF mapping calculations to specific regions.
64+
65+
A brain mask significantly improves processing speed by limiting calculations
66+
to brain tissue voxels and excluding background regions. It also improves
67+
the quality of results by focusing the fitting algorithm on relevant tissue.
6568
6669
A image mask is simply an image that defines the voxels where the ASL
67-
calculation should be made. Basically any integer value can be used as
68-
proper label mask.
70+
calculation should be made. The mask should have the same spatial dimensions
71+
as the M0 reference image.
6972
7073
A most common approach is to use a binary image (zeros for background
71-
and 1 for the brain tissues). Anyway, the default behavior of the
72-
method can transform a integer-pixel values image to a binary mask with
73-
the `label` parameter provided by the user
74+
and 1 for brain tissues). However, the method can also handle multi-label
75+
masks by specifying which label value represents brain tissue.
7476
7577
Args:
76-
brain_mask (np.ndarray): The image representing the brain mask label (int, optional): The label value used to define the foreground tissue (brain). Defaults to 1.
78+
brain_mask (np.ndarray): The image representing the brain mask.
79+
Must match the spatial dimensions of the M0 image.
80+
label (int, optional): The label value used to define the foreground
81+
tissue (brain). Defaults to 1. Voxels with this value will be
82+
included in processing.
83+
84+
Examples:
85+
Use a binary brain mask:
86+
>>> from asltk.asldata import ASLData
87+
>>> from asltk.reconstruction import CBFMapping
88+
>>> import numpy as np
89+
>>> asl_data = ASLData(
90+
... pcasl='./tests/files/pcasl_mte.nii.gz',
91+
... m0='./tests/files/m0.nii.gz',
92+
... ld_values=[1.8], pld_values=[1.8]
93+
... )
94+
>>> cbf_mapper = CBFMapping(asl_data)
95+
>>> # Create a simple brain mask (center region only)
96+
>>> mask_shape = asl_data('m0').shape # Get M0 dimensions
97+
>>> brain_mask = np.zeros(mask_shape)
98+
>>> brain_mask[2:6, 10:25, 10:25] = 1 # Define brain region
99+
>>> cbf_mapper.set_brain_mask(brain_mask)
100+
101+
Load and use an existing brain mask:
102+
>>> # Load pre-computed brain mask
103+
>>> from asltk.utils import load_image
104+
>>> brain_mask = load_image('./tests/files/m0_brain_mask.nii.gz')
105+
>>> cbf_mapper.set_brain_mask(brain_mask)
106+
107+
Use multi-label mask (select specific region):
108+
>>> # Assuming a segmentation mask with different tissue labels
109+
>>> segmentation_mask = np.random.randint(0, 4, mask_shape) # Example
110+
>>> # Use only label 2 (e.g., grey matter)
111+
>>> cbf_mapper.set_brain_mask(segmentation_mask, label=2)
112+
113+
Automatic thresholding of M0 image as mask:
114+
>>> # Use M0 intensity to create brain mask
115+
>>> m0_data = asl_data('m0')
116+
>>> threshold = np.percentile(m0_data, 20) # Bottom 20% as background
117+
>>> auto_mask = (m0_data > threshold).astype(np.uint8)
118+
>>> cbf_mapper.set_brain_mask(auto_mask)
119+
120+
Raises:
121+
ValueError: If brain_mask dimensions don't match M0 image dimensions.
77122
"""
78123
_check_mask_values(brain_mask, label, self._asl_data('m0').shape)
79124

80125
binary_mask = (brain_mask == label).astype(np.uint8) * label
81126
self._brain_mask = binary_mask
82127

83128
def get_brain_mask(self):
84-
"""Get the brain mask image
129+
"""Get the current brain mask image being used for CBF calculations.
85130
86131
Returns:
87-
(np.ndarray): The brain mask image
132+
np.ndarray: The brain mask image as a binary array where 1 indicates
133+
brain tissue voxels that will be processed, and 0 indicates
134+
background voxels that will be skipped.
135+
136+
Examples:
137+
Check if a brain mask has been set:
138+
>>> from asltk.asldata import ASLData
139+
>>> from asltk.reconstruction import CBFMapping
140+
>>> import numpy as np
141+
>>> asl_data = ASLData(
142+
... pcasl='./tests/files/pcasl_mte.nii.gz',
143+
... m0='./tests/files/m0.nii.gz',
144+
... ld_values=[1.8], pld_values=[1.8]
145+
... )
146+
>>> cbf_mapper = CBFMapping(asl_data)
147+
>>> # Initially, mask covers entire volume
148+
>>> current_mask = cbf_mapper.get_brain_mask()
149+
150+
Verify brain mask after setting:
151+
>>> brain_mask = np.ones(asl_data('m0').shape)
152+
>>> brain_mask[0:4, :, :] = 0 # Remove some slices
153+
>>> cbf_mapper.set_brain_mask(brain_mask)
154+
>>> updated_mask = cbf_mapper.get_brain_mask()
88155
"""
89156
return self._brain_mask
90157

@@ -95,7 +162,11 @@ def create_map(
95162
par0=[1e-5, 1000],
96163
cores: int = cpu_count(),
97164
):
98-
"""Create the CBF and also ATT maps
165+
"""Create the CBF and also ATT maps using the Buxton ASL model.
166+
167+
This method performs voxel-wise non-linear fitting of the Buxton ASL model
168+
to generate Cerebral Blood Flow (CBF) and Arterial Transit Time (ATT) maps.
169+
The fitting is performed in parallel using multiple CPU cores for efficiency.
99170
100171
Note:
101172
By default the ATT map is already calculated using the same Buxton
@@ -110,13 +181,55 @@ def create_map(
110181
options
111182
112183
Args:
113-
ub (list, optional): The upper limit values. Defaults to [1.0, 5000.0].
114-
lb (list, optional): The lower limit values. Defaults to [0.0, 0.0].
115-
par0 (list, optional): The initial guess parameter for non-linear fitting. Defaults to [1e-5, 1000].
116-
cores (int, optional): Defines how many CPU threads can be used for the class. Defaults is using all the availble threads.
184+
ub (list, optional): The upper bounds for [CBF, ATT] fitting parameters.
185+
Defaults to [1.0, 5000.0]. CBF in relative units, ATT in ms.
186+
lb (list, optional): The lower bounds for [CBF, ATT] fitting parameters.
187+
Defaults to [0.0, 0.0]. Both parameters must be non-negative.
188+
par0 (list, optional): The initial guess for [CBF, ATT] parameters.
189+
Defaults to [1e-5, 1000]. Good starting values help convergence.
190+
cores (int, optional): Number of CPU threads to use for parallel processing.
191+
Defaults to using all available threads. Use fewer cores to preserve
192+
system resources.
117193
118194
Returns:
119-
(dict): A dictionary with 'cbf', 'att' and 'cbf_norm'
195+
dict: A dictionary containing:
196+
- 'cbf': Raw CBF map in model units (numpy.ndarray)
197+
- 'cbf_norm': Normalized CBF map in mL/100g/min (numpy.ndarray)
198+
- 'att': ATT map in milliseconds (numpy.ndarray)
199+
200+
Examples: # doctest: +SKIP
201+
Basic CBF mapping with default parameters:
202+
>>> from asltk.asldata import ASLData
203+
>>> from asltk.reconstruction import CBFMapping
204+
>>> import numpy as np
205+
>>> # Load ASL data with LD/PLD values
206+
>>> asl_data = ASLData(
207+
... pcasl='./tests/files/pcasl_mte.nii.gz',
208+
... m0='./tests/files/m0.nii.gz',
209+
... ld_values=[1.8, 1.8, 1.8],
210+
... pld_values=[0.8, 1.8, 2.8]
211+
... )
212+
>>> cbf_mapper = CBFMapping(asl_data)
213+
>>> # Set brain mask (recommended for faster processing)
214+
>>> brain_mask = np.ones((5, 35, 35)) # Example mask
215+
>>> cbf_mapper.set_brain_mask(brain_mask)
216+
>>> # Generate maps
217+
>>> results = cbf_mapper.create_map() # doctest: +SKIP
218+
219+
Custom parameter bounds for specific tissue properties:
220+
>>> # For grey matter regions (higher CBF expected)
221+
>>> results_gm = cbf_mapper.create_map(
222+
... ub=[2.0, 3000.0], # Higher CBF upper bound
223+
... lb=[0.1, 500.0], # Reasonable lower bounds
224+
... par0=[0.5, 1200.0] # Good initial guess for GM
225+
... ) # doctest: +SKIP
226+
227+
Memory-efficient processing with limited cores:
228+
>>> # Use only 4 cores to preserve system resources
229+
>>> results = cbf_mapper.create_map(cores=4) # doctest: +SKIP
230+
231+
Raises:
232+
ValueError: If cores parameter is invalid, or if LD/PLD values are missing.
120233
"""
121234
if (cores < 0) or (cores > cpu_count()) or not isinstance(cores, int):
122235
raise ValueError(

asltk/reconstruction/multi_dw_mapping.py

Lines changed: 168 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,58 @@
2828

2929
class MultiDW_ASLMapping(MRIParameters):
3030
def __init__(self, asl_data: ASLData):
31+
"""Multi-Diffusion-Weighted ASL mapping constructor for advanced perfusion analysis.
32+
33+
MultiDW_ASLMapping enables sophisticated ASL analysis by incorporating multiple
34+
diffusion weightings (b-values) to separate intravascular and tissue
35+
compartments. This approach provides enhanced characterization of perfusion
36+
and can help differentiate between different vascular compartments.
37+
38+
The class implements diffusion-weighted ASL analysis that can distinguish:
39+
- Fast-flowing blood (intravascular component)
40+
- Slow-flowing blood and tissue perfusion
41+
- Apparent diffusion coefficients for each compartment
42+
- Water exchange parameters between compartments
43+
44+
Notes:
45+
The ASLData object must contain `dw_values` - a list of diffusion
46+
b-values used during ASL acquisition. These b-values are essential
47+
for the multi-compartment diffusion model fitting.
48+
49+
Examples:
50+
Basic multi-DW ASL mapping setup:
51+
>>> from asltk.asldata import ASLData
52+
>>> from asltk.reconstruction import MultiDW_ASLMapping
53+
>>> # Create ASL data with diffusion weighting
54+
>>> asl_data = ASLData(
55+
... pcasl='./tests/files/pcasl_mdw.nii.gz',
56+
... m0='./tests/files/m0.nii.gz',
57+
... dw_values=[0, 50, 100, 200], # b-values in s/mm²
58+
... ld_values=[1.8, 1.8, 1.8, 1.8],
59+
... pld_values=[0.8, 1.8, 2.8, 3.8]
60+
... )
61+
>>> mdw_mapper = MultiDW_ASLMapping(asl_data)
62+
63+
Access diffusion-related maps (after processing):
64+
>>> # These maps will be populated after create_map() is called
65+
>>> # A1: Signal amplitude for compartment 1
66+
>>> # D1: Apparent diffusion coefficient for compartment 1
67+
>>> # A2: Signal amplitude for compartment 2
68+
>>> # D2: Apparent diffusion coefficient for compartment 2
69+
>>> # kw: Water exchange parameter
70+
71+
Args:
72+
asl_data (ASLData): The ASL data object containing multi-DW acquisition.
73+
Must include dw_values (b-values), ld_values, and pld_values.
74+
75+
Raises:
76+
ValueError: If ASLData object lacks required DW values for
77+
diffusion-weighted analysis.
78+
79+
See Also:
80+
CBFMapping: For basic CBF/ATT mapping without diffusion weighting
81+
MultiTE_ASLMapping: For multi-echo ASL analysis
82+
"""
3183
super().__init__()
3284
self._asl_data = asl_data
3385
self._basic_maps = CBFMapping(asl_data)
@@ -52,20 +104,43 @@ def __init__(self, asl_data: ASLData):
52104
self._kw = np.zeros(self._asl_data('m0').shape)
53105

54106
def set_brain_mask(self, brain_mask: np.ndarray, label: int = 1):
55-
"""Defines whether a brain a mask is applied to the MultiDW_ASLMapping
56-
calculation
107+
"""Set brain mask for MultiDW-ASL processing (strongly recommended).
57108
58-
A image mask is simply an image that defines the voxels where the ASL
59-
calculation should be made. Basically any integer value can be used as
60-
proper label mask.
109+
A brain mask is especially important for multi-diffusion-weighted ASL
110+
processing as it significantly reduces computation time by limiting
111+
the intensive voxel-wise fitting to brain tissue regions only.
61112
62-
A most common approach is to use a binary image (zeros for background
63-
and 1 for the brain tissues). Anyway, the default behavior of the
64-
method can transform a integer-pixel values image to a binary mask with
65-
the `label` parameter provided by the user
113+
Without a brain mask, processing time can be prohibitively long (hours)
114+
for whole-volume analysis. A proper brain mask can reduce processing
115+
time by 5-10x while maintaining analysis quality.
66116
67117
Args:
68-
brain_mask (np.ndarray): The image representing the brain mask label (int, optional): The label value used to define the foreground tissue (brain). Defaults to 1.
118+
brain_mask (np.ndarray): The image representing the brain mask.
119+
Must match the spatial dimensions of the M0 image.
120+
label (int, optional): The label value used to define brain tissue.
121+
Defaults to 1. Voxels with this value will be processed.
122+
123+
Examples:
124+
Set a brain mask for efficient processing:
125+
>>> from asltk.asldata import ASLData
126+
>>> from asltk.reconstruction import MultiDW_ASLMapping
127+
>>> import numpy as np
128+
>>> asl_data = ASLData(
129+
... pcasl='./tests/files/pcasl_mdw.nii.gz',
130+
... m0='./tests/files/m0.nii.gz',
131+
... dw_values=[0, 50, 100], ld_values=[1.8]*3, pld_values=[1.8]*3
132+
... )
133+
>>> mdw_mapper = MultiDW_ASLMapping(asl_data)
134+
>>> # Create conservative brain mask (center region only)
135+
>>> mask_shape = asl_data('m0').shape
136+
>>> brain_mask = np.zeros(mask_shape)
137+
>>> brain_mask[1:4, 5:30, 5:30] = 1 # Conservative brain region
138+
>>> mdw_mapper.set_brain_mask(brain_mask)
139+
140+
Note:
141+
For multi-DW ASL, consider using a more conservative (smaller) brain
142+
mask initially to test parameters and processing time, then expand
143+
to full brain analysis once satisfied with results.
69144
"""
70145
_check_mask_values(brain_mask, label, self._asl_data('m0').shape)
71146

@@ -124,6 +199,89 @@ def create_map(
124199
ub: list = [np.inf, np.inf, np.inf, np.inf],
125200
par0: list = [0.5, 0.000005, 0.5, 0.000005],
126201
):
202+
"""Create multi-diffusion-weighted ASL maps for compartment analysis.
203+
204+
This method performs advanced diffusion-weighted ASL analysis to generate
205+
multi-compartment perfusion maps. The analysis uses multiple b-values to
206+
separate fast-flowing intravascular and slower tissue perfusion components.
207+
208+
The method fits a bi-exponential diffusion model to estimate:
209+
- Signal amplitudes and apparent diffusion coefficients for two compartments
210+
- Water exchange parameters between vascular and tissue compartments
211+
- Enhanced CBF characterization with compartment specificity
212+
213+
Note:
214+
The CBF and ATT maps can be provided before calling this method using
215+
set_cbf_map() and set_att_map() methods. If not provided, basic maps
216+
are automatically calculated using the CBFMapping class.
217+
218+
Warning:
219+
This method is computationally intensive as it performs voxel-wise
220+
non-linear fitting without parallel processing. Consider using a brain
221+
mask to limit processing to relevant tissue areas.
222+
223+
Args:
224+
lb (list, optional): Lower bounds for [A1, D1, A2, D2] parameters.
225+
Defaults to [0.0, 0.0, 0.0, 0.0]. All parameters should be non-negative.
226+
- A1, A2: Signal amplitudes (relative units)
227+
- D1, D2: Apparent diffusion coefficients (mm²/s)
228+
ub (list, optional): Upper bounds for [A1, D1, A2, D2] parameters.
229+
Defaults to [np.inf, np.inf, np.inf, np.inf].
230+
par0 (list, optional): Initial guess for [A1, D1, A2, D2] parameters.
231+
Defaults to [0.5, 0.000005, 0.5, 0.000005].
232+
- A1, A2: Typical values 0.1-1.0 (relative amplitudes)
233+
- D1, D2: Typical values 1e-6 to 1e-3 mm²/s
234+
235+
Returns:
236+
dict: Dictionary containing diffusion-weighted ASL maps:
237+
- 'cbf': Basic CBF map in original units (numpy.ndarray)
238+
- 'cbf_norm': Normalized CBF in mL/100g/min (numpy.ndarray)
239+
- 'att': Arterial transit time in ms (numpy.ndarray)
240+
- 'A1': Signal amplitude for compartment 1 (numpy.ndarray)
241+
- 'D1': Apparent diffusion coefficient for compartment 1 in mm²/s (numpy.ndarray)
242+
- 'A2': Signal amplitude for compartment 2 (numpy.ndarray)
243+
- 'D2': Apparent diffusion coefficient for compartment 2 in mm²/s (numpy.ndarray)
244+
- 'kw': Water exchange parameter (numpy.ndarray)
245+
246+
Examples:
247+
Basic multi-DW ASL analysis:
248+
>>> from asltk.asldata import ASLData
249+
>>> from asltk.reconstruction import MultiDW_ASLMapping
250+
>>> import numpy as np
251+
>>> # Load multi-DW ASL data
252+
>>> asl_data = ASLData(
253+
... pcasl='./tests/files/pcasl_mdw.nii.gz',
254+
... m0='./tests/files/m0.nii.gz',
255+
... dw_values=[0, 50, 100, 200], # b-values in s/mm²
256+
... ld_values=[1.8, 1.8, 1.8, 1.8],
257+
... pld_values=[0.8, 1.8, 2.8, 3.8]
258+
... )
259+
>>> mdw_mapper = MultiDW_ASLMapping(asl_data)
260+
>>> # Set brain mask for faster processing (recommended)
261+
>>> brain_mask = np.ones(asl_data('m0').shape)
262+
>>> brain_mask[0:2, :, :] = 0 # Remove some background slices
263+
>>> mdw_mapper.set_brain_mask(brain_mask)
264+
>>> # Generate all maps (may take several minutes)
265+
>>> results = mdw_mapper.create_map() # doctest: +SKIP
266+
267+
Custom parameters for specific tissue analysis:
268+
>>> # For analyzing fast vs slow perfusion components
269+
>>> results = mdw_mapper.create_map(
270+
... lb=[0.1, 1e-6, 0.1, 1e-7], # Minimum realistic values
271+
... ub=[2.0, 1e-3, 2.0, 1e-4], # Maximum realistic values
272+
... par0=[0.8, 5e-5, 0.3, 1e-5] # Initial guesses
273+
... ) # doctest: +SKIP
274+
275+
Note:
276+
Processing time scales with brain mask size. For a full brain analysis,
277+
expect processing times of 30+ minutes depending on data size and
278+
hardware capabilities.
279+
280+
See Also:
281+
set_cbf_map(): Provide pre-computed CBF map
282+
set_att_map(): Provide pre-computed ATT map
283+
CBFMapping: For basic CBF/ATT mapping
284+
"""
127285
self._basic_maps.set_brain_mask(self._brain_mask)
128286

129287
basic_maps = {'cbf': self._cbf_map, 'att': self._att_map}

0 commit comments

Comments
 (0)