antspymm 0.9.9__py3-none-any.whl → 1.0.0__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
@@ -5,6 +5,10 @@ except:
5
5
  pass
6
6
 
7
7
  from .mm import get_data
8
+ from .mm import get_dti
9
+ from .mm import triangular_to_tensor
10
+ from .mm import dti_numpy_to_image
11
+ from .mm import transform_and_reorient_dti
8
12
  from .mm import get_models
9
13
  from .mm import nrg_format_path
10
14
  from .mm import highest_quality_repeat
antspymm/mm.py CHANGED
@@ -13,6 +13,7 @@ __all__ = ['version',
13
13
  'merge_dwi_data',
14
14
  'outlierness_by_modality',
15
15
  'bvec_reorientation',
16
+ 'get_dti',
16
17
  'dti_reg',
17
18
  'mc_reg',
18
19
  'get_data',
@@ -421,7 +422,7 @@ def collect_blind_qc_by_modality( modality_path, set_index_to_fn=True ):
421
422
 
422
423
  def outlierness_by_modality( qcdf, uid='fn', outlier_columns = ['noise', 'snr', 'cnr', 'psnr', 'ssim', 'mi','reflection_err', 'EVR', 'msk_vol'], verbose=False ):
423
424
  """
424
- Calculates outlierness scores for each modality in a dataframe based on given outlier columns using antspyt1w.loop_outlierness() and LOF. LOF appears to be more conservative.
425
+ Calculates outlierness scores for each modality in a dataframe based on given outlier columns using antspyt1w.loop_outlierness() and LOF. LOF appears to be more conservative. This function will impute missing columns with the mean.
425
426
 
426
427
  Args:
427
428
  - qcdf: (Pandas DataFrame) Dataframe containing columns with outlier information for each modality.
@@ -452,6 +453,9 @@ def outlierness_by_modality( qcdf, uid='fn', outlier_columns = ['noise', 'snr',
452
453
  lof = LocalOutlierFactor()
453
454
  locsel = qcdfout["modality"] == mod
454
455
  rr = qcdfout[locsel][outlier_columns]
456
+ with pd.option_context('mode.use_inf_as_na', True):
457
+ for myolcol in outlier_columns:
458
+ rr[myolcol].fillna(rr[myolcol].mean(), inplace=True)
455
459
  if rr.shape[0] > 1:
456
460
  if verbose:
457
461
  print(mod)
@@ -746,6 +750,16 @@ def match_modalities( qc_dataframe, unique_identifier='fn', outlier_column='ol_l
746
750
  mmdf.iloc[k, mmdf.columns.get_loc("flairfn")] = fldf['fn'][locsel].values[0]
747
751
  mmdf.iloc[k, mmdf.columns.get_loc("flairloop")] = fldf[outlier_column][locsel].values[0]
748
752
  mmdf.iloc[k, mmdf.columns.get_loc("flairlof")] = fldf['ol_lof_decision'][locsel].values[0]
753
+ elif sum(locsel) > 1:
754
+ locdf = fldf[locsel]
755
+ dedupe = locdf[["snr","cnr"]].duplicated()
756
+ locdf = locdf[~dedupe]
757
+ if locdf.shape[0] > 1:
758
+ locdf = locdf.sort_values(outlier_column).iloc[:2]
759
+ mmdf.iloc[k, mmdf.columns.get_loc("flairid")] = locdf["imageID"].values[0]
760
+ mmdf.iloc[k, mmdf.columns.get_loc("flairfn")] = locdf["fn"].values[0]
761
+ mmdf.iloc[k, mmdf.columns.get_loc("flairloop")] = locdf[outlier_column].values[0]
762
+ mmdf.iloc[k, mmdf.columns.get_loc("flairlof")] = locdf['ol_lof_decision'].values[0]
749
763
 
750
764
  if nmdf is not None:
751
765
  locsel = nmdf['subjectIDdate'] == mmdf['subjectIDdate'].iloc[k]
@@ -1182,7 +1196,7 @@ def merge_dwi_data( img_LRdwp, bval_LR, bvec_LR, img_RLdwp, bval_RL, bvec_RL ):
1182
1196
  img_LRdwp = ants.list_to_ndimage( img_LRdwp, mimg )
1183
1197
  return img_LRdwp, bval_LR, bvec_LR
1184
1198
 
1185
- def bvec_reorientation( motion_parameters, bvecs ):
1199
+ def bvec_reorientation( motion_parameters, bvecs, rebase=None ):
1186
1200
  if motion_parameters is None:
1187
1201
  return bvecs
1188
1202
  n = len(motion_parameters)
@@ -1223,8 +1237,207 @@ def bvec_reorientation( motion_parameters, bvecs ):
1223
1237
  txparam = ants.get_ants_transform_parameters(txparam)[0:9].reshape( [3,3])
1224
1238
  Rinv = inv( txparam )
1225
1239
  bvecs[myidx,:] = np.dot( Rinv, bvecs[myidx,:] )
1240
+ if rebase is not None:
1241
+ # FIXME - should combine these operations
1242
+ bvecs[myidx,:] = np.dot( rebase, bvecs[myidx,:] )
1226
1243
  return bvecs
1227
1244
 
1245
+ def get_dti( reference_image, tensormodel, upper_triangular=True, return_image=False ):
1246
+ """
1247
+ extract DTI data from a dipy tensormodel
1248
+
1249
+ reference_image : antsImage defining physical space (3D)
1250
+
1251
+ tensormodel : from dipy e.g. the variable myoutx['dtrecon_LR_dewarp']['tensormodel'] if myoutx is produced my joint_dti_recon
1252
+
1253
+ upper_triangular: boolean otherwise use lower triangular coding
1254
+
1255
+ return_image : boolean return the ANTsImage form of DTI otherwise return an array
1256
+
1257
+ Returns
1258
+ -------
1259
+ either an ANTsImage (dim=X.Y.Z with 6 component voxels, upper triangular form)
1260
+ or a 5D NumPy array (dim=X.Y.Z.3.3)
1261
+
1262
+ Notes
1263
+ -----
1264
+ DiPy returns lower triangular form but ANTs expects upper triangular.
1265
+ Here, we default to the ANTs standard but could generalize in the future
1266
+ because not much here depends on ANTs standards of tensor data.
1267
+ ANTs xx,xy,xz,yy,yz,zz
1268
+ DiPy Dxx, Dxy, Dyy, Dxz, Dyz, Dzz
1269
+
1270
+ """
1271
+ # make the DTI - see
1272
+ # https://dipy.org/documentation/1.7.0/examples_built/07_reconstruction/reconst_dti/#sphx-glr-examples-built-07-reconstruction-reconst-dti-py
1273
+ # By default, in DIPY, values are ordered as (Dxx, Dxy, Dyy, Dxz, Dyz, Dzz)
1274
+ # in ANTs - we have: [xx,xy,xz,yy,yz,zz]
1275
+ reoind = np.array([0,1,3,2,4,5]) # arrays are faster than lists
1276
+ import dipy.reconst.dti as dti
1277
+ dtiut = dti.lower_triangular(tensormodel.quadratic_form)
1278
+ it = np.ndindex( reference_image.shape )
1279
+ yyind=2
1280
+ xzind=3
1281
+ if upper_triangular:
1282
+ yyind=3
1283
+ xzind=2
1284
+ for i in it: # convert to upper triangular
1285
+ dtiut[i] = dtiut[i][ reoind ] # do we care if this is doing extra work?
1286
+ if return_image:
1287
+ dtiAnts = ants.from_numpy(dtiut,has_components=True)
1288
+ ants.copy_image_info( reference_image, dtiAnts )
1289
+ return dtiAnts
1290
+ # copy these data into a tensor
1291
+ dtinp = np.zeros(reference_image.shape + (3,3), dtype=float)
1292
+ dtix = np.zeros((3,3), dtype=float)
1293
+ it = np.ndindex( reference_image.shape )
1294
+ for i in it:
1295
+ dtivec = dtiut[i] # in ANTs - we have: [xx,xy,xz,yy,yz,zz]
1296
+ dtix[0,0]=dtivec[0]
1297
+ dtix[1,1]=dtivec[yyind] # 2 for LT
1298
+ dtix[2,2]=dtivec[5]
1299
+ dtix[0,1]=dtix[1,0]=dtivec[1]
1300
+ dtix[0,2]=dtix[2,0]=dtivec[xzind] # 3 for LT
1301
+ dtix[1,2]=dtix[2,1]=dtivec[4]
1302
+ dtinp[i]=dtix
1303
+ return dtinp
1304
+
1305
+ def triangular_to_tensor( image, upper_triangular=True ):
1306
+ """
1307
+ convert triangular tensor image to a full tensor form (in numpy)
1308
+
1309
+ image : antsImage holding dti in either upper or lower triangular format
1310
+
1311
+ upper_triangular: boolean
1312
+
1313
+ Note
1314
+ --------
1315
+ see get_dti function for more details
1316
+ """
1317
+ reoind = np.array([0,1,3,2,4,5]) # arrays are faster than lists
1318
+ it = np.ndindex( image.shape )
1319
+ yyind=2
1320
+ xzind=3
1321
+ if upper_triangular:
1322
+ yyind=3
1323
+ xzind=2
1324
+ # copy these data into a tensor
1325
+ dtinp = np.zeros(image.shape + (3,3), dtype=float)
1326
+ dtix = np.zeros((3,3), dtype=float)
1327
+ it = np.ndindex( image.shape )
1328
+ dtiut = image.numpy()
1329
+ for i in it:
1330
+ dtivec = dtiut[i] # in ANTs - we have: [xx,xy,xz,yy,yz,zz]
1331
+ dtix[0,0]=dtivec[0]
1332
+ dtix[1,1]=dtivec[yyind] # 2 for LT
1333
+ dtix[2,2]=dtivec[5]
1334
+ dtix[0,1]=dtix[1,0]=dtivec[1]
1335
+ dtix[0,2]=dtix[2,0]=dtivec[xzind] # 3 for LT
1336
+ dtix[1,2]=dtix[2,1]=dtivec[4]
1337
+ dtinp[i]=dtix
1338
+ return dtinp
1339
+
1340
+
1341
+ def dti_numpy_to_image( reference_image, tensorarray, upper_triangular=True):
1342
+ """
1343
+ convert numpy DTI data to antsImage
1344
+
1345
+ reference_image : antsImage defining physical space (3D)
1346
+
1347
+ tensorarray : numpy array X,Y,Z,3,3 shape
1348
+
1349
+ upper_triangular: boolean otherwise use lower triangular coding
1350
+
1351
+ Returns
1352
+ -------
1353
+ ANTsImage
1354
+
1355
+ Notes
1356
+ -----
1357
+ DiPy returns lower triangular form but ANTs expects upper triangular.
1358
+ Here, we default to the ANTs standard but could generalize in the future
1359
+ because not much here depends on ANTs standards of tensor data.
1360
+ ANTs xx,xy,xz,yy,yz,zz
1361
+ DiPy Dxx, Dxy, Dyy, Dxz, Dyz, Dzz
1362
+
1363
+ """
1364
+ dtiut = np.zeros(reference_image.shape + (6,), dtype=float)
1365
+ dtivec = np.zeros(6, dtype=float)
1366
+ it = np.ndindex( reference_image.shape )
1367
+ yyind=2
1368
+ xzind=3
1369
+ if upper_triangular:
1370
+ yyind=3
1371
+ xzind=2
1372
+ for i in it:
1373
+ dtix = tensorarray[i] # in ANTs - we have: [xx,xy,xz,yy,yz,zz]
1374
+ dtivec[0]=dtix[0,0]
1375
+ dtivec[yyind]=dtix[1,1] # 2 for LT
1376
+ dtivec[5]=dtix[2,2]
1377
+ dtivec[1]=dtix[0,1]
1378
+ dtivec[xzind]=dtix[2,0] # 3 for LT
1379
+ dtivec[4]=dtix[1,2]
1380
+ dtiut[i]=dtivec
1381
+ dtiAnts = ants.from_numpy( dtiut, has_components=True )
1382
+ ants.copy_image_info( reference_image, dtiAnts )
1383
+ return dtiAnts
1384
+
1385
+ def transform_and_reorient_dti( fixed, moving_dti, composite_transform, py_based=True, verbose=False, **kwargs):
1386
+ """
1387
+ apply a transform to DTI in the style of ants.apply_transforms. this function
1388
+ expects a pre-computed composite transform which it will use to reorient
1389
+ the DTI using preservation of principle directions.
1390
+
1391
+ fixed : antsImage reference space
1392
+
1393
+ moving_dti : antsImage DTI in upper triangular format
1394
+
1395
+ composite_transform : should be a composition of all transforms to be applied stored on disk ( a filename ) ... might change this in the future.
1396
+
1397
+ py_based : boolean
1398
+
1399
+ verbose : boolean
1400
+
1401
+ **kwargs : passed to ants.apply_transforms
1402
+
1403
+ """
1404
+ if moving_dti.dimension != 3:
1405
+ raise ValueError('moving image should have 3 dimensions')
1406
+ if moving_dti.components != 6:
1407
+ raise ValueError('moving image should have 6 components')
1408
+ # now apply the transform to the template
1409
+ # 1. transform the tensor components
1410
+ dtsplit = moving_dti.split_channels()
1411
+ dtiw = []
1412
+ for k in range(len(dtsplit)):
1413
+ dtiw.append( ants.apply_transforms( fixed, dtsplit[k], composite_transform ) )
1414
+ dtiw=ants.merge_channels(dtiw)
1415
+ if verbose:
1416
+ print("reorient tensors locally: compose and get reo image")
1417
+ locrot = ants.deformation_gradient( ants.image_read(composite_transform),
1418
+ to_rotation = True, py_based=py_based )
1419
+ rebaser = np.dot( np.transpose( fixed.direction ), moving_dti.direction )
1420
+ if verbose:
1421
+ print("convert UT to full tensor")
1422
+ dtiw2tensor = triangular_to_tensor( dtiw )
1423
+ if verbose:
1424
+ print("rebase them to new space via iterator")
1425
+ it = np.ndindex( fixed.shape )
1426
+ for i in it:
1427
+ # direction * dt * direction.transpose();
1428
+ mmm = dtiw2tensor[i]
1429
+ # transform rebase
1430
+ locrotx = np.reshape( locrot[i], [3,3] )
1431
+ mmm = np.dot( mmm, np.transpose( locrotx ) )
1432
+ mmm = np.dot( locrotx, mmm )
1433
+ # physical space rebase
1434
+ mmm = np.dot( mmm, np.transpose( rebaser ) )
1435
+ mmm = np.dot( rebaser, mmm )
1436
+ dtiw2tensor[i] = mmm
1437
+ if verbose:
1438
+ print("done with rebasing")
1439
+ return dti_numpy_to_image( fixed, dtiw2tensor )
1440
+
1228
1441
 
1229
1442
  def dti_reg(
1230
1443
  image,
@@ -1438,7 +1651,9 @@ def dti_reg(
1438
1651
  if verbose:
1439
1652
  print("Reorient bvecs")
1440
1653
  if bvecs is not None:
1441
- bvecs = bvec_reorientation( motion_parameters, bvecs )
1654
+ # direction = target->GetDirection().GetTranspose() * img_mov->GetDirection().GetVnlMatrix();
1655
+ rebase = np.dot( np.transpose( avg_b0.direction ), ab0.direction )
1656
+ bvecs = bvec_reorientation( motion_parameters, bvecs, rebase )
1442
1657
 
1443
1658
  if remove_it:
1444
1659
  import shutil
@@ -1619,7 +1834,7 @@ def mc_reg(
1619
1834
  }
1620
1835
 
1621
1836
 
1622
- def get_data( name=None, force_download=False, version=11, target_extension='.csv' ):
1837
+ def get_data( name=None, force_download=False, version=13, target_extension='.csv' ):
1623
1838
  """
1624
1839
  Get ANTsPyMM data filename
1625
1840
 
@@ -2543,7 +2758,7 @@ def joint_dti_recon(
2543
2758
 
2544
2759
  t1w : antsimage t1w neuroimage (brain-extracted)
2545
2760
 
2546
- brain_mask : mask for the DWI - just 3D
2761
+ brain_mask : mask for the DWI - just 3D - provided brain mask should be in reference_B0 space
2547
2762
 
2548
2763
  motion_correct : None Rigid or SyN
2549
2764
 
@@ -2705,7 +2920,9 @@ def joint_dti_recon(
2705
2920
  fa_SNR = mask_snr( reconFA, bgmask, fgmask, bias_correct=False )
2706
2921
  fa_evr = antspyt1w.patch_eigenvalue_ratio( reconFA, 512, [16,16,16], evdepth = 0.9, mask=recon_LR_dewarp['dwi_mask'] )
2707
2922
 
2923
+ dti_itself = get_dti( reconFA, recon_LR_dewarp['tensormodel'], return_image=True )
2708
2924
  return {
2925
+ 'dti': dti_itself,
2709
2926
  'recon_fa':reconFA,
2710
2927
  'recon_fa_summary':df_FA_JHU_ORRL_bfwide,
2711
2928
  'recon_md':reconMD,
@@ -4211,11 +4428,15 @@ def write_bvals_bvecs(bvals, bvecs, prefix ):
4211
4428
  N = len(bvals)
4212
4429
  fname = prefix + '.bval'
4213
4430
  fmt = _VAL_FMT * N + '\n'
4214
- open(fname, 'wt').write(fmt % bvals)
4431
+ myfile = open(fname, 'wt')
4432
+ myfile.write(fmt % bvals)
4433
+ myfile.close()
4215
4434
  fname = prefix + '.bvec'
4216
4435
  bvf = open(fname, 'wt')
4217
4436
  for dim_vals in bvecs.T:
4218
4437
  bvf.write(fmt % tuple(dim_vals))
4438
+ bvf.close()
4439
+
4219
4440
 
4220
4441
  def crop_mcimage( x, mask, padder=None ):
4221
4442
  """
@@ -4258,6 +4479,8 @@ def mm(
4258
4479
  do_tractography = False,
4259
4480
  do_kk = False,
4260
4481
  do_normalization = None,
4482
+ group_template = None,
4483
+ group_transform = None,
4261
4484
  target_range = [0,1],
4262
4485
  dti_motion_correct = 'Rigid',
4263
4486
  dti_denoise = False,
@@ -4296,6 +4519,10 @@ def mm(
4296
4519
 
4297
4520
  do_normalization : template transformation if available
4298
4521
 
4522
+ group_template : optional reference template corresponding to the group_transform
4523
+
4524
+ group_transform : optional transforms corresponding to the group_template
4525
+
4299
4526
  target_range : 2-element tuple
4300
4527
  a tuple or array defining the (min, max) of the input image
4301
4528
  (e.g., [-127.5, 127.5] or [0,1]). Output images will be scaled back to original
@@ -4331,6 +4558,9 @@ def mm(
4331
4558
  JHU_atlas = mm_read( JHU_atlasfn ) # Read in JHU atlas
4332
4559
  JHU_labels = mm_read( JHU_labelsfn ) # Read in JHU labels
4333
4560
  template = mm_read( templatefn ) # Read in template
4561
+ if group_template is None:
4562
+ group_template = template
4563
+ group_transform = do_normalization['fwdtransforms']
4334
4564
  #####################
4335
4565
  # T1 hierarchical #
4336
4566
  #####################
@@ -4354,6 +4584,7 @@ def mm(
4354
4584
  normalization_dict = {
4355
4585
  'kk_norm': None,
4356
4586
  'NM_norm' : None,
4587
+ 'DTI_norm': None,
4357
4588
  'FA_norm' : None,
4358
4589
  'MD_norm' : None,
4359
4590
  'alff_norm' : None,
@@ -4560,24 +4791,32 @@ def mm(
4560
4791
  # t1reg = ants.registration( template, hier['brain_n4_dnz'], "antsRegistrationSyNQuickRepro[s]")
4561
4792
  t1reg = do_normalization
4562
4793
  if do_kk:
4563
- normalization_dict['kk_norm'] = ants.apply_transforms( template, output_dict['kk']['thickness_image'], t1reg['fwdtransforms'])
4794
+ normalization_dict['kk_norm'] = ants.apply_transforms( group_template, output_dict['kk']['thickness_image'], group_transform )
4564
4795
  if output_dict['DTI'] is not None:
4565
4796
  mydti = output_dict['DTI']
4566
4797
  dtirig = ants.registration( hier['brain_n4_dnz'], mydti['recon_fa'], 'Rigid' )
4567
- normalization_dict['MD_norm'] = ants.apply_transforms( template, mydti['recon_md'],t1reg['fwdtransforms']+dtirig['fwdtransforms'] )
4568
- normalization_dict['FA_norm'] = ants.apply_transforms( template, mydti['recon_fa'],t1reg['fwdtransforms']+dtirig['fwdtransforms'] )
4798
+ normalization_dict['MD_norm'] = ants.apply_transforms( group_template, mydti['recon_md'],group_transform+dtirig['fwdtransforms'] )
4799
+ normalization_dict['FA_norm'] = ants.apply_transforms( group_template, mydti['recon_fa'],group_transform+dtirig['fwdtransforms'] )
4800
+ output_directory = tempfile.mkdtemp()
4801
+ comptx = ants.apply_transforms( group_template, group_template,
4802
+ group_transform+dtirig['fwdtransforms'],
4803
+ compose = output_directory + '/xxx' )
4804
+ normalization_dict['DTI_norm'] = transform_and_reorient_dti(
4805
+ group_template, mydti['dti'], comptx, py_based=True, verbose=True )
4806
+ import shutil
4807
+ shutil.rmtree(output_directory, ignore_errors=True )
4569
4808
  if output_dict['rsf'] is not None:
4570
4809
  rsfpro = output_dict['rsf']
4571
4810
  rsfrig = ants.registration( hier['brain_n4_dnz'], rsfpro['meanBold'], 'Rigid' )
4572
4811
  for netid in mynets:
4573
4812
  rsfkey = netid + "_norm"
4574
4813
  normalization_dict[rsfkey] = ants.apply_transforms(
4575
- template, rsfpro[netid],
4576
- t1reg['fwdtransforms']+rsfrig['fwdtransforms'] )
4814
+ group_template, rsfpro[netid],
4815
+ group_transform+rsfrig['fwdtransforms'] )
4577
4816
  if nm_image_list is not None:
4578
4817
  nmpro = output_dict['NM']
4579
4818
  nmrig = nmpro['t1_to_NM_transform'] # this is an inverse tx
4580
- normalization_dict['NM_norm'] = ants.apply_transforms( template, nmpro['NM_avg'],t1reg['fwdtransforms']+nmrig,
4819
+ normalization_dict['NM_norm'] = ants.apply_transforms( group_template, nmpro['NM_avg'], group_transform+nmrig,
4581
4820
  whichtoinvert=[False,False,True])
4582
4821
 
4583
4822
  if verbose:
@@ -4585,7 +4824,7 @@ def mm(
4585
4824
  return output_dict, normalization_dict
4586
4825
 
4587
4826
 
4588
- def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4827
+ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_', verbose=False ):
4589
4828
  """
4590
4829
  write the tabular and normalization output of the mm function
4591
4830
 
@@ -4602,6 +4841,8 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4602
4841
 
4603
4842
  separator : string or character separator for filenames
4604
4843
 
4844
+ verbose : boolean
4845
+
4605
4846
  Returns
4606
4847
  ---------
4607
4848
 
@@ -4635,6 +4876,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4635
4876
  if mm['DTI'] is not None:
4636
4877
  mydti = mm['DTI']
4637
4878
  myop = output_prefix + separator
4879
+ ants.image_write( mydti['dti'], myop + 'dti.nii.gz' )
4638
4880
  write_bvals_bvecs( mydti['bval_LR'], mydti['bvec_LR'], myop + 'reoriented' )
4639
4881
  image_write_with_thumbnail( mydti['dwi_LR_dewarped'], myop + 'dwi.nii.gz' )
4640
4882
  image_write_with_thumbnail( mydti['dtrecon_LR_dewarp']['RGB'] , myop + 'DTIRGB.nii.gz' )
@@ -4745,6 +4987,8 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4745
4987
  mm_wide['dti_FD_mean'] = mm_wide['dti_FD_max'] = 'NA'
4746
4988
  mmwidefn = output_prefix + separator + 'mmwide.csv'
4747
4989
  mm_wide.to_csv( mmwidefn )
4990
+ if verbose:
4991
+ print( output_prefix + " write_mm done." )
4748
4992
  return
4749
4993
 
4750
4994
 
@@ -4887,7 +5131,8 @@ def mm_nrg(
4887
5131
  'fwdtransforms': [ regout+'1Warp.nii.gz', regout+'0GenericAffine.mat'],
4888
5132
  'invtransforms': [ regout+'0GenericAffine.mat', regout+'1InverseWarp.nii.gz'] }
4889
5133
  if verbose:
4890
- print( "REGISTRATION EXISTENCE: " +
5134
+ print( "-<REGISTRATION EXISTENCE>-: \n" +
5135
+ "NAMING: " + regout+'0GenericAffine.mat' + " \n " +
4891
5136
  str(exists( templateTx['fwdtransforms'][0])) + " " +
4892
5137
  str(exists( templateTx['fwdtransforms'][1])) + " " +
4893
5138
  str(exists( templateTx['invtransforms'][0])) + " " +
@@ -5239,14 +5484,14 @@ def mm_nrg(
5239
5484
  mydti = tabPro['DTI']
5240
5485
  if visualize:
5241
5486
  maxslice = np.min( [21, mydti['recon_fa'] ] )
5242
- ants.plot( mydti['recon_fa'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA (supposed to be better)', filename=mymm+mysep+"FAbetter.png" )
5487
+ ants.plot( mydti['recon_fa'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA', filename=mymm+mysep+"FAbetter.png" )
5243
5488
  ants.plot( mydti['recon_fa'], mydti['jhu_labels'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA + JHU', filename=mymm+mysep+"FAJHU.png" )
5244
5489
  ants.plot( mydti['recon_md'], axis=2, nslices=maxslice, ncol=7, crop=True, title='MD', filename=mymm+mysep+"MD.png" )
5245
5490
  if dowrite:
5246
- write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep )
5491
+ write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep, verbose=True )
5247
5492
  for mykey in normPro.keys():
5248
5493
  if normPro[mykey] is not None:
5249
- if visualize:
5494
+ if visualize and normPro[mykey].components == 1:
5250
5495
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
5251
5496
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
5252
5497
  return
@@ -5266,7 +5511,11 @@ def mm_csv(
5266
5511
  srmodel_DTI = False, # optional - will add a great deal of time
5267
5512
  dti_motion_correct = 'SyN',
5268
5513
  dti_denoise = True,
5269
- nrg_modality_list = None
5514
+ nrg_modality_list = None,
5515
+ normalization_template = None,
5516
+ normalization_template_output = None,
5517
+ normalization_template_transform_type = "antsRegistrationSyNRepro[s]",
5518
+ normalization_template_spacing=None
5270
5519
  ):
5271
5520
  """
5272
5521
  too dangerous to document ... use with care.
@@ -5326,6 +5575,17 @@ def mm_csv(
5326
5575
 
5327
5576
  nrg_modality_list : optional; defaults to None; use to focus on a given modality
5328
5577
 
5578
+ normalization_template : optional; defaults to None; if present, all images will
5579
+ be deformed into this space and the deformation will be stored with an extension
5580
+ related to this variable. this should be a brain extracted T1w image.
5581
+
5582
+ normalization_template_output : optional string; defaults to None; naming for the
5583
+ normalization_template outputs which will be in the T1w directory.
5584
+
5585
+ normalization_template_transform_type : optional string transform type passed to ants.registration
5586
+
5587
+ normalization_template_spacing : 3-tuple controlling the resolution at which registration is computed
5588
+
5329
5589
  Returns
5330
5590
  ---------
5331
5591
 
@@ -5411,7 +5671,6 @@ def mm_csv(
5411
5671
  iid2=iid+"_"+t1iid
5412
5672
  myoutputPrefix = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + locmod + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + locmod + mysep + iid2
5413
5673
  if verbose:
5414
- print("VERBOSE in docsamson")
5415
5674
  print( locmod )
5416
5675
  print( myimgsInput )
5417
5676
  print( myoutputPrefix )
@@ -5429,15 +5688,23 @@ def mm_csv(
5429
5688
  t1 = mm_read( t1fn, modality='T1w' )
5430
5689
  hierfn = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + "T1wHierarchical" + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + "T1wHierarchical" + mysep + iid + mysep
5431
5690
  hierfnSR = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + "T1wHierarchicalSR" + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + "T1wHierarchicalSR" + mysep + iid + mysep
5432
- hierfntest = hierfn + 'snseg.csv'
5691
+ hierfntest = hierfn + 'cerebellum.csv'
5433
5692
  if verbose:
5434
5693
  print( hierfntest )
5435
- regout = hierfn + "syn"
5694
+ regout = re.sub("T1wHierarchical","T1w",hierfn) + "syn"
5436
5695
  templateTx = {
5437
5696
  'fwdtransforms': [ regout+'1Warp.nii.gz', regout+'0GenericAffine.mat'],
5438
5697
  'invtransforms': [ regout+'0GenericAffine.mat', regout+'1InverseWarp.nii.gz'] }
5698
+ groupTx = None
5699
+ if normalization_template_output is not None:
5700
+ normout = re.sub("T1wHierarchical","T1w",hierfn) + normalization_template_output
5701
+ templateNormTx = {
5702
+ 'fwdtransforms': [ normout+'1Warp.nii.gz', normout+'0GenericAffine.mat'],
5703
+ 'invtransforms': [ normout+'0GenericAffine.mat', normout+'1InverseWarp.nii.gz'] }
5704
+ groupTx = templateNormTx['fwdtransforms']
5439
5705
  if verbose:
5440
- print( "REGISTRATION EXISTENCE: " +
5706
+ print( "-<REGISTRATION EXISTENCE>-: \n" +
5707
+ "NAMING: " + regout+'0GenericAffine.mat' + " \n " +
5441
5708
  str(exists( templateTx['fwdtransforms'][0])) + " " +
5442
5709
  str(exists( templateTx['fwdtransforms'][1])) + " " +
5443
5710
  str(exists( templateTx['invtransforms'][0])) + " " +
@@ -5498,6 +5765,43 @@ def mm_csv(
5498
5765
  if not testloop:
5499
5766
  t1imgbrn = hier['brain_n4_dnz']
5500
5767
  t1atropos = hier['dkt_parc']['tissue_segmentation']
5768
+
5769
+ if not exists( regout + "logjacobian.nii.gz" ) or not exists( regout+'1Warp.nii.gz' ):
5770
+ if verbose:
5771
+ print('start t1 registration')
5772
+ ex_path = os.path.expanduser( "~/.antspyt1w/" )
5773
+ templatefn = ex_path + 'CIT168_T1w_700um_pad_adni.nii.gz'
5774
+ template = mm_read( templatefn )
5775
+ template = ants.resample_image( template, [1,1,1], use_voxels=False )
5776
+ t1reg = ants.registration( template,
5777
+ hier['brain_n4_dnz'],
5778
+ "antsRegistrationSyNQuickRepro[s]", outprefix = regout, verbose=False )
5779
+ myjac = ants.create_jacobian_determinant_image( template,
5780
+ t1reg['fwdtransforms'][0], do_log=True, geom=True )
5781
+ image_write_with_thumbnail( myjac, regout + "logjacobian.nii.gz", thumb=False )
5782
+ if visualize:
5783
+ ants.plot( ants.iMath(t1reg['warpedmovout'],"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='warped to template', filename=regout+"totemplate.png" )
5784
+ ants.plot( ants.iMath(myjac,"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='jacobian', filename=regout+"jacobian.png" )
5785
+
5786
+ if normalization_template_output is not None and normalization_template is not None:
5787
+ if verbose:
5788
+ print("begin group template registration")
5789
+ if not exists( normout+'0GenericAffine.mat' ):
5790
+ if normalization_template_spacing is not None:
5791
+ normalization_template_rr=ants.resample_image(normalization_template,normalization_template_spacing)
5792
+ else:
5793
+ normalization_template_rr=normalization_template
5794
+ greg = ants.registration(
5795
+ normalization_template_rr,
5796
+ hier['brain_n4_dnz'],
5797
+ normalization_template_transform_type,
5798
+ outprefix = normout, verbose=False )
5799
+ if verbose:
5800
+ print("end group template registration")
5801
+ else:
5802
+ if verbose:
5803
+ print("group template registration already done")
5804
+
5501
5805
  # loop over modalities and then unique image IDs
5502
5806
  # we treat NM in a "special" way -- aggregating repeats
5503
5807
  # other modalities (beyond T1) are treated individually
@@ -5562,6 +5866,8 @@ def mm_csv(
5562
5866
  do_tractography=False,
5563
5867
  do_kk=False,
5564
5868
  do_normalization=templateTx,
5869
+ group_template = normalization_template,
5870
+ group_transform = groupTx,
5565
5871
  test_run=test_run,
5566
5872
  verbose=True )
5567
5873
  if not test_run:
@@ -5601,21 +5907,6 @@ def mm_csv(
5601
5907
  img = mm_read( myimg )
5602
5908
  ishapelen = len( img.shape )
5603
5909
  if mymod == 'T1w' and ishapelen == 3: # for a real run, set to True
5604
- if not exists( regout + "logjacobian.nii.gz" ) or not exists( regout+'1Warp.nii.gz' ):
5605
- if verbose:
5606
- print('start t1 registration')
5607
- ex_path = os.path.expanduser( "~/.antspyt1w/" )
5608
- templatefn = ex_path + 'CIT168_T1w_700um_pad_adni.nii.gz'
5609
- template = mm_read( templatefn )
5610
- template = ants.resample_image( template, [1,1,1], use_voxels=False )
5611
- t1reg = ants.registration( template, hier['brain_n4_dnz'],
5612
- "antsRegistrationSyNQuickRepro[s]", outprefix = regout, verbose=False )
5613
- myjac = ants.create_jacobian_determinant_image( template,
5614
- t1reg['fwdtransforms'][0], do_log=True, geom=True )
5615
- image_write_with_thumbnail( myjac, regout + "logjacobian.nii.gz", thumb=False )
5616
- if visualize:
5617
- ants.plot( ants.iMath(t1reg['warpedmovout'],"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='warped to template', filename=regout+"totemplate.png" )
5618
- ants.plot( ants.iMath(myjac,"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='jacobian', filename=regout+"jacobian.png" )
5619
5910
  if not exists( mymm + mysep + "kk_norm.nii.gz" ):
5620
5911
  dowrite=True
5621
5912
  if verbose:
@@ -5625,6 +5916,8 @@ def mm_csv(
5625
5916
  do_tractography=False,
5626
5917
  do_kk=True,
5627
5918
  do_normalization=templateTx,
5919
+ group_template = normalization_template,
5920
+ group_transform = groupTx,
5628
5921
  test_run=test_run,
5629
5922
  verbose=True )
5630
5923
  if visualize:
@@ -5640,6 +5933,8 @@ def mm_csv(
5640
5933
  do_tractography=False,
5641
5934
  do_kk=False,
5642
5935
  do_normalization=templateTx,
5936
+ group_template = normalization_template,
5937
+ group_transform = groupTx,
5643
5938
  test_run=test_run,
5644
5939
  verbose=True )
5645
5940
  if visualize:
@@ -5662,6 +5957,8 @@ def mm_csv(
5662
5957
  do_tractography=False,
5663
5958
  do_kk=False,
5664
5959
  do_normalization=templateTx,
5960
+ group_template = normalization_template,
5961
+ group_transform = groupTx,
5665
5962
  test_run=test_run,
5666
5963
  verbose=True )
5667
5964
  if tabPro['rsf'] is not None and visualize:
@@ -5723,6 +6020,8 @@ def mm_csv(
5723
6020
  do_tractography=not test_run,
5724
6021
  do_kk=False,
5725
6022
  do_normalization=templateTx,
6023
+ group_template = normalization_template,
6024
+ group_transform = groupTx,
5726
6025
  dti_motion_correct = dti_motion_correct,
5727
6026
  dti_denoise = dti_denoise,
5728
6027
  test_run=test_run,
@@ -5736,7 +6035,7 @@ def mm_csv(
5736
6035
  if dowrite:
5737
6036
  write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep )
5738
6037
  for mykey in normPro.keys():
5739
- if normPro[mykey] is not None:
6038
+ if normPro[mykey] is not None and normPro[mykey].components == 1:
5740
6039
  if visualize:
5741
6040
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
5742
6041
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
@@ -6795,7 +7094,7 @@ def blind_image_assessment(
6795
7094
  viz_filename=None,
6796
7095
  title=False,
6797
7096
  pull_rank=False,
6798
- resample='max',
7097
+ resample=None,
6799
7098
  verbose=False
6800
7099
  ):
6801
7100
  """
@@ -6852,7 +7151,7 @@ def blind_image_assessment(
6852
7151
  json_name = re.sub(".nii.gz",".json",image_filename)
6853
7152
  if exists( json_name ):
6854
7153
  with open(json_name, 'r') as fcc_file:
6855
- mymeta = json.load(fcc_file)
7154
+ mymeta = json.load(fcc_file, strict=False)
6856
7155
  if verbose:
6857
7156
  print(json.dumps(mymeta, indent=4))
6858
7157
  mystem=Path( image ).stem
@@ -6895,16 +7194,30 @@ def blind_image_assessment(
6895
7194
  else:
6896
7195
  image_compare = ants.image_clone( image_b0 )
6897
7196
  image = ants.iMath( image, 'TruncateIntensity',0.01,0.995)
7197
+ minspc = np.min(ants.get_spacing(image))
7198
+ maxspc = np.max(ants.get_spacing(image))
6898
7199
  if resample is not None:
6899
7200
  if resample == 'min':
6900
- newspc = np.repeat( np.min(ants.get_spacing(image)), 3 )
7201
+ if minspc < 1e-12:
7202
+ minspc = np.max(ants.get_spacing(image))
7203
+ newspc = np.repeat( minspc, 3 )
6901
7204
  elif resample == 'max':
6902
- newspc = np.repeat( np.max(ants.get_spacing(image)), 3 )
7205
+ newspc = np.repeat( maxspc, 3 )
6903
7206
  else:
6904
7207
  newspc = np.repeat( resample, 3 )
6905
7208
  image = ants.resample_image( image, newspc )
6906
7209
  image_compare = ants.resample_image( image_compare, newspc )
7210
+ else:
7211
+ # check for spc close to zero
7212
+ spc = list(ants.get_spacing(image))
7213
+ for spck in range(len(spc)):
7214
+ if spc[spck] < 1e-12:
7215
+ spc[spck]=1
7216
+ ants.set_spacing( image, spc )
7217
+ ants.set_spacing( image_compare, spc )
6907
7218
  # if "NM2DMT" in image_filename or "FIXME" in image_filename or "SPECT" in image_filename or "UNKNOWN" in image_filename:
7219
+ minspc = np.min(ants.get_spacing(image))
7220
+ maxspc = np.max(ants.get_spacing(image))
6908
7221
  msk = ants.threshold_image( ants.iMath(image,'Normalize'), 0.15, 1.0 )
6909
7222
  # else:
6910
7223
  # msk = ants.get_mask( image )
@@ -6941,8 +7254,17 @@ def blind_image_assessment(
6941
7254
  imagereflect = ants.reflect_image(image, axis=0)
6942
7255
  asym_err = ( image - imagereflect ).abs().mean()
6943
7256
  # estimate noise by center cropping, denoizing and taking magnitude of difference
6944
- mycc = antspyt1w.special_crop( image,
6945
- ants.get_center_of_mass( msk *0 + 1 ), patch_shape )
7257
+ nocrop=False
7258
+ if image.dimension == 3:
7259
+ if image.shape[2] == 1:
7260
+ nocrop=True
7261
+ if maxspc/minspc > 10:
7262
+ nocrop=True
7263
+ if nocrop:
7264
+ mycc = ants.image_clone( image )
7265
+ else:
7266
+ mycc = antspyt1w.special_crop( image,
7267
+ ants.get_center_of_mass( msk *0 + 1 ), patch_shape )
6946
7268
  myccd = ants.denoise_image( mycc, p=2,r=2,noise_model='Gaussian' )
6947
7269
  noizlevel = ( mycc - myccd ).abs().mean()
6948
7270
  # ants.plot_ortho( image, crop=False, filename=viz_filename, flat=True, xyz_lines=False, orient_labels=False, xyz_pad=0 )
@@ -6959,7 +7281,10 @@ def blind_image_assessment(
6959
7281
  cnrref = ( fgmean - bgmean ) / bgstd
6960
7282
  psnrref = antspynet.psnr( image_compare, image )
6961
7283
  ssimref = antspynet.ssim( image_compare, image )
6962
- mymi = ants.image_mutual_information( image_compare, image )
7284
+ if nocrop:
7285
+ mymi = math.inf
7286
+ else:
7287
+ mymi = ants.image_mutual_information( image_compare, image )
6963
7288
  mriseries='NA'
6964
7289
  mrimfg='NA'
6965
7290
  mrimodel='NA'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: antspymm
3
- Version: 0.9.9
3
+ Version: 1.0.0
4
4
  Summary: multi-channel/time-series medical image processing with antspyx
5
5
  Home-page: https://github.com/stnava/ANTsPyMM
6
6
  Author: Avants, Gosselin, Tustison, Reardon
@@ -11,7 +11,7 @@ License-File: LICENSE
11
11
  Requires-Dist: h5py (>=2.10.0)
12
12
  Requires-Dist: numpy (>=1.19.4)
13
13
  Requires-Dist: pandas (>=1.0.1)
14
- Requires-Dist: antspyx (>=0.2.7)
14
+ Requires-Dist: antspyx (>=0.4.1)
15
15
  Requires-Dist: antspyt1w (>=0.2.3)
16
16
  Requires-Dist: pathlib
17
17
  Requires-Dist: dipy
@@ -0,0 +1,7 @@
1
+ antspymm/__init__.py,sha256=BTNN_iisXoz2on4gVcAQWFB_qJYDM-yqZOJU1xho9sw,2931
2
+ antspymm/mm.py,sha256=UMAxi91xK-rFUQT6StQIAkLKyphGMpM_Jy0pFGiY_8E,308235
3
+ antspymm-1.0.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
+ antspymm-1.0.0.dist-info/METADATA,sha256=RjWizo9kiWyCCLDYjdP2LLDlcqDT0zmqU6Q8H4x8Zh8,10717
5
+ antspymm-1.0.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
6
+ antspymm-1.0.0.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
+ antspymm-1.0.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- antspymm/__init__.py,sha256=NeiBXRH7onjAUWMh3Pgpr-c6D8ZoR9xrdvDOUmV1eqY,2792
2
- antspymm/mm.py,sha256=V4aH_G8VExOBwUyEf4PcMwilWOPRdMyuby7Pxtt-33I,295165
3
- antspymm-0.9.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-0.9.9.dist-info/METADATA,sha256=PRGC4oEbud_9yi3Ls_RAC5t6VO76vpFy384FVHdC0I8,10717
5
- antspymm-0.9.9.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
6
- antspymm-0.9.9.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-0.9.9.dist-info/RECORD,,