antspymm 1.5.0__py3-none-any.whl → 1.5.2__py3-none-any.whl

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.
antspymm/__init__.py CHANGED
@@ -7,6 +7,8 @@ except:
7
7
  from .mm import get_data
8
8
  from .mm import ants_to_nibabel_affine
9
9
  from .mm import get_dti
10
+ from .mm import efficient_tensor_fit
11
+ from .mm import efficient_dwi_fit
10
12
  from .mm import triangular_to_tensor
11
13
  from .mm import dti_numpy_to_image
12
14
  from .mm import transform_and_reorient_dti
antspymm/mm.py CHANGED
@@ -3555,6 +3555,239 @@ def trim_dti_mask( fa, mask, param=4.0 ):
3555
3555
  trim_mask = ants.iMath(trim_mask,"MD",paramVox-1)
3556
3556
  return trim_mask
3557
3557
 
3558
+
3559
+
3560
+ def efficient_tensor_fit( gtab, fit_method, imagein, maskin, diffusion_model='DTI',
3561
+ chunk_size=10, num_threads=1, verbose=True):
3562
+ """
3563
+ Efficient and optionally parallelized tensor reconstruction using DiPy.
3564
+
3565
+ Parameters
3566
+ ----------
3567
+ gtab : GradientTable
3568
+ Dipy gradient table.
3569
+ fit_method : str
3570
+ Tensor fitting method (e.g. 'WLS', 'OLS', 'RESTORE').
3571
+ imagein : ants.ANTsImage
3572
+ 4D diffusion-weighted image.
3573
+ maskin : ants.ANTsImage
3574
+ Binary brain mask image.
3575
+ diffusion_model : string, optional
3576
+ DTI, FreeWater, DKI.
3577
+ chunk_size : int, optional
3578
+ Number of slices (along z-axis) to process at once.
3579
+ num_threads : int, optional
3580
+ Number of threads to use (1 = single-threaded).
3581
+ verbose : bool, optional
3582
+ Print status updates.
3583
+
3584
+ Returns
3585
+ -------
3586
+ tenfit : TensorFit or FreeWaterTensorFit
3587
+ Fitted tensor model.
3588
+ FA : ants.ANTsImage
3589
+ Fractional anisotropy image.
3590
+ MD : ants.ANTsImage
3591
+ Mean diffusivity image.
3592
+ RGB : ants.ANTsImage
3593
+ RGB FA map.
3594
+ """
3595
+ assert imagein.dimension == 4, "Input image must be 4D"
3596
+
3597
+ import ants
3598
+ import numpy as np
3599
+ import dipy.reconst.dti as dti
3600
+ import dipy.reconst.fwdti as fwdti
3601
+ from dipy.reconst.dti import fractional_anisotropy
3602
+ from dipy.reconst.dti import color_fa
3603
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3604
+
3605
+ img_data = imagein.numpy()
3606
+ mask = maskin.numpy().astype(bool)
3607
+ X, Y, Z, N = img_data.shape
3608
+ if verbose:
3609
+ print(f"Input shape: {img_data.shape}, Processing in chunks of {chunk_size} slices.")
3610
+
3611
+ model = fwdti.FreeWaterTensorModel(gtab) if diffusion_model == 'FreeWater' else dti.TensorModel(gtab, fit_method=fit_method)
3612
+
3613
+ def process_chunk(z_start):
3614
+ z_end = min(Z, z_start + chunk_size)
3615
+ local_data = img_data[:, :, z_start:z_end, :]
3616
+ local_mask = mask[:, :, z_start:z_end]
3617
+ masked_data = local_data * local_mask[..., None]
3618
+ masked_data = np.nan_to_num(masked_data, nan=0)
3619
+ fit = model.fit(masked_data)
3620
+ FA_chunk = fractional_anisotropy(fit.evals)
3621
+ FA_chunk[np.isnan(FA_chunk)] = 1
3622
+ FA_chunk = np.clip(FA_chunk, 0, 1)
3623
+ MD_chunk = dti.mean_diffusivity(fit.evals)
3624
+ RGB_chunk = color_fa(FA_chunk, fit.evecs)
3625
+ return z_start, z_end, FA_chunk, MD_chunk, RGB_chunk
3626
+
3627
+ FA_vol = np.zeros((X, Y, Z), dtype=np.float32)
3628
+ MD_vol = np.zeros((X, Y, Z), dtype=np.float32)
3629
+ RGB_vol = np.zeros((X, Y, Z, 3), dtype=np.float32)
3630
+
3631
+ chunks = range(0, Z, chunk_size)
3632
+ if num_threads > 1:
3633
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
3634
+ futures = {executor.submit(process_chunk, z): z for z in chunks}
3635
+ for f in as_completed(futures):
3636
+ z_start, z_end, FA_chunk, MD_chunk, RGB_chunk = f.result()
3637
+ FA_vol[:, :, z_start:z_end] = FA_chunk
3638
+ MD_vol[:, :, z_start:z_end] = MD_chunk
3639
+ RGB_vol[:, :, z_start:z_end, :] = RGB_chunk
3640
+ else:
3641
+ for z in chunks:
3642
+ z_start, z_end, FA_chunk, MD_chunk, RGB_chunk = process_chunk(z)
3643
+ FA_vol[:, :, z_start:z_end] = FA_chunk
3644
+ MD_vol[:, :, z_start:z_end] = MD_chunk
3645
+ RGB_vol[:, :, z_start:z_end, :] = RGB_chunk
3646
+
3647
+ b0 = ants.slice_image(imagein, axis=3, idx=0)
3648
+ FA = ants.copy_image_info(b0, ants.from_numpy(FA_vol))
3649
+ MD = ants.copy_image_info(b0, ants.from_numpy(MD_vol))
3650
+ RGB_channels = [ants.copy_image_info(b0, ants.from_numpy(RGB_vol[..., i])) for i in range(3)]
3651
+ RGB = ants.merge_channels(RGB_channels)
3652
+
3653
+ return model.fit(img_data * mask[..., None]), FA, MD, RGB
3654
+
3655
+
3656
+
3657
+ def efficient_dwi_fit(gtab, diffusion_model, imagein, maskin,
3658
+ model_params=None, bvals_to_use=None,
3659
+ chunk_size=1024, num_threads=1, verbose=True):
3660
+ """
3661
+ Efficient and optionally parallelized diffusion model reconstruction using DiPy.
3662
+
3663
+ Parameters
3664
+ ----------
3665
+ gtab : GradientTable
3666
+ DiPy gradient table.
3667
+ diffusion_model : str
3668
+ One of ['DTI', 'FreeWater', 'DKI'].
3669
+ imagein : ants.ANTsImage
3670
+ 4D diffusion-weighted image.
3671
+ maskin : ants.ANTsImage
3672
+ Binary brain mask image.
3673
+ model_params : dict, optional
3674
+ Additional parameters passed to model constructors.
3675
+ bvals_to_use : list of int, optional
3676
+ Subset of b-values to use for the fit (e.g., [0, 1000, 2000]).
3677
+ chunk_size : int, optional
3678
+ Maximum number of voxels per chunk (default 1024).
3679
+ num_threads : int, optional
3680
+ Number of parallel threads.
3681
+ verbose : bool, optional
3682
+ Whether to print status messages.
3683
+
3684
+ Returns
3685
+ -------
3686
+ fit : dipy ModelFit
3687
+ The fitted model object.
3688
+ FA : ants.ANTsImage or None
3689
+ Fractional anisotropy image (if applicable).
3690
+ MD : ants.ANTsImage or None
3691
+ Mean diffusivity image (if applicable).
3692
+ RGB : ants.ANTsImage or None
3693
+ Color FA image (if applicable).
3694
+ """
3695
+ import ants
3696
+ import numpy as np
3697
+ import dipy.reconst.dti as dti
3698
+ import dipy.reconst.fwdti as fwdti
3699
+ import dipy.reconst.dki as dki
3700
+ from dipy.core.gradients import gradient_table
3701
+ from dipy.reconst.dti import fractional_anisotropy, color_fa, mean_diffusivity
3702
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3703
+
3704
+ assert imagein.dimension == 4, "Input image must be 4D"
3705
+ model_params = model_params or {}
3706
+
3707
+ img_data = imagein.numpy()
3708
+ mask = maskin.numpy().astype(bool)
3709
+ X, Y, Z, N = img_data.shape
3710
+ inplane_size = X * Y
3711
+
3712
+ # Convert chunk_size from voxel count to number of slices
3713
+ slices_per_chunk = max(1, chunk_size // inplane_size)
3714
+
3715
+ if verbose:
3716
+ print(f"[INFO] Image shape: {img_data.shape}")
3717
+ print(f"[INFO] Using model: {diffusion_model}")
3718
+ print(f"[INFO] Max voxels per chunk: {chunk_size} (→ {slices_per_chunk} slices) | Threads: {num_threads}")
3719
+
3720
+ if bvals_to_use is not None:
3721
+ bvals_to_use = set(bvals_to_use)
3722
+ sel = np.isin(gtab.bvals, list(bvals_to_use))
3723
+ img_data = img_data[..., sel]
3724
+ gtab = gradient_table(gtab.bvals[sel], bvecs=gtab.bvecs[sel])
3725
+ if verbose:
3726
+ print(f"[INFO] Selected b-values: {sorted(bvals_to_use)}")
3727
+ print(f"[INFO] Selected volumes: {sel.sum()} / {N}")
3728
+
3729
+ def get_model(name, gtab, **params):
3730
+ if name == 'DTI':
3731
+ return dti.TensorModel(gtab, **params)
3732
+ elif name == 'FreeWater':
3733
+ return fwdti.FreeWaterTensorModel(gtab)
3734
+ elif name == 'DKI':
3735
+ return dki.DiffusionKurtosisModel(gtab, **params)
3736
+ else:
3737
+ raise ValueError(f"Unsupported model: {name}")
3738
+
3739
+ model = get_model(diffusion_model, gtab, **model_params)
3740
+
3741
+ FA_vol = np.zeros((X, Y, Z), dtype=np.float32)
3742
+ MD_vol = np.zeros((X, Y, Z), dtype=np.float32)
3743
+ RGB_vol = np.zeros((X, Y, Z, 3), dtype=np.float32)
3744
+ has_tensor_metrics = diffusion_model in ['DTI', 'FreeWater']
3745
+
3746
+ def process_chunk(z_start):
3747
+ z_end = min(Z, z_start + slices_per_chunk)
3748
+ local_data = img_data[:, :, z_start:z_end, :]
3749
+ local_mask = mask[:, :, z_start:z_end]
3750
+ masked_data = local_data * local_mask[..., None]
3751
+ masked_data = np.nan_to_num(masked_data, nan=0)
3752
+ fit = model.fit(masked_data)
3753
+ if has_tensor_metrics and hasattr(fit, 'evals') and hasattr(fit, 'evecs'):
3754
+ FA = fractional_anisotropy(fit.evals)
3755
+ FA[np.isnan(FA)] = 1
3756
+ FA = np.clip(FA, 0, 1)
3757
+ MD = mean_diffusivity(fit.evals)
3758
+ RGB = color_fa(FA, fit.evecs)
3759
+ return z_start, z_end, FA, MD, RGB
3760
+ return z_start, z_end, None, None, None
3761
+
3762
+ chunks = range(0, Z, slices_per_chunk)
3763
+ if num_threads > 1:
3764
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
3765
+ futures = {executor.submit(process_chunk, z): z for z in chunks}
3766
+ for f in as_completed(futures):
3767
+ z_start, z_end, FA, MD, RGB = f.result()
3768
+ if FA is not None:
3769
+ FA_vol[:, :, z_start:z_end] = FA
3770
+ MD_vol[:, :, z_start:z_end] = MD
3771
+ RGB_vol[:, :, z_start:z_end, :] = RGB
3772
+ else:
3773
+ for z in chunks:
3774
+ z_start, z_end, FA, MD, RGB = process_chunk(z)
3775
+ if FA is not None:
3776
+ FA_vol[:, :, z_start:z_end] = FA
3777
+ MD_vol[:, :, z_start:z_end] = MD
3778
+ RGB_vol[:, :, z_start:z_end, :] = RGB
3779
+
3780
+ b0 = ants.slice_image(imagein, axis=3, idx=0)
3781
+ FA_img = ants.copy_image_info(b0, ants.from_numpy(FA_vol)) if has_tensor_metrics else None
3782
+ MD_img = ants.copy_image_info(b0, ants.from_numpy(MD_vol)) if has_tensor_metrics else None
3783
+ RGB_img = (ants.merge_channels([
3784
+ ants.copy_image_info(b0, ants.from_numpy(RGB_vol[..., i])) for i in range(3)
3785
+ ]) if has_tensor_metrics else None)
3786
+
3787
+ full_fit = model.fit(img_data * mask[..., None])
3788
+ return full_fit, FA_img, MD_img, RGB_img
3789
+
3790
+
3558
3791
  def dipy_dti_recon(
3559
3792
  image,
3560
3793
  bvalsfn,
@@ -3565,7 +3798,7 @@ def dipy_dti_recon(
3565
3798
  mask_closing = 5,
3566
3799
  fit_method='WLS',
3567
3800
  trim_the_mask=2.0,
3568
- free_water=False,
3801
+ diffusion_model='DTI',
3569
3802
  verbose=False ):
3570
3803
  """
3571
3804
  DiPy DTI reconstruction - building on the DiPy basic DTI example
@@ -3592,7 +3825,8 @@ def dipy_dti_recon(
3592
3825
 
3593
3826
  trim_the_mask : float >=0 post-hoc method for trimming the mask
3594
3827
 
3595
- free_water : boolean
3828
+ diffusion_model : string
3829
+ DTI, FreeWater, DKI
3596
3830
 
3597
3831
  verbose : boolean
3598
3832
 
@@ -3645,47 +3879,22 @@ def dipy_dti_recon(
3645
3879
  if verbose:
3646
3880
  print("recon dti.TensorModel",flush=True)
3647
3881
 
3648
- def justthefit( gtab, fit_method, imagein, maskin, free_water=False ):
3649
- if fit_method is None:
3650
- return None, None, None, None
3651
- maskedimage=[]
3652
- for myidx in range(imagein.shape[3]):
3653
- b0 = ants.slice_image( imagein, axis=3, idx=myidx)
3654
- maskedimage.append( b0 * maskin )
3655
- maskedimage = ants.list_to_ndimage( imagein, maskedimage )
3656
- maskdata = maskedimage.numpy()
3657
- if free_water:
3658
- tenmodel = fwdti.FreeWaterTensorModel(gtab)
3659
- else:
3660
- tenmodel = dti.TensorModel(gtab,fit_method=fit_method)
3661
- tenfit = tenmodel.fit(maskdata)
3662
- FA = fractional_anisotropy(tenfit.evals)
3663
- FA[np.isnan(FA)] = 1
3664
- FA = np.clip(FA, 0, 1)
3665
- MD1 = dti.mean_diffusivity(tenfit.evals)
3666
- MD1 = ants.copy_image_info( b0, ants.from_numpy( MD1.astype(np.float32) ) )
3667
- FA = ants.copy_image_info( b0, ants.from_numpy( FA.astype(np.float32) ) )
3668
- FA, MD1 = impute_fa( FA, MD1 )
3669
- RGB = color_fa(FA.numpy(), tenfit.evecs)
3670
- RGB = ants.from_numpy( RGB.astype(np.float32) )
3671
- RGB0 = ants.copy_image_info( b0, ants.slice_image( RGB, axis=3, idx=0 ) )
3672
- RGB1 = ants.copy_image_info( b0, ants.slice_image( RGB, axis=3, idx=1 ) )
3673
- RGB2 = ants.copy_image_info( b0, ants.slice_image( RGB, axis=3, idx=2 ) )
3674
- RGB = ants.merge_channels( [RGB0,RGB1,RGB2] )
3675
- return tenfit, FA, MD1, RGB
3676
-
3677
3882
  bvecs = repair_bvecs( bvecs )
3678
3883
  gtab = gradient_table(bvals, bvecs=bvecs, atol=2.0 )
3679
- if free_water:
3680
- free_water=len( np.unique( bvals ) ) >= 3
3681
- tenfit, FA, MD1, RGB = justthefit( gtab, fit_method, image, maskdil, free_water=free_water )
3884
+ mynt=1
3885
+ threads_env = os.environ.get("ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS")
3886
+ if threads_env is not None:
3887
+ mynt = int(threads_env)
3888
+ tenfit, FA, MD1, RGB = efficient_dwi_fit( gtab, diffusion_model, image, maskdil,
3889
+ num_threads=mynt )
3682
3890
  if verbose:
3683
3891
  print("recon dti.TensorModel done",flush=True)
3684
3892
 
3685
3893
  # change the brain mask based on high FA values
3686
3894
  if trim_the_mask > 0 and fit_method is not None:
3687
3895
  mask = trim_dti_mask( FA, mask, trim_the_mask )
3688
- tenfit, FA, MD1, RGB = justthefit( gtab, fit_method, image, mask, free_water=free_water )
3896
+ tenfit, FA, MD1, RGB = efficient_dwi_fit( gtab, diffusion_model, image, maskdil,
3897
+ num_threads=mynt )
3689
3898
 
3690
3899
  return {
3691
3900
  'tensormodel' : tenfit,
@@ -3769,7 +3978,7 @@ def joint_dti_recon(
3769
3978
  fit_method='WLS',
3770
3979
  impute = False,
3771
3980
  censor = True,
3772
- free_water = False,
3981
+ diffusion_model = 'DTI',
3773
3982
  verbose = False ):
3774
3983
  """
3775
3984
  1. pass in subject data and 1mm JHU atlas/labels
@@ -3824,7 +4033,8 @@ def joint_dti_recon(
3824
4033
 
3825
4034
  censor : boolean
3826
4035
 
3827
- free_water : boolean
4036
+ diffusion_model : string
4037
+ DTI, FreeWater, DKI
3828
4038
 
3829
4039
  verbose : boolean
3830
4040
 
@@ -3944,7 +4154,7 @@ def joint_dti_recon(
3944
4154
  img_LRdwp, bval_LR, bvec_LR,
3945
4155
  mask = brain_mask,
3946
4156
  fit_method=fit_method,
3947
- mask_dilation=0, free_water=free_water, verbose=True )
4157
+ mask_dilation=0, diffusion_model=diffusion_model, verbose=True )
3948
4158
  if verbose:
3949
4159
  print("recon done", flush=True)
3950
4160
 
@@ -4245,7 +4455,7 @@ def dwi_deterministic_tracking(
4245
4455
  gtab = gradient_table(bvals, bvecs=bvecs, atol=2.0 )
4246
4456
  if mask is None:
4247
4457
  mask = ants.threshold_image( fa, fa_thresh, 2.0 ).iMath("GetLargestComponent")
4248
- dwi_data = dwi.numpy() # dwi_img.get_fdata()
4458
+ dwi_data = dwi.numpy()
4249
4459
  dwi_mask = mask.numpy() == 1
4250
4460
  dti_model = dti.TensorModel(gtab,fit_method=fit_method)
4251
4461
  if verbose:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: antspymm
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: multi-channel/time-series medical image processing with antspyx
5
5
  Author-email: "Avants, Gosselin, Tustison, Reardon" <stnava@gmail.com>
6
6
  License: Apache-2.0
@@ -0,0 +1,7 @@
1
+ antspymm/__init__.py,sha256=DnkidUfEu3Dl0tuWNTA-9VOUkBtH_cROKiPGNNXNagU,4637
2
+ antspymm/mm.py,sha256=NbT1IBiuEMtMoanr_8yO3kLNpSSfV0j1_155gykGOM0,526972
3
+ antspymm-1.5.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
+ antspymm-1.5.2.dist-info/METADATA,sha256=3Ttc-cPytZsiNct2MRz4PZwe5JmdZzaEsEgu7hxKxvA,25939
5
+ antspymm-1.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ antspymm-1.5.2.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
+ antspymm-1.5.2.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- antspymm/__init__.py,sha256=3t4jqSbQVRfecm1ekc02ytWlZ3yAGcPyB_kzQAcg4Bc,4566
2
- antspymm/mm.py,sha256=UdmKcS4wYazBdC8DUchdnzXZzM4tetFx3RfDS9zH4ys,519104
3
- antspymm-1.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-1.5.0.dist-info/METADATA,sha256=fXgNR7g8l0I1AFjAPIZwTRpN_RBrW5FiaBtdjg5z0Mc,25939
5
- antspymm-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- antspymm-1.5.0.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-1.5.0.dist-info/RECORD,,