antspymm 0.9.7__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
@@ -84,3 +88,5 @@ from .mm import novelty_detection_quantile
84
88
  from .mm import generate_mm_dataframe
85
89
  from .mm import collect_blind_qc_by_modality
86
90
  from .mm import get_valid_modalities
91
+ from .mm import study_dataframe_from_matched_dataframe
92
+ from .mm import merge_wides_to_study_dataframe
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',
@@ -78,6 +79,8 @@ __all__ = ['version',
78
79
  'novelty_detection_loop',
79
80
  'novelty_detection_quantile',
80
81
  'generate_mm_dataframe',
82
+ 'study_dataframe_from_matched_dataframe',
83
+ 'merge_wides_to_study_dataframe',
81
84
  'wmh']
82
85
 
83
86
  from pathlib import Path
@@ -419,7 +422,7 @@ def collect_blind_qc_by_modality( modality_path, set_index_to_fn=True ):
419
422
 
420
423
  def outlierness_by_modality( qcdf, uid='fn', outlier_columns = ['noise', 'snr', 'cnr', 'psnr', 'ssim', 'mi','reflection_err', 'EVR', 'msk_vol'], verbose=False ):
421
424
  """
422
- 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.
423
426
 
424
427
  Args:
425
428
  - qcdf: (Pandas DataFrame) Dataframe containing columns with outlier information for each modality.
@@ -450,6 +453,9 @@ def outlierness_by_modality( qcdf, uid='fn', outlier_columns = ['noise', 'snr',
450
453
  lof = LocalOutlierFactor()
451
454
  locsel = qcdfout["modality"] == mod
452
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)
453
459
  if rr.shape[0] > 1:
454
460
  if verbose:
455
461
  print(mod)
@@ -494,6 +500,98 @@ def nrg_format_path( projectID, subjectID, date, modality, imageID, separator='-
494
500
  thefilename = str(projectID) + separator + str(subjectID) + separator + str(date) + separator + str(modality) + separator + str(imageID)
495
501
  return os.path.join( thedirectory, thefilename )
496
502
 
503
+
504
+ def study_dataframe_from_matched_dataframe( matched_dataframe, rootdir, outputdir, verbose=False ):
505
+ """
506
+ converts the output of antspymm.match_modalities dataframe (one row) to that needed for a study-driving dataframe for input to mm_csv
507
+
508
+ matched_dataframe : output of antspymm.match_modalities
509
+
510
+ rootdir : location for the input data root folder (in e.g. NRG format)
511
+
512
+ outputdir : location for the output data
513
+
514
+ verbose : boolean
515
+ """
516
+ iext='.nii.gz'
517
+ from os.path import exists
518
+ musthavecols = ['projectID', 'subjectID','date','imageID','fn']
519
+ for k in range(len(musthavecols)):
520
+ if not musthavecols[k] in matched_dataframe.keys():
521
+ raise ValueError('matched_dataframe is missing column ' +musthavecols[k] + ' in study_dataframe_from_qc_dataframe' )
522
+ csvrow=matched_dataframe.dropna(axis=1)
523
+ pid=str(csvrow['projectID'].iloc[0] )
524
+ sid=str(csvrow['subjectID'].iloc[0] )
525
+ dt=str(csvrow['date'].iloc[0])
526
+ iid=str(csvrow['imageID'].iloc[0])
527
+ nrgt1fn=os.path.join( rootdir, pid, sid, dt, 'T1w', iid, str(csvrow['fn'].iloc[0]+iext) )
528
+ if not exists( nrgt1fn ):
529
+ raise ValueError("T1 " + nrgt1fn + " does not exist in study_dataframe_from_qc_dataframe")
530
+ flList=[]
531
+ dtList=[]
532
+ rsfList=[]
533
+ nmList=[]
534
+ if 'flairfn' in csvrow.keys():
535
+ flid=str(int(csvrow['flairid'].iloc[0]))
536
+ nrgt2fn=os.path.join( rootdir, pid, sid, dt, 'T2Flair', flid, str(csvrow['flairfn'].iloc[0]+iext) )
537
+ if exists( nrgt2fn ):
538
+ flList.append( nrgt2fn )
539
+ if 'dtfn1' in csvrow.keys():
540
+ dtid=str(int(csvrow['dtid1'].iloc[0]))
541
+ dtfn1=glob.glob(os.path.join( rootdir, pid, sid, dt, 'DTI*', dtid, str(csvrow['dtfn1'].iloc[0]+iext) ))[0]
542
+ if exists( dtfn1 ):
543
+ dtList.append( dtfn1 )
544
+ if 'dtfn2' in csvrow.keys():
545
+ dtid=str(int(csvrow['dtid2'].iloc[0]))
546
+ dtfn2=glob.glob(os.path.join(rootdir, pid, sid, dt, 'DTI*', dtid, str(csvrow['dtfn2'].iloc[0]+iext) ))[0]
547
+ if exists( dtfn2 ):
548
+ dtList.append( dtfn2 )
549
+ if 'rsffn1' in csvrow.keys():
550
+ rsid=str(int(csvrow['rsfid1'].iloc[0]))
551
+ rsfn1=glob.glob(os.path.join( rootdir, pid, sid, dt, 'rsfMRI*', rsid, str(csvrow['rsffn1'].iloc[0]+iext) ))[0]
552
+ if exists( rsfn1 ):
553
+ rsfList.append( rsfn1 )
554
+ if 'rsffn2' in csvrow.keys():
555
+ rsid=str(int(csvrow['rsfid2'].iloc[0]))
556
+ rsfn2=glob.glob(os.path.join( rootdir, pid, sid, dt, 'rsfMRI*', rsid, str(csvrow['rsffn2'].iloc[0]+iext) ))[0]
557
+ if exists( rsfn2 ):
558
+ rsfList.append( rsfn2 )
559
+ for j in range(11):
560
+ keyname="nmfn"+str(j)
561
+ keynameid="nmid"+str(j)
562
+ if keyname in csvrow.keys() and keynameid in csvrow.keys():
563
+ nmid=str(int(csvrow[keynameid].iloc[0]))
564
+ nmsearchpath=os.path.join( rootdir, pid, sid, dt, 'NM2DMT', nmid, "*"+nmid+iext)
565
+ nmfn=glob.glob( nmsearchpath )
566
+ nmfn=nmfn[0]
567
+ if exists( nmfn ):
568
+ nmList.append( nmfn )
569
+ if verbose:
570
+ print("assembled the image lists mapping to ....")
571
+ print(nrgt1fn)
572
+ print("NM")
573
+ print(nmList)
574
+ print("FLAIR")
575
+ print(flList)
576
+ print("DTI")
577
+ print(dtList)
578
+ print("rsfMRI")
579
+ print(rsfList)
580
+ studycsv = generate_mm_dataframe(
581
+ pid,
582
+ sid,
583
+ dt,
584
+ iid, # the T1 id
585
+ 'T1w',
586
+ rootdir,
587
+ outputdir,
588
+ t1_filename=nrgt1fn,
589
+ flair_filename=flList,
590
+ dti_filenames=dtList,
591
+ rsf_filenames=rsfList,
592
+ nm_filenames=nmList)
593
+ return studycsv.dropna(axis=1)
594
+
497
595
  def highest_quality_repeat(mxdfin, idvar, visitvar, qualityvar):
498
596
  """
499
597
  This function returns a subset of the input dataframe that retains only the rows
@@ -652,6 +750,16 @@ def match_modalities( qc_dataframe, unique_identifier='fn', outlier_column='ol_l
652
750
  mmdf.iloc[k, mmdf.columns.get_loc("flairfn")] = fldf['fn'][locsel].values[0]
653
751
  mmdf.iloc[k, mmdf.columns.get_loc("flairloop")] = fldf[outlier_column][locsel].values[0]
654
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]
655
763
 
656
764
  if nmdf is not None:
657
765
  locsel = nmdf['subjectIDdate'] == mmdf['subjectIDdate'].iloc[k]
@@ -1088,7 +1196,7 @@ def merge_dwi_data( img_LRdwp, bval_LR, bvec_LR, img_RLdwp, bval_RL, bvec_RL ):
1088
1196
  img_LRdwp = ants.list_to_ndimage( img_LRdwp, mimg )
1089
1197
  return img_LRdwp, bval_LR, bvec_LR
1090
1198
 
1091
- def bvec_reorientation( motion_parameters, bvecs ):
1199
+ def bvec_reorientation( motion_parameters, bvecs, rebase=None ):
1092
1200
  if motion_parameters is None:
1093
1201
  return bvecs
1094
1202
  n = len(motion_parameters)
@@ -1129,8 +1237,207 @@ def bvec_reorientation( motion_parameters, bvecs ):
1129
1237
  txparam = ants.get_ants_transform_parameters(txparam)[0:9].reshape( [3,3])
1130
1238
  Rinv = inv( txparam )
1131
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,:] )
1132
1243
  return bvecs
1133
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
+
1134
1441
 
1135
1442
  def dti_reg(
1136
1443
  image,
@@ -1344,7 +1651,9 @@ def dti_reg(
1344
1651
  if verbose:
1345
1652
  print("Reorient bvecs")
1346
1653
  if bvecs is not None:
1347
- 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 )
1348
1657
 
1349
1658
  if remove_it:
1350
1659
  import shutil
@@ -1525,7 +1834,7 @@ def mc_reg(
1525
1834
  }
1526
1835
 
1527
1836
 
1528
- 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' ):
1529
1838
  """
1530
1839
  Get ANTsPyMM data filename
1531
1840
 
@@ -2316,7 +2625,10 @@ def dipy_dti_recon(
2316
2625
  RGB = ants.merge_channels( [RGB0,RGB1,RGB2] )
2317
2626
  return tenfit, FA, MD1, RGB
2318
2627
 
2319
- gtab = gradient_table(bvals, bvecs)
2628
+ import numpy as np
2629
+ if abs(np.linalg.norm(bvecs)-1) > 0.009 and False:
2630
+ bvecs=bvecs/np.linalg.norm(bvecs, axis=1)
2631
+ gtab = gradient_table(bvals, bvecs, atol=0.1 )
2320
2632
  tenfit, FA, MD1, RGB = justthefit( gtab, fit_method, image, maskdil )
2321
2633
  if verbose:
2322
2634
  print("recon dti.TensorModel done",flush=True)
@@ -2446,7 +2758,7 @@ def joint_dti_recon(
2446
2758
 
2447
2759
  t1w : antsimage t1w neuroimage (brain-extracted)
2448
2760
 
2449
- 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
2450
2762
 
2451
2763
  motion_correct : None Rigid or SyN
2452
2764
 
@@ -2483,11 +2795,11 @@ def joint_dti_recon(
2483
2795
  return( img )
2484
2796
 
2485
2797
  img_LR = fix_dwi_shape( img_LR, bval_LR, bvec_LR )
2486
- if denoise and srmodel is None:
2798
+ if denoise :
2487
2799
  img_LR = mc_denoise( img_LR )
2488
2800
  if img_RL is not None:
2489
2801
  img_RL = fix_dwi_shape( img_RL, bval_RL, bvec_RL )
2490
- if denoise and srmodel is None:
2802
+ if denoise :
2491
2803
  img_RL = mc_denoise( img_RL )
2492
2804
 
2493
2805
  if brain_mask is not None:
@@ -2608,7 +2920,9 @@ def joint_dti_recon(
2608
2920
  fa_SNR = mask_snr( reconFA, bgmask, fgmask, bias_correct=False )
2609
2921
  fa_evr = antspyt1w.patch_eigenvalue_ratio( reconFA, 512, [16,16,16], evdepth = 0.9, mask=recon_LR_dewarp['dwi_mask'] )
2610
2922
 
2923
+ dti_itself = get_dti( reconFA, recon_LR_dewarp['tensormodel'], return_image=True )
2611
2924
  return {
2925
+ 'dti': dti_itself,
2612
2926
  'recon_fa':reconFA,
2613
2927
  'recon_fa_summary':df_FA_JHU_ORRL_bfwide,
2614
2928
  'recon_md':reconMD,
@@ -2850,7 +3164,10 @@ def dwi_deterministic_tracking(
2850
3164
  affine = dwi_img.affine
2851
3165
  if isinstance( bvals, str ) or isinstance( bvecs, str ):
2852
3166
  bvals, bvecs = read_bvals_bvecs(bvals, bvecs)
2853
- gtab = gradient_table(bvals, bvecs)
3167
+ import numpy as np
3168
+ if abs(np.linalg.norm(bvecs)-1) > 0.009 and False:
3169
+ bvecs=bvecs/np.linalg.norm(bvecs, axis=1 )
3170
+ gtab = gradient_table(bvals, bvecs, atol=0.1 )
2854
3171
  if mask is None:
2855
3172
  mask = ants.threshold_image( fa, fa_thresh, 2.0 ).iMath("GetLargestComponent")
2856
3173
  dwi_data = dwi_img.get_fdata()
@@ -3018,7 +3335,10 @@ def dwi_closest_peak_tracking(
3018
3335
  affine = dwi_img.affine
3019
3336
  if isinstance( bvals, str ) or isinstance( bvecs, str ):
3020
3337
  bvals, bvecs = read_bvals_bvecs(bvals, bvecs)
3021
- gtab = gradient_table(bvals, bvecs)
3338
+ import numpy as np
3339
+ if abs(np.linalg.norm(bvecs)-1) > 0.009 and False:
3340
+ bvecs=bvecs/np.linalg.norm(bvecs, axis=1)
3341
+ gtab = gradient_table(bvals, bvecs, atol=0.1 )
3022
3342
  if mask is None:
3023
3343
  mask = ants.threshold_image( fa, fa_thresh, 2.0 ).iMath("GetLargestComponent")
3024
3344
  dwi_data = dwi_img.get_fdata()
@@ -4003,15 +4323,18 @@ def resting_state_fmri_networks( fmri, fmri_template, t1, t1segmentation,
4003
4323
  netname = re.sub( "-", "", netname )
4004
4324
  ww = np.where( powers_areal_mni_itk['SystemName'] == networks[mynet] )[0]
4005
4325
  dfnImg = ants.make_points_image(pts2bold.iloc[ww,:3].values, bmask, radius=1).threshold_image( 1, 1e9 )
4006
- dfnmat = ants.timeseries_to_matrix( simg, ants.threshold_image( dfnImg, 1, dfnImg.max() ) )
4007
- dfnmat = ants.bandpass_filter_matrix( dfnmat, tr = tr, lowf=f[0], highf=f[1] )
4008
- dfnmat = ants.regress_components( dfnmat, nuisance )
4009
- dfnsignal = dfnmat.mean( axis = 1 )
4010
- gmmatDFNCorr = np.zeros( gmmat.shape[1] )
4011
- for k in range( gmmat.shape[1] ):
4012
- gmmatDFNCorr[ k ] = pearsonr( dfnsignal, gmmat[:,k] )[0]
4013
- corrImg = ants.make_image( gmseg, gmmatDFNCorr )
4014
- outdict[ netname ] = corrImg
4326
+ if dfnImg.max() >= 1:
4327
+ dfnmat = ants.timeseries_to_matrix( simg, ants.threshold_image( dfnImg, 1, dfnImg.max() ) )
4328
+ dfnmat = ants.bandpass_filter_matrix( dfnmat, tr = tr, lowf=f[0], highf=f[1] )
4329
+ dfnmat = ants.regress_components( dfnmat, nuisance )
4330
+ dfnsignal = dfnmat.mean( axis = 1 )
4331
+ gmmatDFNCorr = np.zeros( gmmat.shape[1] )
4332
+ for k in range( gmmat.shape[1] ):
4333
+ gmmatDFNCorr[ k ] = pearsonr( dfnsignal, gmmat[:,k] )[0]
4334
+ corrImg = ants.make_image( gmseg, gmmatDFNCorr )
4335
+ outdict[ netname ] = corrImg
4336
+ else:
4337
+ outdict[ netname ] = None
4015
4338
  ct = ct + 1
4016
4339
 
4017
4340
  A = np.zeros( ( len( numofnets ) , len( numofnets ) ) )
@@ -4105,11 +4428,15 @@ def write_bvals_bvecs(bvals, bvecs, prefix ):
4105
4428
  N = len(bvals)
4106
4429
  fname = prefix + '.bval'
4107
4430
  fmt = _VAL_FMT * N + '\n'
4108
- open(fname, 'wt').write(fmt % bvals)
4431
+ myfile = open(fname, 'wt')
4432
+ myfile.write(fmt % bvals)
4433
+ myfile.close()
4109
4434
  fname = prefix + '.bvec'
4110
4435
  bvf = open(fname, 'wt')
4111
4436
  for dim_vals in bvecs.T:
4112
4437
  bvf.write(fmt % tuple(dim_vals))
4438
+ bvf.close()
4439
+
4113
4440
 
4114
4441
  def crop_mcimage( x, mask, padder=None ):
4115
4442
  """
@@ -4152,6 +4479,8 @@ def mm(
4152
4479
  do_tractography = False,
4153
4480
  do_kk = False,
4154
4481
  do_normalization = None,
4482
+ group_template = None,
4483
+ group_transform = None,
4155
4484
  target_range = [0,1],
4156
4485
  dti_motion_correct = 'Rigid',
4157
4486
  dti_denoise = False,
@@ -4190,6 +4519,10 @@ def mm(
4190
4519
 
4191
4520
  do_normalization : template transformation if available
4192
4521
 
4522
+ group_template : optional reference template corresponding to the group_transform
4523
+
4524
+ group_transform : optional transforms corresponding to the group_template
4525
+
4193
4526
  target_range : 2-element tuple
4194
4527
  a tuple or array defining the (min, max) of the input image
4195
4528
  (e.g., [-127.5, 127.5] or [0,1]). Output images will be scaled back to original
@@ -4225,6 +4558,9 @@ def mm(
4225
4558
  JHU_atlas = mm_read( JHU_atlasfn ) # Read in JHU atlas
4226
4559
  JHU_labels = mm_read( JHU_labelsfn ) # Read in JHU labels
4227
4560
  template = mm_read( templatefn ) # Read in template
4561
+ if group_template is None:
4562
+ group_template = template
4563
+ group_transform = do_normalization['fwdtransforms']
4228
4564
  #####################
4229
4565
  # T1 hierarchical #
4230
4566
  #####################
@@ -4248,6 +4584,7 @@ def mm(
4248
4584
  normalization_dict = {
4249
4585
  'kk_norm': None,
4250
4586
  'NM_norm' : None,
4587
+ 'DTI_norm': None,
4251
4588
  'FA_norm' : None,
4252
4589
  'MD_norm' : None,
4253
4590
  'alff_norm' : None,
@@ -4454,24 +4791,32 @@ def mm(
4454
4791
  # t1reg = ants.registration( template, hier['brain_n4_dnz'], "antsRegistrationSyNQuickRepro[s]")
4455
4792
  t1reg = do_normalization
4456
4793
  if do_kk:
4457
- 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 )
4458
4795
  if output_dict['DTI'] is not None:
4459
4796
  mydti = output_dict['DTI']
4460
4797
  dtirig = ants.registration( hier['brain_n4_dnz'], mydti['recon_fa'], 'Rigid' )
4461
- normalization_dict['MD_norm'] = ants.apply_transforms( template, mydti['recon_md'],t1reg['fwdtransforms']+dtirig['fwdtransforms'] )
4462
- 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 )
4463
4808
  if output_dict['rsf'] is not None:
4464
4809
  rsfpro = output_dict['rsf']
4465
4810
  rsfrig = ants.registration( hier['brain_n4_dnz'], rsfpro['meanBold'], 'Rigid' )
4466
4811
  for netid in mynets:
4467
4812
  rsfkey = netid + "_norm"
4468
4813
  normalization_dict[rsfkey] = ants.apply_transforms(
4469
- template, rsfpro[netid],
4470
- t1reg['fwdtransforms']+rsfrig['fwdtransforms'] )
4814
+ group_template, rsfpro[netid],
4815
+ group_transform+rsfrig['fwdtransforms'] )
4471
4816
  if nm_image_list is not None:
4472
4817
  nmpro = output_dict['NM']
4473
4818
  nmrig = nmpro['t1_to_NM_transform'] # this is an inverse tx
4474
- 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,
4475
4820
  whichtoinvert=[False,False,True])
4476
4821
 
4477
4822
  if verbose:
@@ -4479,7 +4824,7 @@ def mm(
4479
4824
  return output_dict, normalization_dict
4480
4825
 
4481
4826
 
4482
- 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 ):
4483
4828
  """
4484
4829
  write the tabular and normalization output of the mm function
4485
4830
 
@@ -4496,6 +4841,8 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4496
4841
 
4497
4842
  separator : string or character separator for filenames
4498
4843
 
4844
+ verbose : boolean
4845
+
4499
4846
  Returns
4500
4847
  ---------
4501
4848
 
@@ -4529,6 +4876,7 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4529
4876
  if mm['DTI'] is not None:
4530
4877
  mydti = mm['DTI']
4531
4878
  myop = output_prefix + separator
4879
+ ants.image_write( mydti['dti'], myop + 'dti.nii.gz' )
4532
4880
  write_bvals_bvecs( mydti['bval_LR'], mydti['bvec_LR'], myop + 'reoriented' )
4533
4881
  image_write_with_thumbnail( mydti['dwi_LR_dewarped'], myop + 'dwi.nii.gz' )
4534
4882
  image_write_with_thumbnail( mydti['dtrecon_LR_dewarp']['RGB'] , myop + 'DTIRGB.nii.gz' )
@@ -4639,6 +4987,8 @@ def write_mm( output_prefix, mm, mm_norm=None, t1wide=None, separator='_' ):
4639
4987
  mm_wide['dti_FD_mean'] = mm_wide['dti_FD_max'] = 'NA'
4640
4988
  mmwidefn = output_prefix + separator + 'mmwide.csv'
4641
4989
  mm_wide.to_csv( mmwidefn )
4990
+ if verbose:
4991
+ print( output_prefix + " write_mm done." )
4642
4992
  return
4643
4993
 
4644
4994
 
@@ -4781,7 +5131,8 @@ def mm_nrg(
4781
5131
  'fwdtransforms': [ regout+'1Warp.nii.gz', regout+'0GenericAffine.mat'],
4782
5132
  'invtransforms': [ regout+'0GenericAffine.mat', regout+'1InverseWarp.nii.gz'] }
4783
5133
  if verbose:
4784
- print( "REGISTRATION EXISTENCE: " +
5134
+ print( "-<REGISTRATION EXISTENCE>-: \n" +
5135
+ "NAMING: " + regout+'0GenericAffine.mat' + " \n " +
4785
5136
  str(exists( templateTx['fwdtransforms'][0])) + " " +
4786
5137
  str(exists( templateTx['fwdtransforms'][1])) + " " +
4787
5138
  str(exists( templateTx['invtransforms'][0])) + " " +
@@ -5133,14 +5484,14 @@ def mm_nrg(
5133
5484
  mydti = tabPro['DTI']
5134
5485
  if visualize:
5135
5486
  maxslice = np.min( [21, mydti['recon_fa'] ] )
5136
- 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" )
5137
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" )
5138
5489
  ants.plot( mydti['recon_md'], axis=2, nslices=maxslice, ncol=7, crop=True, title='MD', filename=mymm+mysep+"MD.png" )
5139
5490
  if dowrite:
5140
- 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 )
5141
5492
  for mykey in normPro.keys():
5142
5493
  if normPro[mykey] is not None:
5143
- if visualize:
5494
+ if visualize and normPro[mykey].components == 1:
5144
5495
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
5145
5496
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
5146
5497
  return
@@ -5158,9 +5509,13 @@ def mm_csv(
5158
5509
  srmodel_T1 = False, # optional - will add a great deal of time
5159
5510
  srmodel_NM = False, # optional - will add a great deal of time
5160
5511
  srmodel_DTI = False, # optional - will add a great deal of time
5161
- dti_motion_correct = 'Rigid',
5162
- dti_denoise = False,
5163
- nrg_modality_list = None
5512
+ dti_motion_correct = 'SyN',
5513
+ dti_denoise = True,
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
5164
5519
  ):
5165
5520
  """
5166
5521
  too dangerous to document ... use with care.
@@ -5220,6 +5575,17 @@ def mm_csv(
5220
5575
 
5221
5576
  nrg_modality_list : optional; defaults to None; use to focus on a given modality
5222
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
+
5223
5589
  Returns
5224
5590
  ---------
5225
5591
 
@@ -5305,7 +5671,6 @@ def mm_csv(
5305
5671
  iid2=iid+"_"+t1iid
5306
5672
  myoutputPrefix = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + locmod + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + locmod + mysep + iid2
5307
5673
  if verbose:
5308
- print("VERBOSE in docsamson")
5309
5674
  print( locmod )
5310
5675
  print( myimgsInput )
5311
5676
  print( myoutputPrefix )
@@ -5323,22 +5688,30 @@ def mm_csv(
5323
5688
  t1 = mm_read( t1fn, modality='T1w' )
5324
5689
  hierfn = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + "T1wHierarchical" + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + "T1wHierarchical" + mysep + iid + mysep
5325
5690
  hierfnSR = outputdir + "/" + projid + "/" + sid + "/" + dtid + "/" + "T1wHierarchicalSR" + '/' + iid + "/" + projid + mysep + sid + mysep + dtid + mysep + "T1wHierarchicalSR" + mysep + iid + mysep
5326
- hierfntest = hierfn + 'snseg.csv'
5691
+ hierfntest = hierfn + 'cerebellum.csv'
5327
5692
  if verbose:
5328
5693
  print( hierfntest )
5329
- regout = hierfn + "syn"
5694
+ regout = re.sub("T1wHierarchical","T1w",hierfn) + "syn"
5330
5695
  templateTx = {
5331
5696
  'fwdtransforms': [ regout+'1Warp.nii.gz', regout+'0GenericAffine.mat'],
5332
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']
5333
5705
  if verbose:
5334
- print( "REGISTRATION EXISTENCE: " +
5706
+ print( "-<REGISTRATION EXISTENCE>-: \n" +
5707
+ "NAMING: " + regout+'0GenericAffine.mat' + " \n " +
5335
5708
  str(exists( templateTx['fwdtransforms'][0])) + " " +
5336
5709
  str(exists( templateTx['fwdtransforms'][1])) + " " +
5337
5710
  str(exists( templateTx['invtransforms'][0])) + " " +
5338
5711
  str(exists( templateTx['invtransforms'][1])) )
5339
5712
  if verbose:
5340
5713
  print( hierfntest )
5341
- hierexists = exists( hierfntest ) # FIXME should test this explicitly but we assume it here
5714
+ hierexists = exists( hierfntest ) and exists( templateTx['fwdtransforms'][0]) and exists( templateTx['fwdtransforms'][1]) and exists( templateTx['invtransforms'][0]) and exists( templateTx['invtransforms'][1])
5342
5715
  hier = None
5343
5716
  if not hierexists and not testloop:
5344
5717
  subjectpropath = os.path.dirname( hierfn )
@@ -5392,6 +5765,43 @@ def mm_csv(
5392
5765
  if not testloop:
5393
5766
  t1imgbrn = hier['brain_n4_dnz']
5394
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
+
5395
5805
  # loop over modalities and then unique image IDs
5396
5806
  # we treat NM in a "special" way -- aggregating repeats
5397
5807
  # other modalities (beyond T1) are treated individually
@@ -5456,6 +5866,8 @@ def mm_csv(
5456
5866
  do_tractography=False,
5457
5867
  do_kk=False,
5458
5868
  do_normalization=templateTx,
5869
+ group_template = normalization_template,
5870
+ group_transform = groupTx,
5459
5871
  test_run=test_run,
5460
5872
  verbose=True )
5461
5873
  if not test_run:
@@ -5495,21 +5907,6 @@ def mm_csv(
5495
5907
  img = mm_read( myimg )
5496
5908
  ishapelen = len( img.shape )
5497
5909
  if mymod == 'T1w' and ishapelen == 3: # for a real run, set to True
5498
- if not exists( regout + "logjacobian.nii.gz" ) or not exists( regout+'1Warp.nii.gz' ):
5499
- if verbose:
5500
- print('start t1 registration')
5501
- ex_path = os.path.expanduser( "~/.antspyt1w/" )
5502
- templatefn = ex_path + 'CIT168_T1w_700um_pad_adni.nii.gz'
5503
- template = mm_read( templatefn )
5504
- template = ants.resample_image( template, [1,1,1], use_voxels=False )
5505
- t1reg = ants.registration( template, hier['brain_n4_dnz'],
5506
- "antsRegistrationSyNQuickRepro[s]", outprefix = regout, verbose=False )
5507
- myjac = ants.create_jacobian_determinant_image( template,
5508
- t1reg['fwdtransforms'][0], do_log=True, geom=True )
5509
- image_write_with_thumbnail( myjac, regout + "logjacobian.nii.gz", thumb=False )
5510
- if visualize:
5511
- ants.plot( ants.iMath(t1reg['warpedmovout'],"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='warped to template', filename=regout+"totemplate.png" )
5512
- ants.plot( ants.iMath(myjac,"Normalize"), axis=2, nslices=21, ncol=7, crop=True, title='jacobian', filename=regout+"jacobian.png" )
5513
5910
  if not exists( mymm + mysep + "kk_norm.nii.gz" ):
5514
5911
  dowrite=True
5515
5912
  if verbose:
@@ -5519,6 +5916,8 @@ def mm_csv(
5519
5916
  do_tractography=False,
5520
5917
  do_kk=True,
5521
5918
  do_normalization=templateTx,
5919
+ group_template = normalization_template,
5920
+ group_transform = groupTx,
5522
5921
  test_run=test_run,
5523
5922
  verbose=True )
5524
5923
  if visualize:
@@ -5534,6 +5933,8 @@ def mm_csv(
5534
5933
  do_tractography=False,
5535
5934
  do_kk=False,
5536
5935
  do_normalization=templateTx,
5936
+ group_template = normalization_template,
5937
+ group_transform = groupTx,
5537
5938
  test_run=test_run,
5538
5939
  verbose=True )
5539
5940
  if visualize:
@@ -5556,6 +5957,8 @@ def mm_csv(
5556
5957
  do_tractography=False,
5557
5958
  do_kk=False,
5558
5959
  do_normalization=templateTx,
5960
+ group_template = normalization_template,
5961
+ group_transform = groupTx,
5559
5962
  test_run=test_run,
5560
5963
  verbose=True )
5561
5964
  if tabPro['rsf'] is not None and visualize:
@@ -5571,12 +5974,12 @@ def mm_csv(
5571
5974
  ants.plot( tabPro['rsf']['meanBold'], tabPro['rsf']['FrontoparietalTaskControl'],
5572
5975
  axis=2, nslices=maxslice, ncol=7, crop=True, title='FrontoparietalTaskControl', filename=mymm+mysep+"boldFrontoparietalTaskControl.png" )
5573
5976
  if ( mymod == 'DTI_LR' or mymod == 'DTI_RL' or mymod == 'DTI' ) and ishapelen == 4:
5574
- dowrite=True
5575
5977
  bvalfn = re.sub( '.nii.gz', '.bval' , myimg )
5576
5978
  bvecfn = re.sub( '.nii.gz', '.bvec' , myimg )
5577
5979
  imgList = [ img ]
5578
5980
  bvalfnList = [ bvalfn ]
5579
5981
  bvecfnList = [ bvecfn ]
5982
+ missing_dti_data=False # bval, bvec or images
5580
5983
  if len( myimgsr ) > 1: # find DTI_RL
5581
5984
  dtilrfn = myimgsr[myimgcount+1]
5582
5985
  if exists( dtilrfn ):
@@ -5589,44 +5992,50 @@ def mm_csv(
5589
5992
  # check existence of all files expected ...
5590
5993
  for dtiex in bvalfnList+bvecfnList+myimgsr:
5591
5994
  if not exists(dtiex):
5592
- raise ValueError('mm_csv dti data ' + dtiex )
5593
- srmodel_DTI_mdl=None
5594
- if srmodel_DTI is not False:
5595
- temp = ants.get_spacing(img)
5596
- dtspc=[temp[0],temp[1],temp[2]]
5597
- bestup = siq.optimize_upsampling_shape( dtspc, modality='DTI' )
5598
- mdlfn = ex_pathmm + "siq_default_sisr_" + bestup + "_1chan_featvggL6_best_mdl.h5"
5599
- if isinstance( srmodel_DTI, str ):
5600
- srmodel_DTI = re.sub( "bestup", bestup, srmodel_DTI )
5601
- mdlfn = os.path.join( ex_pathmm, srmodel_DTI )
5602
- if exists( mdlfn ):
5603
- if verbose:
5604
- print(mdlfn)
5605
- srmodel_DTI_mdl = tf.keras.models.load_model( mdlfn, compile=False )
5606
- else:
5607
- print(mdlfn + " does not exist - wont use SR")
5608
- tabPro, normPro = mm( t1, hier,
5609
- dw_image=imgList,
5610
- bvals = bvalfnList,
5611
- bvecs = bvecfnList,
5612
- srmodel=srmodel_DTI_mdl,
5613
- do_tractography=not test_run,
5614
- do_kk=False,
5615
- do_normalization=templateTx,
5616
- dti_motion_correct = dti_motion_correct,
5617
- dti_denoise = dti_denoise,
5618
- test_run=test_run,
5619
- verbose=True )
5620
- mydti = tabPro['DTI']
5621
- if visualize:
5622
- maxslice = np.min( [21, mydti['recon_fa'] ] )
5623
- ants.plot( mydti['recon_fa'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA (supposed to be better)', filename=mymm+mysep+"FAbetter.png" )
5624
- ants.plot( mydti['recon_fa'], mydti['jhu_labels'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA + JHU', filename=mymm+mysep+"FAJHU.png" )
5625
- ants.plot( mydti['recon_md'], axis=2, nslices=maxslice, ncol=7, crop=True, title='MD', filename=mymm+mysep+"MD.png" )
5995
+ print('mm_csv: missing dti data ' + dtiex )
5996
+ missing_dti_data=True
5997
+ dowrite=False
5998
+ if not missing_dti_data:
5999
+ dowrite=True
6000
+ srmodel_DTI_mdl=None
6001
+ if srmodel_DTI is not False:
6002
+ temp = ants.get_spacing(img)
6003
+ dtspc=[temp[0],temp[1],temp[2]]
6004
+ bestup = siq.optimize_upsampling_shape( dtspc, modality='DTI' )
6005
+ mdlfn = ex_pathmm + "siq_default_sisr_" + bestup + "_1chan_featvggL6_best_mdl.h5"
6006
+ if isinstance( srmodel_DTI, str ):
6007
+ srmodel_DTI = re.sub( "bestup", bestup, srmodel_DTI )
6008
+ mdlfn = os.path.join( ex_pathmm, srmodel_DTI )
6009
+ if exists( mdlfn ):
6010
+ if verbose:
6011
+ print(mdlfn)
6012
+ srmodel_DTI_mdl = tf.keras.models.load_model( mdlfn, compile=False )
6013
+ else:
6014
+ print(mdlfn + " does not exist - wont use SR")
6015
+ tabPro, normPro = mm( t1, hier,
6016
+ dw_image=imgList,
6017
+ bvals = bvalfnList,
6018
+ bvecs = bvecfnList,
6019
+ srmodel=srmodel_DTI_mdl,
6020
+ do_tractography=not test_run,
6021
+ do_kk=False,
6022
+ do_normalization=templateTx,
6023
+ group_template = normalization_template,
6024
+ group_transform = groupTx,
6025
+ dti_motion_correct = dti_motion_correct,
6026
+ dti_denoise = dti_denoise,
6027
+ test_run=test_run,
6028
+ verbose=True )
6029
+ mydti = tabPro['DTI']
6030
+ if visualize:
6031
+ maxslice = np.min( [21, mydti['recon_fa'] ] )
6032
+ ants.plot( mydti['recon_fa'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA (supposed to be better)', filename=mymm+mysep+"FAbetter.png" )
6033
+ ants.plot( mydti['recon_fa'], mydti['jhu_labels'], axis=2, nslices=maxslice, ncol=7, crop=True, title='FA + JHU', filename=mymm+mysep+"FAJHU.png" )
6034
+ ants.plot( mydti['recon_md'], axis=2, nslices=maxslice, ncol=7, crop=True, title='MD', filename=mymm+mysep+"MD.png" )
5626
6035
  if dowrite:
5627
6036
  write_mm( output_prefix=mymm, mm=tabPro, mm_norm=normPro, t1wide=t1wide, separator=mysep )
5628
6037
  for mykey in normPro.keys():
5629
- if normPro[mykey] is not None:
6038
+ if normPro[mykey] is not None and normPro[mykey].components == 1:
5630
6039
  if visualize:
5631
6040
  ants.plot( template, normPro[mykey], axis=2, nslices=21, ncol=7, crop=True, title=mykey, filename=mymm+mysep+mykey+".png" )
5632
6041
  if overmodX == nrg_modality_list[ len( nrg_modality_list ) - 1 ]:
@@ -6030,6 +6439,110 @@ def read_mm_csv( x, is_t1=False, colprefix=None, separator='-', verbose=False ):
6030
6439
  xdf.columns=colprefix + xdf.columns
6031
6440
  return pd.concat( [df,xdf], axis=1 )
6032
6441
 
6442
+ def merge_wides_to_study_dataframe( sdf, processing_dir, separator='-', sid_is_int=True, id_is_int=True, date_is_int=True, report_missing=False,
6443
+ progress=False, verbose=False ):
6444
+ """
6445
+ extend a study data frame with wide outputs
6446
+
6447
+ sdf : the input study dataframe
6448
+
6449
+ processing_dir: the directory location of the processed data
6450
+
6451
+ separator : string usually '-' or '_'
6452
+
6453
+ sid_is_int : boolean set to True to cast unique subject ids to int; can be useful if they are inadvertently stored as float by pandas
6454
+
6455
+ date_is_int : boolean set to True to cast date to int; can be useful if they are inadvertently stored as float by pandas
6456
+
6457
+ id_is_int : boolean set to True to cast unique image ids to int; can be useful if they are inadvertently stored as float by pandas
6458
+
6459
+ report_missing : boolean combined with verbose will report missing modalities
6460
+
6461
+ progress : integer reports percent progress modulo progress value
6462
+
6463
+ verbose : boolean
6464
+ """
6465
+ from os.path import exists
6466
+ musthavecols = ['projectID', 'subjectID','date','imageID','fn']
6467
+ for k in range(len(musthavecols)):
6468
+ if not musthavecols[k] in sdf.keys():
6469
+ raise ValueError('sdf is missing column ' +musthavecols[k] + ' in merge_wides_to_study_dataframe' )
6470
+ possible_iids = [ 'imageID', 'imageID', 'imageID', 'flairid', 'dtid1', 'dtid2', 'rsfid1', 'rsfid2', 'nmid1', 'nmid2', 'nmid3', 'nmid4', 'nmid5', 'nmid6', 'nmid7', 'nmid8', 'nmid9', 'nmid10' ]
6471
+ modality_ids = [ 'T1wHierarchical', 'T1wHierarchicalSR', 'T1w', 'T2Flair', 'DTI', 'DTI', 'rsfMRI', 'rsfMRI', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT', 'NM2DMT']
6472
+ alldf=pd.DataFrame()
6473
+ for myk in sdf.index:
6474
+ if progress > 0 and int(myk) % int(progress) == 0:
6475
+ print( str( round( myk/sdf.shape[0]*100.0)) + "%...", end='', flush=True)
6476
+ if verbose:
6477
+ print( "DOROW " + str(myk) + ' of ' + str( sdf.shape[0] ) )
6478
+ csvrow = sdf.loc[sdf.index == myk].dropna(axis=1)
6479
+ ct=-1
6480
+ for iidkey in possible_iids:
6481
+ ct=ct+1
6482
+ mod_name = modality_ids[ct]
6483
+ if iidkey in csvrow.keys():
6484
+ if id_is_int:
6485
+ iid = str( int( csvrow[iidkey].iloc[0] ) )
6486
+ else:
6487
+ iid = str( csvrow[iidkey].iloc[0] )
6488
+ if verbose:
6489
+ print( "iidkey " + iidkey + " modality " + mod_name + ' iid '+ iid )
6490
+ pid=str(csvrow['projectID'].iloc[0] )
6491
+ if sid_is_int:
6492
+ sid=str(int(csvrow['subjectID'].iloc[0] ))
6493
+ else:
6494
+ sid=str(csvrow['subjectID'].iloc[0] )
6495
+ if date_is_int:
6496
+ dt=str(int(csvrow['date'].iloc[0]))
6497
+ else:
6498
+ dt=str(csvrow['date'].iloc[0])
6499
+ if id_is_int:
6500
+ t1iid=str(int(csvrow['imageID'].iloc[0]))
6501
+ else:
6502
+ t1iid=str(csvrow['imageID'].iloc[0])
6503
+ if t1iid != iid:
6504
+ iidj=iid+"_"+t1iid
6505
+ else:
6506
+ iidj=iid
6507
+ rootid = pid +separator+ sid +separator+dt+separator+mod_name+separator+iidj
6508
+ myext = rootid +separator+'mmwide.csv'
6509
+ nrgwidefn=os.path.join( processing_dir, pid, sid, dt, mod_name, iid, myext )
6510
+ moddersub = mod_name
6511
+ is_t1=False
6512
+ if mod_name == 'T1wHierarchical':
6513
+ is_t1=True
6514
+ moddersub='T1Hier'
6515
+ elif mod_name == 'T1wHierarchicalSR':
6516
+ is_t1=True
6517
+ moddersub='T1HSR'
6518
+ if exists( nrgwidefn ):
6519
+ if verbose:
6520
+ print( nrgwidefn + " exists")
6521
+ mm=read_mm_csv( nrgwidefn, colprefix=moddersub+'_', is_t1=is_t1, separator=separator, verbose=verbose )
6522
+ if mm is not None:
6523
+ if mod_name == 'T1wHierarchical':
6524
+ a=list( csvrow.keys() )
6525
+ b=list( mm.keys() )
6526
+ abintersect=list(set(b).intersection( set(a) ) )
6527
+ if len( abintersect ) > 0 :
6528
+ for qq in abintersect:
6529
+ mm.pop( qq )
6530
+ mm.index=csvrow.index
6531
+ uidname = mod_name + '_mmwide_filename'
6532
+ mm[ uidname ] = rootid
6533
+ csvrow=pd.concat( [csvrow,mm], axis=1 )
6534
+ else:
6535
+ if verbose and report_missing:
6536
+ print( nrgwidefn + " absent")
6537
+ if alldf.shape[0] == 0:
6538
+ alldf = csvrow.copy()
6539
+ alldf = alldf.loc[:,~alldf.columns.duplicated()]
6540
+ else:
6541
+ csvrow=csvrow.loc[:,~csvrow.columns.duplicated()]
6542
+ alldf = alldf.loc[:,~alldf.columns.duplicated()]
6543
+ alldf = pd.concat( [alldf, csvrow], axis=0, ignore_index=True)
6544
+ return alldf
6545
+
6033
6546
  def assemble_modality_specific_dataframes( mm_wide_csvs, hierdfin, nrg_modality, separator='-', progress=None, verbose=False ):
6034
6547
  moddersub = re.sub( "[*]","",nrg_modality)
6035
6548
  nmdf=pd.DataFrame()
@@ -6136,7 +6649,7 @@ def boot_wmh( flair, t1, t1seg, mmfromconvexhull = 0.0, strict=True,
6136
6649
  if prior_probability is not None:
6137
6650
  augprob_prior = flair * 0.0
6138
6651
  for n in range(n_simulations):
6139
- augflair, tx, itx = augment_image( flair, 5 )
6652
+ augflair, tx, itx = augment_image( ants.iMath(flair,"Normalize"), 5, 0.01 )
6140
6653
  locwmh = wmh( augflair, t1, t1seg, mmfromconvexhull = mmfromconvexhull,
6141
6654
  strict=strict, probability_mask=None, prior_probability=prior_probability )
6142
6655
  if verbose:
@@ -6581,7 +7094,7 @@ def blind_image_assessment(
6581
7094
  viz_filename=None,
6582
7095
  title=False,
6583
7096
  pull_rank=False,
6584
- resample='max',
7097
+ resample=None,
6585
7098
  verbose=False
6586
7099
  ):
6587
7100
  """
@@ -6638,7 +7151,7 @@ def blind_image_assessment(
6638
7151
  json_name = re.sub(".nii.gz",".json",image_filename)
6639
7152
  if exists( json_name ):
6640
7153
  with open(json_name, 'r') as fcc_file:
6641
- mymeta = json.load(fcc_file)
7154
+ mymeta = json.load(fcc_file, strict=False)
6642
7155
  if verbose:
6643
7156
  print(json.dumps(mymeta, indent=4))
6644
7157
  mystem=Path( image ).stem
@@ -6681,16 +7194,30 @@ def blind_image_assessment(
6681
7194
  else:
6682
7195
  image_compare = ants.image_clone( image_b0 )
6683
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))
6684
7199
  if resample is not None:
6685
7200
  if resample == 'min':
6686
- 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 )
6687
7204
  elif resample == 'max':
6688
- newspc = np.repeat( np.max(ants.get_spacing(image)), 3 )
7205
+ newspc = np.repeat( maxspc, 3 )
6689
7206
  else:
6690
7207
  newspc = np.repeat( resample, 3 )
6691
7208
  image = ants.resample_image( image, newspc )
6692
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 )
6693
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))
6694
7221
  msk = ants.threshold_image( ants.iMath(image,'Normalize'), 0.15, 1.0 )
6695
7222
  # else:
6696
7223
  # msk = ants.get_mask( image )
@@ -6727,8 +7254,17 @@ def blind_image_assessment(
6727
7254
  imagereflect = ants.reflect_image(image, axis=0)
6728
7255
  asym_err = ( image - imagereflect ).abs().mean()
6729
7256
  # estimate noise by center cropping, denoizing and taking magnitude of difference
6730
- mycc = antspyt1w.special_crop( image,
6731
- 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 )
6732
7268
  myccd = ants.denoise_image( mycc, p=2,r=2,noise_model='Gaussian' )
6733
7269
  noizlevel = ( mycc - myccd ).abs().mean()
6734
7270
  # ants.plot_ortho( image, crop=False, filename=viz_filename, flat=True, xyz_lines=False, orient_labels=False, xyz_pad=0 )
@@ -6745,7 +7281,10 @@ def blind_image_assessment(
6745
7281
  cnrref = ( fgmean - bgmean ) / bgstd
6746
7282
  psnrref = antspynet.psnr( image_compare, image )
6747
7283
  ssimref = antspynet.ssim( image_compare, image )
6748
- 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 )
6749
7288
  mriseries='NA'
6750
7289
  mrimfg='NA'
6751
7290
  mrimodel='NA'
@@ -6794,9 +7333,7 @@ def average_blind_qc_by_modality(qc_full,verbose=False):
6794
7333
  uid = qc_full['fn'] + "_" + qc_full['modality'].astype(str)
6795
7334
  to_average = uid.unique()
6796
7335
  # Define column indices
6797
- contcols = ['noise', 'snr', 'cnr', 'psnr', 'ssim', 'mi',
6798
- 'reflection_err', 'EVR', 'msk_vol', 'spc0', 'spc1', 'spc2', 'dimx',
6799
- 'dimy', 'dimz', 'slice']
7336
+ contcols = ['noise', 'snr', 'cnr', 'psnr', 'ssim', 'mi','reflection_err', 'EVR', 'msk_vol', 'spc0', 'spc1', 'spc2', 'org0','org1','org2', 'dimx', 'dimy', 'dimz', 'slice']
6800
7337
  ocols = ['fn','modality', 'mriseries', 'mrimfg', 'mrimodel']
6801
7338
  # restrict to columns we "know"
6802
7339
  qc_full = qc_full[ocols+contcols]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: antspymm
3
- Version: 0.9.7
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
@@ -91,6 +91,17 @@ will all modalities will take around 2 hours on an average laptop.
91
91
 
92
92
  documentation of functions [here](http://htmlpreview.github.io/?https://github.com/stnava/ANTsPyMM/blob/main/docs/antspymm/mm.html).
93
93
 
94
+
95
+ achieved through four steps (recommended approach):
96
+
97
+ 1. organize data in NRG format
98
+
99
+ 2. perform blind QC
100
+
101
+ 3. compute outlierness per modality and select optimally matched modalities ( steps 3.1 and 3.2 )
102
+
103
+ 4. run the main antspymm function
104
+
94
105
  # first time setup
95
106
 
96
107
  ```python
@@ -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=81mPpK_dfpuD0bVTpTr1U5oxOA4dvUqcShgxo0herXc,2690
2
- antspymm/mm.py,sha256=6ETAYzaAw1-MznWTS6JN693OJshqa2AV6whPdLBoKDY,285326
3
- antspymm-0.9.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
4
- antspymm-0.9.7.dist-info/METADATA,sha256=roFi7QJkXiLyAgdcPTsW8HgXKLlk2cuUAdkpTGAHPTM,10476
5
- antspymm-0.9.7.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
6
- antspymm-0.9.7.dist-info/top_level.txt,sha256=iyD1sRhCKzfwKRJLq5ZUeV9xsv1cGQl8Ejp6QwXM1Zg,9
7
- antspymm-0.9.7.dist-info/RECORD,,