Skip to content

Commit

Permalink
Merge pull request #510 from oesteban/enh/489
Browse files Browse the repository at this point in the history
[ENH] Intensity harmonization in the anatomical workflow
  • Loading branch information
oesteban committed May 22, 2017
2 parents 0298bac + 56ef5c3 commit a761c37
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 17 deletions.
8 changes: 4 additions & 4 deletions mriqc/data/testdata/T1w.csv
@@ -1,5 +1,5 @@
subject_id,cjv,cnr,efc,fber,fwhm_avg,fwhm_x,fwhm_y,fwhm_z,icvs_csf,icvs_gm,icvs_wm,inu_med,inu_range,qi_1,qi_2,rpve_csf,rpve_gm,rpve_wm,size_x,size_y,size_z,snr_csf,snr_gm,snr_total,snr_wm,snrd_csf,snrd_gm,snrd_total,snrd_wm,spacing_x,spacing_y,spacing_z,summary_bg_k,summary_bg_mean,summary_bg_p05,summary_bg_p95,summary_bg_stdv,summary_csf_k,summary_csf_mean,summary_csf_p05,summary_csf_p95,summary_csf_stdv,summary_gm_k,summary_gm_mean,summary_gm_p05,summary_gm_p95,summary_gm_stdv,summary_wm_k,summary_wm_mean,summary_wm_p05,summary_wm_p95,summary_wm_stdv,tpm_overlap_csf,tpm_overlap_gm,tpm_overlap_wm,wm2max
50137,0.565541684627533,1.0896437168121338,0.5319478135739509,219.10040283203125,3.97176,3.84915,4.30615,3.75998,0.2133978898855149,0.42686969810291575,0.35973241201156936,1.2152518033981323,0.45196907520294216,0.0,0.0015283866670782967,1.8645534021456245,1.9502849035534549,4.081417886788103,208,256,176,2.238815903981205,8.85070887924841,8.49633122221015,14.399468883400834,-1.0,-1.0,-1.0,-1.0,1.0,1.0,1.0,8.661054560018206,6.259814262390137,0.0,25.0,9.651860237121582,79.4594446296568,115.38166046142578,62.0,155.0,50.372474670410156,-0.46137475899747793,186.69911193847656,141.0,241.0,30.38212776184082,-0.40774409895001495,286.1103210449219,226.0,348.0,37.21195983886719,0.23382176458835602,0.514546811580658,0.5550331473350525,0.46006389776357826
50152,0.5132028460502625,7.670464038848877,0.5214256578867659,202.1680450439453,3.4652784600909166,3.4267453802727488,3.65075,3.31834,0.16868135537307122,0.4885912301853383,0.3427274144415905,0.9292771816253662,0.3650368571281435,0.0,0.00640636674822209,1.765594289508632,2.3376199943253915,3.2912439466622065,160,239,200,3.5005435256874744,11.199840721944808,9.55084757500443,13.95215847738101,6.01660690446043,9.782957096938803,9.95264372741453,14.058367180844355,1.100000023841858,1.0,1.0,-1.0660642845540835,9.22282600402832,0.0,22.0,8.128961563110352,6.375584361190979,125.77245330810547,63.0,176.0,36.377098083496094,-0.6639384790376175,205.5374755859375,155.0,260.0,31.786623001098633,-0.35423826045711815,303.6893005371094,245.0,362.0,35.58559036254883,0.2079515904188156,0.5339961051940918,0.5221186280250549,0.591796875
50785,0.7246322631835938,1.0023349523544312,0.3827107285546845,633.4791259765625,3.352164997200236,3.199,3.611644991600707,3.24585,0.20414998904652737,0.4384699518998948,0.35738005905357784,1.3165277242660522,0.1928600192070007,0.0,0.001401353360826457,1.756238682956272,1.93681274168809,2.699256129878401,256,180,256,3.314242754568008,7.7660285522071275,6.436361165127116,8.228812188606211,-1.0,-1.0,-1.0,-1.0,1.0,0.9999875426292419,1.0,471.69220114417385,0.5898849368095398,0.0,0.0,6.1377482414245605,3.9118672776407806,267.7598876953125,122.0,362.0,75.62522888183594,-0.7159644876334115,455.7116394042969,350.0,569.0,67.79915618896484,0.6003910857172552,709.3773803710938,569.0,880.0,97.70134735107422,0.20489205420017242,0.49960723519325256,0.5370237827301025,0.3732545649838883
51187,0.6709977984428406,1.0940006971359253,0.3998666109215282,319.3792419433594,3.7801451255770178,4.346218421569919,3.1878984798915484,3.8063184752695856,0.18837399720708245,0.4525009740272195,0.359125028765698,1.7580251693725586,0.6602846145629884,0.0,0.00011083487655092947,1.4883090436864055,1.9651207274425997,3.038739081909938,256,132,256,2.5933751628444095,6.454748681549273,5.754041768760325,8.21400146188729,-1.0,-1.0,-1.0,-1.0,0.8593999743461609,1.5000007152557373,0.8593999743461609,6.751267645328532,86.73345947265625,0.0,361.0,134.44102478027344,126.45728715460484,2106.998046875,1028.0,2964.0,847.3251953125,0.04866928122287417,3658.23828125,2545.0,5084.0,777.8174438476562,-0.1645218582127237,6219.9453125,4467.0,8255.0,1181.12939453125,0.17126674950122833,0.48971468210220337,0.5144017338752747,0.3484224965706447
50137,0.5655418038368225,1.0896434783935547,0.5319478533169331,219.10037231445312,3.97176,3.84915,4.30615,3.75998,0.2133978898855149,0.42686969810291575,0.35973241201156936,1.2152518033981323,0.45196907520294216,0.0,0.0015283866670782967,1.8645534021456245,1.9502849035534549,4.081417886788103,208,256,176,2.2388158155068605,8.850709180011705,8.496331340677312,14.399469026513371,-1.0,-1.0,-1.0,-1.0,1.0,1.0,1.0,10.025728583421737,24.52124786376953,0.0,97.97048645019531,38.064552307128906,71.05386307848232,434.1557922363281,227.0186668395996,552.9233245849609,170.4384002685547,-0.7402684607533514,679.5755004882812,556.08193359375,817.7769226074219,81.76122283935547,0.20637971830494006,970.2426147460938,837.7473327636719,1088.440051269531,77.92474365234375,0.23382176458835602,0.514546811580658,0.5550331473350525,0.424328142234561
50152,0.5132023692131042,7.670464992523193,0.5214256578867659,202.168212890625,3.4652784600909166,3.4267453802727488,3.65075,3.31834,0.16868135537307122,0.4885912301853383,0.3427274144415905,0.9292771816253662,0.3650368571281435,0.0,0.00640636674822209,1.765594289508632,2.3376199943253915,3.2912439466622065,160,239,200,3.5005434525757626,11.199842877239929,9.550847751525884,13.95215692476196,6.016607014160164,9.7829576124543,9.952644185737848,14.058367930599081,1.100000023841858,1.0,1.0,-0.9237421578735807,34.02091598510742,0.0,82.92952270507811,30.356678009033203,4.409982278118531,449.5712585449219,234.48362884521484,590.3638305664061,120.90904998779297,-0.2643032225866122,693.541748046875,589.0667755126954,813.6859313964844,67.97644805908203,-0.3518840615356651,963.1089477539062,819.515267944336,1074.1770629882812,78.13372802734375,0.2079515904188156,0.5339961051940918,0.5221186280250549,0.5589662953179234
50785,0.7246319651603699,1.0023353099822998,0.3827107984006105,633.478515625,3.352164997200236,3.199,3.611644991600707,3.24585,0.20414998904652737,0.4384699518998948,0.35738005905357784,1.3165277242660522,0.1928600192070007,0.0,0.001401353360826457,1.756238682956272,1.93681274168809,2.699256129878401,256,180,256,3.314242591580933,7.766029217071026,6.436361123922026,8.22881156311412,-1.0,-1.0,-1.0,-1.0,1.0,0.9999875426292419,1.0,423.96144804863866,0.8749421834945679,0.0,0.0,8.935418128967285,4.661123994028605,369.53472900390625,170.515380859375,484.2899856567383,102.40303039550781,-0.9001679042967661,624.609375,490.3605773925781,766.7576904296874,88.27824401855469,1.2987212736798055,955.612060546875,790.9808044433594,1161.2386169433594,119.04894256591797,0.20489205420017242,0.49960723519325256,0.5370237827301025,0.4205544698159526
51187,0.6709979772567749,1.0940005779266357,0.3998666524946227,319.37921142578125,3.7801451255770178,4.346218421569919,3.1878984798915484,3.8063184752695856,0.18837399720708245,0.4525009740272195,0.359125028765698,1.7580251693725586,0.6602846145629884,0.0,0.00011083487655092947,1.4883090436864055,1.9651207274425997,3.038739081909938,256,132,256,2.593375073525042,6.454747956898468,5.754041599444694,8.214001767910572,-1.0,-1.0,-1.0,-1.0,0.8593999743461609,1.5000007152557373,0.8593999743461609,6.319455961407572,15.678553581237793,0.0,64.85365295410156,24.06060028076172,148.0743648674437,352.1060485839844,171.95945434570314,469.4457092285156,129.7559814453125,-0.7299780165339089,594.0829467773438,445.5936584472656,750.001708984375,95.08065795898438,-0.029851327734237376,941.2471313476562,764.3513000488281,1118.777001953125,113.59614562988281,0.17126674950122833,0.48971468210220337,0.5144017338752747,0.32675716205523264
2 changes: 1 addition & 1 deletion mriqc/interfaces/__init__.py
Expand Up @@ -8,7 +8,7 @@
from __future__ import absolute_import
from __future__ import unicode_literals

from mriqc.interfaces.anatomical import StructuralQC, ArtifactMask, ComputeQI2
from mriqc.interfaces.anatomical import StructuralQC, ArtifactMask, ComputeQI2, Harmonize
from mriqc.interfaces.functional import FunctionalQC, Spikes
from mriqc.interfaces.bids import ReadSidecarJSON, IQMFileSink
from mriqc.interfaces.viz import PlotMosaic, PlotContours, PlotSpikes
Expand Down
60 changes: 53 additions & 7 deletions mriqc/interfaces/anatomical.py
Expand Up @@ -7,7 +7,7 @@
# @Date: 2016-01-05 11:29:40
# @Email: code@oscaresteban.es
# @Last modified by: oesteban
# @Last Modified time: 2017-05-17 16:54:26
# @Last Modified time: 2017-05-19 16:36:34
""" Nipype interfaces to support anatomical workflow """
from __future__ import print_function, division, absolute_import, unicode_literals
import os.path as op
Expand All @@ -17,6 +17,7 @@
from builtins import zip

from nipype import logging
from nipype.utils.filemanip import fname_presuffix
from nipype.interfaces.base import (traits, TraitedSpec, File,
InputMultiPath, BaseInterfaceInputSpec)

Expand Down Expand Up @@ -122,7 +123,7 @@ def _run_interface(self, runtime): # pylint: disable=R0914
self._results['efc'] = efc(inudata)

# M2WM
self._results['wm2max'] = wm2max(imdata, segdata)
self._results['wm2max'] = wm2max(inudata, segdata)

# Artifacts
self._results['qi_1'] = art_qi1(airdata, artdata)
Expand All @@ -147,18 +148,18 @@ def _run_interface(self, runtime): # pylint: disable=R0914
self._results['rpve'] = rpve(pvmdata, segdata)

# Summary stats
self._results['summary'] = summary_stats(imdata, pvmdata, airdata)
self._results['summary'] = summary_stats(inudata, pvmdata, airdata)

# Image specs
self._results['size'] = {'x': int(imdata.shape[0]),
'y': int(imdata.shape[1]),
'z': int(imdata.shape[2])}
self._results['size'] = {'x': int(inudata.shape[0]),
'y': int(inudata.shape[1]),
'z': int(inudata.shape[2])}
self._results['spacing'] = {
i: float(v) for i, v in zip(
['x', 'y', 'z'], imnii.get_header().get_zooms()[:3])}

try:
self._results['size']['t'] = int(imdata.shape[3])
self._results['size']['t'] = int(inudata.shape[3])
except IndexError:
pass

Expand Down Expand Up @@ -278,6 +279,51 @@ def _run_interface(self, runtime):
return runtime


class HarmonizeInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True, mandatory=True, desc='input data (after bias correction)')
wm_mask = File(exists=True, mandatory=True, desc='white-matter mask')
erodemsk = traits.Bool(True, usedefault=True, desc='erode mask')


class HarmonizeOutputSpec(TraitedSpec):
out_file = File(exists=True, desc='input data (after intensity harmonization)')


class Harmonize(SimpleInterface):
"""
Computes the artifact mask using the method described in [Mortamet2009]_.
"""
input_spec = HarmonizeInputSpec
output_spec = HarmonizeOutputSpec

def _run_interface(self, runtime):

in_file = nb.load(self.inputs.in_file)
wm_mask = nb.load(self.inputs.wm_mask).get_data()
wm_mask[wm_mask < 0.9] = 0
wm_mask[wm_mask > 0] = 1
wm_mask = wm_mask.astype(np.uint8)

if self.inputs.erodemsk:
# Create a structural element to be used in an opening operation.
struc = nd.generate_binary_structure(3, 2)
# Perform an opening operation on the background data.
wm_mask = nd.binary_opening(wm_mask, structure=struc).astype(np.uint8)

data = in_file.get_data()
data *= 1000.0 / np.median(data[wm_mask > 0])

out_file = fname_presuffix(self.inputs.in_file,
suffix='_harmonized', newpath='.')
in_file.__class__(data, in_file.affine, in_file.header).to_filename(
out_file)

self._results['out_file'] = out_file

return runtime



def artifact_mask(imdata, airdata, distance, zscore=10.):
"""Computes a mask of artifacts found in the air region"""
from statsmodels.robust.scale import mad
Expand Down
17 changes: 13 additions & 4 deletions mriqc/workflows/anatomical.py
Expand Up @@ -95,7 +95,7 @@ def anat_qc_workflow(dataset, settings, mod='T1w', name='anatMRIQC'):
# 1. Reorient anatomical image
to_ras = pe.Node(ConformImage(), name='conform')
# 2. Skull-stripping (afni)
asw = skullstrip_wf(n4_nthreads=settings.get('ants_nthreads', 1))
asw = skullstrip_wf(n4_nthreads=settings.get('ants_nthreads', 1), unifize=False)
# 3. Head mask
hmsk = headmsk_wf()
# 4. Spatial Normalization, using ANTs
Expand Down Expand Up @@ -237,6 +237,7 @@ def compute_iqms(settings, modality='T1w', name='ComputeIQMs'):
"""
from mriqc.workflows.utils import _tofloat
from mriqc.interfaces.anatomical import Harmonize

workflow = pe.Workflow(name=name)
inputnode = pe.Node(niu.IdentityInterface(fields=[
Expand All @@ -253,6 +254,9 @@ def compute_iqms(settings, modality='T1w', name='ComputeIQMs'):
fwhm = pe.Node(afni.FWHMx(combine=True, detrend=True), name='smoothness')
# fwhm.inputs.acf = True # add when AFNI >= 16

# Harmonize
homog = pe.Node(Harmonize(), name='harmonize')

# Mortamet's QI2
getqi2 = pe.Node(ComputeQI2(erodemsk=settings.get('testing', False)),
name='ComputeQI2')
Expand All @@ -272,6 +276,9 @@ def compute_iqms(settings, modality='T1w', name='ComputeIQMs'):
name='datasink')
datasink.inputs.modality = modality

def _getwm(inlist):
return inlist[-1]

workflow.connect([
(inputnode, datasink, [('subject_id', 'subject_id'),
('session_id', 'session_id'),
Expand All @@ -281,8 +288,9 @@ def compute_iqms(settings, modality='T1w', name='ComputeIQMs'):
('metadata', 'metadata')]),
(inputnode, getqi2, [('orig', 'in_file'),
('airmask', 'air_msk')]),
(inputnode, measures, [('inu_corrected', 'in_noinu'),
('in_inu', 'in_bias'),
(inputnode, homog, [('inu_corrected', 'in_file'),
(('pvms', _getwm), 'wm_mask')]),
(inputnode, measures, [('in_inu', 'in_bias'),
('orig', 'in_file'),
('airmask', 'air_msk'),
('headmask', 'head_msk'),
Expand All @@ -293,12 +301,13 @@ def compute_iqms(settings, modality='T1w', name='ComputeIQMs'):
('brainmask', 'mask')]),
(inputnode, invt, [('orig', 'reference_image'),
('inverse_composite_transform', 'transforms')]),
(homog, measures, [('out_file', 'in_noinu')]),
(invt, measures, [('output_image', 'mni_tpms')]),
(fwhm, measures, [(('fwhm', _tofloat), 'in_fwhm')]),
(measures, datasink, [('out_qc', 'root')]),
(getqi2, datasink, [('qi2', 'qi_2')]),
(getqi2, outputnode, [('out_file', 'out_noisefit')]),
(datasink, outputnode, [('out_file', 'out_file')])
(datasink, outputnode, [('out_file', 'out_file')]),
])
return workflow

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
@@ -1,3 +1,3 @@
numpy>=1.12.0
git+https://github.com/nipy/nipype.git@f92a333b0d191773aa9cefd87fba869b87ab1414#egg=nipype
git+https://github.com/poldracklab/niworkflows.git@8b0838a6717904c9c171486d5fddb6568202ef5a#egg=niworkflows
git+https://github.com/poldracklab/niworkflows.git@57f9609790628bed0285823943403472e95ac70f#egg=niworkflows
4 changes: 4 additions & 0 deletions tests/nii_T1w.txt
Expand Up @@ -34,15 +34,19 @@ aaafd84713bf0e7b43e45845c3c93228de07e735 /scratch/work/workflow_enumerator/anatM
5feb2b8a1ff88237d5631f5e66a80304f21778d3 /scratch/work/workflow_enumerator/anatMRIQCT1w/AirMaskWorkflow/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/ArtifactMask/sub-51187_T1w_conformed_noart-air.nii.gz
c75931ec5f1e1f5104bd25b90507be24e484aba0 /scratch/work/workflow_enumerator/anatMRIQCT1w/AirMaskWorkflow/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/Binarize/1mm_headmask_trans_bin.nii.gz
083245f8226f08929d764e5d599665e84ee43d1f /scratch/work/workflow_enumerator/anatMRIQCT1w/AirMaskWorkflow/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/invert_xfm/1mm_headmask_trans.nii.gz
0089f13ee49aa6a1e6d23e902d5478bc0e455709 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50137..anat..sub-50137_T1w.nii.gz/harmonize/sub-50137_T1w_conformed_corrected_harmonized.nii.gz
29cb0e176238cfdc8a8d0762cfc17792299d1d4e /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50137..anat..sub-50137_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t10/1mm_tpm_csf_trans.nii.gz
52923c01b6af7557869ba5080c9f3915fca971a6 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50137..anat..sub-50137_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t11/1mm_tpm_gm_trans.nii.gz
13b33085ef4661932f79575c704fad84dc3feb27 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50137..anat..sub-50137_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t12/1mm_tpm_wm_trans.nii.gz
694f1916b589e295304f01b21f20d4a207c35a32 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50152..anat..sub-50152_T1w.nii.gz/harmonize/sub-50152_T1w_conformed_corrected_harmonized.nii.gz
6e8c56697a9b42d687f94389e9032234a12815fd /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50152..anat..sub-50152_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t10/1mm_tpm_csf_trans.nii.gz
896a69f791936edc6b3a8df7b178e685a7fb4ce3 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50152..anat..sub-50152_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t11/1mm_tpm_gm_trans.nii.gz
b35d258e85fb743a68c22d15781963901b53d260 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50152..anat..sub-50152_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t12/1mm_tpm_wm_trans.nii.gz
97d4f3b45f8e3bce616974ed2670de29af15dc6b /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50785..anat..sub-50785_T1w.nii.gz/harmonize/sub-50785_T1w_conformed_corrected_harmonized.nii.gz
9c64016b484ba0010d339a5ffa3fb02c56ce2979 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50785..anat..sub-50785_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t10/1mm_tpm_csf_trans.nii.gz
3558eb0db57f86fefb1d32ada58b7577c7862edb /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50785..anat..sub-50785_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t11/1mm_tpm_gm_trans.nii.gz
7c199ce85f066e71ad4b7155d7447eee208499e7 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-50785..anat..sub-50785_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t12/1mm_tpm_wm_trans.nii.gz
31ad2f11cdf1dd7ea473abcb19388c9e8859d54e /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/harmonize/sub-51187_T1w_conformed_corrected_harmonized.nii.gz
ecd3eec83a91571c53ea8e5e7cd798dd7c53b5fc /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t10/1mm_tpm_csf_trans.nii.gz
4747a4ba51f8722250422e246c09b9548215a162 /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t11/1mm_tpm_gm_trans.nii.gz
1f11d6ab154435bd2455363597ab99cbfa1735fa /scratch/work/workflow_enumerator/anatMRIQCT1w/ComputeIQMs/_in_file_..data..circle-tests..sub-51187..anat..sub-51187_T1w.nii.gz/MNItpms2t1/mapflow/_MNItpms2t12/1mm_tpm_wm_trans.nii.gz
Expand Down

0 comments on commit a761c37

Please sign in to comment.