Skip to content

Commit dbdf148

Browse files
Merge pull request #9 from pnlbwh/fix-warp
do not glob transform files with *ToMNI* pattern during --create, structure outputs into csv, provide demonstrative plots
2 parents c7ce6a4 + 899f9b6 commit dbdf148

10 files changed

Lines changed: 213 additions & 105 deletions

README.md

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Table of Contents
2929
* [Create template](#create-template)
3030
* [Harmonize data](#harmonize-data)
3131
* [Debug](#debug)
32+
* [1. Same target list](#1-same-target-list)
33+
* [2. Different target list](#2-different-target-list)
3234
* [Tests](#tests)
3335
* [1. pipeline](#1-pipeline)
3436
* [2. unittest](#2-unittest)
@@ -339,29 +341,103 @@ or provided(--nshm 4), data can be harmonized.
339341
## Harmonize data
340342

341343
multi-shell-dMRIharmonization/lib/multi-shell-harmonization.py --tar_list tar_list.txt
342-
--ref_name REF --tar_name TAR --template template
344+
--tar_name TAR --template template
343345
--process
344346

345347
multi-shell-dMRIharmonization/lib/multi-shell-harmonization.py --tar_list tar_list.txt
346-
--ref_name REF --tar_name TAR --template template
348+
--tar_name TAR --template template
347349
--process --debug
348350

349351

350352
## Debug
351353

352-
Runs together with `--create` and `--process`
354+
### 1. Same target list
355+
356+
Run together with `--create` and `--process`:
353357

354358
multi-shell-dMRIharmonization/lib/multi-shell-harmonization.py --ref_list ref_list.txt --tar_list tar_list.txt
355359
--ref_name REF --tar_name TAR --template template
356360
--create --process --debug
357361

358362

363+
### 2. Different target list
364+
365+
In theory, you want to create a template with small number of data from each sites and then use the template to
366+
harmonize all of your data in the target site. Since the data for template creation and harmonization are not same,
367+
we don't have luxury of using `--create --process --debug` altogether. Instead, you would use `--debug` with each of
368+
`--create` and `--process`. The `--debug` flag creates some files that are used to obtain statistics later.
369+
The steps are described below:
370+
371+
(i) Create template with `--debug` enabled:
372+
373+
multi-shell-dMRIharmonization/lib/multi-shell-harmonization.py --ref_list ref_list.txt --tar_list tar_small_list.txt
374+
--ref_name REF --tar_name TAR --template template
375+
--create --debug
376+
377+
(ii) Harmonize data with `--debug` enabled:
378+
379+
multi-shell-dMRIharmonization/lib/multi-shell-harmonization.py --tar_list tar_list.txt
380+
--tar_name TAR --template template
381+
--process --debug
382+
383+
(iii) Obtain statistics
384+
385+
## reference site ##
386+
387+
# start with highest bvalue shell
388+
389+
multi-shell-dMRIharmonization/lib/tests/fa_skeleton_test.py
390+
-i ref_list_b3000.csv.modified -s REF
391+
-t template/ --bshell_b 3000
392+
393+
# repeat for other non-zero bvalues
394+
395+
multi-shell-dMRIharmonization/lib/tests/fa_skeleton_test.py
396+
-i ref_list_b2000.csv.modified -s REF
397+
-t template/ --bshell_b 2000
398+
399+
multi-shell-dMRIharmonization/lib/tests/fa_skeleton_test.py
400+
-i ref_list_b1000.csv.modified -s REF
401+
-t template/ --bshell_b 1000
402+
403+
...
404+
...
405+
406+
407+
408+
## target site before harmonization ##
409+
410+
# again, start with highest bvalue shell, notice the absence of ".modified" at the end of -i
411+
412+
multi-shell-dMRIharmonization/lib/tests/fa_skeleton_test.py
413+
-i tar_list_b3000.csv -s TAR
414+
-t template/ --bshell_b 3000
415+
416+
# repeat for other non-zero bvalues
417+
418+
...
419+
...
420+
421+
422+
423+
## target site after harmonization ##
424+
425+
# once again, start with highest bvalue shell, notice the presence of ".modified.harmonized" at the end of -i
426+
427+
multi-shell-dMRIharmonization/lib/tests/fa_skeleton_test.py
428+
-i tar_list_b3000.csv.modified.harmonized -s TAR
429+
-t template/ --bshell_b 3000
430+
431+
# repeat for other non-zero bvalues
432+
...
433+
...
434+
359435

360-
**TBD** Tests are incomplete as of now but will be completed soon.
361436

362437
# Tests
363438

364439
A small test data is provided with each [release](https://github.com/pnlbwh/multi-shell-dMRIharmonization/releases).
440+
**TBD** Tests are incomplete as of now but will be completed soon.
365441

366442

367443
## 1. pipeline
@@ -442,7 +518,10 @@ each subject FA (reference, target before harmonization, and after harmonization
442518
optional arguments:
443519
-h, --help show this help message and exit
444520
-i INPUT, --input INPUT
445-
input list of FA images
521+
a .txt/.csv file that you used in/obtained from
522+
harmonization.py having two columns for (img,mask)
523+
pair. See pnlbwh/dMRIharmonization documentation for
524+
more details
446525
-s SITE, --site SITE site name for locating template FA and mask in
447526
tempalte directory
448527
-t TEMPLATE, --template TEMPLATE
@@ -466,7 +545,7 @@ in the same space, registration is performed only once. The script is intelligen
466545
files if registration was performed before.
467546

468547
In multi-shell-dMRIharmonization approach, registration is performed with highest b-shell. Obtained transform files
469-
are used to warp rest of the b-shells.
548+
are used to warp rest of the b-shells. See [Different target list](#2-different-target-list) for details.
470549

471550

472551
# Caveats/Issues

lib/buildTemplate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ def warp_bands(imgPath, maskPath, templatePath):
4747
prefix= basename(imgPath).split('.nii')[0]
4848
transPrefix= prefix.replace(f'_b{bshell_b}','')
4949
directory= dirname(imgPath)
50-
warp = glob(pjoin(templatePath, transPrefix + f'*_FA*[!Inverse]Warp.nii.gz'))
51-
trans = glob(pjoin(templatePath, transPrefix + f'*_FA*GenericAffine.mat'))
50+
warp = glob(pjoin(templatePath, transPrefix + f'*_FA*[!ToMNI]1Warp.nii.gz'))
51+
trans = glob(pjoin(templatePath, transPrefix + f'*_FA*[!ToMNI]0GenericAffine.mat'))
5252

5353
# warping the mask
5454
applyXform(maskPath,

lib/harmonization.py

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,11 @@
2020
from determineNshm import verifyNshmForAll, determineNshm
2121
from util import *
2222
from fileUtil import read_caselist, check_dir, check_csv
23-
from conversion import read_imgs_masks
2423

2524
N_CPU= psutil.cpu_count()
2625
SCRIPTDIR= dirname(__file__)
2726

2827

29-
def printStat(ref_mean, csvFile):
30-
31-
print('mean FA over IIT_mean_FA_skeleton.nii.gz for all cases: ')
32-
imgs, _ = read_imgs_masks(csvFile)
33-
for i, imgPath in enumerate(imgs):
34-
print(basename(imgPath), ref_mean[i])
35-
36-
print('')
37-
print('mean meanFA: ', np.mean(ref_mean))
38-
print('std meanFA: ', np.std(ref_mean))
39-
print('')
40-
41-
42-
4328
class pipeline(cli.Application):
4429

4530
"""
@@ -62,19 +47,19 @@ class pipeline(cli.Application):
6247
ref_csv = cli.SwitchAttr(
6348
['--ref_list'],
6449
cli.ExistingFile,
65-
help='reference csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\ndwi2,mask2\n...',
50+
help='reference csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\\n dwi2,mask2\\n...',
6651
mandatory=False)
6752

6853
target_csv = cli.SwitchAttr(
6954
['--tar_list'],
7055
cli.ExistingFile,
71-
help='target csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\ndwi2,mask2\n...',
56+
help='target csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\\n dwi2,mask2\n...',
7257
mandatory=False)
7358

7459
harm_csv = cli.SwitchAttr(
7560
['--harm_list'],
7661
cli.ExistingFile,
77-
help='harmonized csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\ndwi2,mask2\n...',
62+
help='harmonized csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\\n dwi2,mask2\n...',
7863
mandatory=False)
7964

8065
templatePath = cli.SwitchAttr(
@@ -145,7 +130,7 @@ class pipeline(cli.Application):
145130
reference = cli.SwitchAttr(
146131
'--ref_name',
147132
help= 'reference site name',
148-
mandatory= True)
133+
mandatory= False)
149134

150135
target = cli.SwitchAttr(
151136
'--tar_name',
@@ -263,6 +248,7 @@ def createTemplate(self):
263248
def harmonizeData(self):
264249

265250
from reconstSignal import reconst
251+
from preprocess import dti_harm
266252

267253
# check the templatePath
268254
if not exists(self.templatePath):
@@ -324,7 +310,7 @@ def harmonizeData(self):
324310
pool.close()
325311
pool.join()
326312

327-
313+
328314
# loop for debugging
329315
# res= []
330316
# for imgPath, maskPath in zip(imgs, masks):
@@ -341,6 +327,17 @@ def harmonizeData(self):
341327
if preFlag:
342328
fm.close()
343329
fh.close()
330+
331+
332+
if self.debug:
333+
harmImgs, harmMasks= read_caselist(self.harm_csv)
334+
pool = multiprocessing.Pool(self.N_proc)
335+
for imgPath,maskPath in zip(harmImgs,harmMasks):
336+
pool.apply_async(func= dti_harm, args= (imgPath,maskPath))
337+
pool.close()
338+
pool.join()
339+
340+
344341
print('\n\nHarmonization completed\n\n')
345342

346343

@@ -365,42 +362,56 @@ def showStat(self):
365362

366363
from debug_fa import analyzeStat
367364
from datetime import datetime
365+
from harm_plot import generate_csv, harm_plot
366+
import pandas as pd
368367

369-
print('Printing statistics :\n\n')
370-
371-
# save statistics for future
372-
statFile= pjoin(self.templatePath, 'meanFAstat.txt')
373-
f= open(statFile,'a')
374-
stdout= sys.stdout
375-
sys.stdout= f
376-
377-
print(datetime.now().strftime('%c'),'\n')
378-
379-
print('b-shell', self.bshell_b, '\n')
368+
print('\n\nComputing statistics\n\n')
380369

381370
print(f'{self.reference} site: ')
382371
ref_mean = analyzeStat(self.ref_csv, self.templatePath)
383-
printStat(ref_mean, self.ref_csv)
372+
generate_csv(self.ref_csv, ref_mean, pjoin(self.templatePath, self.reference), self.bshell_b)
384373

385374
print(f'{self.target} site before harmonization: ')
386-
target_mean_before = analyzeStat(self.tar_unproc_csv, self.templatePath)
387-
printStat(target_mean_before, self.tar_unproc_csv)
375+
target_mean_before = analyzeStat(self.tar_unproc_csv, self.templatePath)
376+
generate_csv(self.tar_unproc_csv, target_mean_before, pjoin(self.templatePath, self.target)+'_before', self.bshell_b)
388377

389378
print(f'{self.target} site after harmonization: ')
390-
target_mean_after = analyzeStat(self.harm_csv, self.templatePath)
391-
printStat(target_mean_after, self.harm_csv)
379+
target_mean_after = analyzeStat(self.harm_csv, self.templatePath)
380+
generate_csv(self.harm_csv, target_mean_after, pjoin(self.templatePath, self.target)+'_after', self.bshell_b)
381+
382+
383+
print('\n\nPrinting statistics\n\n')
384+
# save statistics for future
385+
statFile= pjoin(self.templatePath, 'meanFAstat.csv')
386+
if isfile(statFile):
387+
df= pd.read_csv(statFile)
388+
else:
389+
timestamp= datetime.now().strftime('%m/%d/%y %H:%M')
390+
sites= [f'{self.reference}',f'{self.target}_before',f'{self.target}_after']
391+
df= pd.DataFrame({timestamp:sites})
392+
393+
header= f'mean meanFA b{self.bshell_b}'
394+
value= [np.mean(x) for x in [ref_mean, target_mean_before, target_mean_after]]
395+
df= df.assign(**{header:value})
396+
397+
# print an empty line so future results, if appended, are visually separate
398+
# df=df.append(pd.Series(),ignore_index=True)
399+
400+
df.to_csv(statFile, index=False)
392401

393-
print('\n\n')
394-
395-
f.close()
396-
sys.stdout= stdout
397-
398402
# print statistics on console
399403
with open(statFile) as f:
400-
print(f.read())
404+
print(f.read())
401405

402-
print('\nThe statistics are also saved in ', statFile)
403-
406+
# generate graph
407+
ebar= harm_plot([ref_mean, target_mean_before, target_mean_after],
408+
labels=[self.reference, self.target+'_before', self.target+'_after'],
409+
outPrefix=pjoin(self.templatePath,'meanFAstat'), bshell_b=self.bshell_b)
410+
411+
print(f'\nDetailed statistics, summary results, and demonstrative plots are saved in:\n\n{self.templatePath}/*_stat.csv'
412+
f'\n{statFile}\n{ebar}\n')
413+
414+
404415

405416

406417
def sanityCheck(self):
@@ -447,9 +458,9 @@ def main(self):
447458
# determine N_shm in default mode during template creation
448459
if self.N_shm==-1 and self.create:
449460
if self.ref_csv:
450-
ref_nshm_img = read_imgs_masks(self.ref_csv)[0][0]
461+
ref_nshm_img = read_caselist(self.ref_csv)[0][0]
451462
elif self.target_csv:
452-
ref_nshm_img = read_imgs_masks(self.target_csv)[0][0]
463+
ref_nshm_img = read_caselist(self.target_csv)[0][0]
453464

454465
directory= dirname(ref_nshm_img)
455466
prefix= basename(ref_nshm_img).split('.nii')[0]

lib/joinBshells.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,7 @@ def joinBshells(imgPath, ref_bvals_file=None, ref_bvals=None, sep_prefix=None):
7272

7373
joinedDwi[:,:,:,ind] = b0_bshell[:,:,:,1:]
7474

75-
if not isfile(harmPrefix + '.nii.gz'):
76-
save_nifti(harmPrefix + '.nii.gz', joinedDwi, b0Img.affine, b0Img.header)
77-
else:
78-
print(harmPrefix + '.nii.gz', 'already exists, not overwritten.')
79-
75+
save_nifti(harmPrefix + '.nii.gz', joinedDwi, b0Img.affine, b0Img.header)
8076

8177

8278
def joinAllBshells(tar_csv, ref_bvals_file, separatedPrefix=None, ncpu=4):

lib/multi-shell-harmonization.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ class multi_shell_pipeline(cli.Application):
5454
help='target csv/txt file with first column for dwi and 2nd column for mask: dwi1,mask1\\ndwi2,mask2\\n...',
5555
mandatory=False)
5656

57-
5857
templatePath = cli.SwitchAttr(
5958
['--template'],
6059
help='template directory',
@@ -107,7 +106,7 @@ class multi_shell_pipeline(cli.Application):
107106
reference = cli.SwitchAttr(
108107
'--ref_name',
109108
help= 'reference site name',
110-
mandatory= True)
109+
mandatory= False)
111110

112111
target = cli.SwitchAttr(
113112
'--tar_name',
@@ -122,6 +121,9 @@ class multi_shell_pipeline(cli.Application):
122121

123122
def main(self):
124123

124+
# FIXME https://github.com/pnlbwh/multi-shell-dMRIharmonization/issues/12
125+
self.force=False
126+
125127
if self.N_proc=='-1':
126128
self.N_proc= N_CPU
127129

lib/preprocess.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from resampling import resampling
2121
from dti import dti
2222
from rish import rish
23+
from glob import glob
2324

2425
SCRIPTDIR= os.path.dirname(__file__)
2526
config = ConfigParser()
@@ -44,10 +45,12 @@ def dti_harm(imgPath, maskPath):
4445
prefix = basename(inPrefix)
4546

4647
outPrefix = os.path.join(directory, 'dti', prefix)
47-
dti(imgPath, maskPath, inPrefix, outPrefix)
48+
if not glob(outPrefix+'_FA.nii.gz'):
49+
dti(imgPath, maskPath, inPrefix, outPrefix)
4850

4951
outPrefix = os.path.join(directory, 'harm', prefix)
50-
rish(imgPath, maskPath, inPrefix, outPrefix, N_shm)
52+
if not glob(outPrefix+'_L0.nii.gz'):
53+
rish(imgPath, maskPath, inPrefix, outPrefix, N_shm)
5154

5255

5356

0 commit comments

Comments
 (0)