def queryAtlas(namespace): """Query an atlas with coordinates or masks.""" atlasDesc = identifyAtlas(namespace.atlas) wcoords = namespace.coord vcoords = namespace.voxel masks = [fslimage.Image(m) for m in namespace.mask] worder = namespace.coord_order vorder = namespace.voxel_order morder = namespace.mask_order atlas = fslatlases.loadAtlas(atlasDesc.atlasID, loadSummary=namespace.label, resolution=namespace.resolution) mlabels, mprops = maskQuery(atlas, masks) wlabels, wprops = coordQuery(atlas, wcoords, False) vlabels, vprops = coordQuery(atlas, vcoords, True) order = list(it.chain(morder, worder, vorder)) labels = list(it.chain(mlabels, wlabels, vlabels)) props = list(it.chain(mprops, wprops, vprops)) sources = list(it.chain(masks, wcoords, vcoords)) types = list( it.chain(['mask'] * len(masks), ['coordinate'] * len(wcoords), ['voxel'] * len(vcoords))) labels = [l for (o, l) in sorted(zip(order, labels))] props = [p for (o, p) in sorted(zip(order, props))] sources = [s for (o, s) in sorted(zip(order, sources))] types = [t for (o, t) in sorted(zip(order, types))] if namespace.short: queryShortOutput(atlas, sources, types, labels, props) else: queryLongOutput(atlas, sources, types, labels, props)
def get_ventricular_csf_mask(fslanatdir, interpolation=3): """ Get a ventricular mask in T1 space. Register the ventricles mask from the harvardoxford-subcortical 2mm atlas to T1 space, using the T1_to_MNI_nonlin_coeff.nii.gz in the provided fsl_anat directory. Parameters ---------- fslanatdir: pathlib.Path Path to an fsl_anat output directory. interpolation: int Order of interpolation to use when performing registration. Default is 3. """ # get ventricles mask from Harv-Ox atlases.rescanAtlases() harvox = atlases.loadAtlas('harvardoxford-subcortical', resolution=2.0) vent_img = Image( harvox.data[:,:,:,2] + harvox.data[:,:,:,13], header=harvox.header ) vent_img = fslmaths(vent_img).thr(0.1).bin().ero().run(LOAD) # apply inv(T1->MNI) registration t1_brain = (fslanatdir/"T1_biascorr_brain.nii.gz").resolve(strict=True) struct2mni = (fslanatdir/"T1_to_MNI_nonlin_coeff.nii.gz").resolve(strict=True) mni2struct = invwarp(str(struct2mni), str(t1_brain), LOAD)['out'] vent_t1_img = applywarp(vent_img, str(t1_brain), LOAD, warp=mni2struct)['out'] vent_t1_img = fslmaths(vent_t1_img).thr(0.9).bin().run(LOAD) return vent_t1_img
def atlasOrDesc(aord, *args, **kwargs): """If ``aord`` is an ``Atlas`` it is returned. Otherwise it is assumed to be an ``AtlasDescription``, in which case the corresponding ``Atlas`` is loaded and returned. """ if isinstance(aord, fslatlases.Atlas): return aord else: return fslatlases.loadAtlas(aord.atlasID, *args, **kwargs)
def Atlas(name): """ Reads in the atlas from the FSL standard atlases :param name: name of the atlas :return: fsl.data.atlases.Atlas representation of an FSL atlas """ atlases.rescanAtlases() if not atlases.hasAtlas(name): atlas_names = tuple(desc.atlasID for desc in atlases.listAtlases()) raise argparse.ArgumentTypeError('Requested atlas %r not one of: %r' % (name, atlas_names)) return atlases.loadAtlas(name)
def load(): atlas = atlases.loadAtlas(atlasID, summary, resolution=res) # The atlas panel may be destroyed # before the atlas is loaded. if not self or self.destroyed(): return self.__loadedAtlases[atlasID, summary, res] = atlas status.update('Atlas {} loaded.'.format(atlasID)) if onLoad is not None: idle.idle(onLoad, atlas)
def _get_atlas(atlasID, res, summary=False): atlas = _atlases.get((atlasID, res, summary), default=None) if atlas is None: atlas = fslatlases.loadAtlas(atlasID, loadSummary=summary, resolution=res) # We need some atlases to be loaded into memory, # so we can use boolean-mask-based indexing if summary or atlasID in ('talairach', 'striatum-structural', 'jhu-labels', 'smatt'): atlas.data _atlases.put((atlasID, res, summary), atlas) return atlas
def _get_atlas(atlasID, res, summary=False): atlas = _atlases.get((atlasID, res, summary), default=None) if atlas is None: if summary or atlasID in ('talairach', 'striatum-structural', 'jhu-labels'): kwargs = {} else: kwargs = {'loadData': False, 'calcRange': False, 'indexed': True} atlas = fslatlases.loadAtlas(atlasID, loadSummary=summary, resolution=res, **kwargs) _atlases.put((atlasID, res, summary), atlas) return atlas
def test_bad_mask(seed): fslatlases.rescanAtlases() capture = CaptureStdout() with tempdir() as td: for atlasID, use_label in it.product(atlases, use_labels): atlas = fslatlases.loadAtlas(atlasID, loadSummary=use_label, indexed=True, loadData=False, calcRange=False) ashape = list(atlas.shape[:3]) wrongdims = fslimage.Image( np.array(np.random.random(list(ashape) + [2]), dtype=np.float32)) wrongspace = fslimage.Image(np.random.random((20, 20, 20)), xform=transform.concat( atlas.voxToWorldMat, np.diag([2, 2, 2, 1]))) print(wrongdims.shape) print(wrongspace.shape) wrongdims.save('wrongdims.nii.gz') wrongspace.save('wrongspace.nii.gz') cmd = ['query', atlasID, '-m', 'wrongdims'] expected = 'Mask has wrong number of dimensions' capture.reset() with capture: assert fslatlasq.main(cmd) != 0 assert capture.stdout.strip() == expected cmd = ['query', atlasID, '-m', 'wrongspace'] expected = 'Mask is not in the same space as atlas' capture.reset() with capture: assert fslatlasq.main(cmd) != 0 assert capture.stdout.strip() == expected
def test_coords(seed): """Test the ohi -a "atlas" -c "coords" mode. """ def expectedProbOutput(atlas, coords): probs = atlas.values(coords) expected = '<b>{}</b><br>'.format(atlas.desc.name) nzprobs = [] for i, p in enumerate(probs): if p > 0: label = atlas.desc.labels[i].name nzprobs.append((p, label)) if len(nzprobs) > 0: nzprobs = reversed(sorted(nzprobs, key=lambda b: b[0])) nzprobs = [ '{:d}% {}'.format(int(round(p)), l) for (p, l) in nzprobs ] expected += ', '.join(nzprobs) else: expected += 'No label found!' return expected def expectedLabelOutput(atlas, coords): label = atlas.label(coords) expected = '<b>{}</b><br>'.format(atlas.desc.name) if label is None: return expected + 'Unclassified' else: return expected + atlas.desc.find(value=int(label)).name capture = CaptureStdout() # random coordinates in MNI152 space, # with some coordinates out of bounds ncoords = 50 xc = -100 + 190 * np.random.random(ncoords) yc = -130 + 220 * np.random.random(ncoords) zc = -80 + 120 * np.random.random(ncoords) coords = np.vstack((xc, yc, zc)).T fslatlases.rescanAtlases() atlases = fslatlases.listAtlases() for ad in atlases: # atlasquery/ohi always uses 2mm resolution atlas = fslatlases.loadAtlas(ad.atlasID, resolution=2) print(ad.name) for x, y, z in coords: cmd = 'ohi -a "{}" -c "{},{},{}"'.format(ad.name, x, y, z) capture.reset() with capture: fslatlasq.main(shlex.split(cmd)) if isinstance(atlas, fslatlases.ProbabilisticAtlas): expected = expectedProbOutput(atlas, (x, y, z)) # LabelAtlas else: expected = expectedLabelOutput(atlas, (x, y, z)) assert capture.stdout.strip() == expected.strip()
def test_mask(seed): """Test the ohi -a "atlas" -m "mask" mode, with label and probabilistic atlases. """ def expectedLabelOutput(mask, atlas): labels, props = atlas.maskLabel(mask) exp = [] for lbl, prop in zip(labels, props): name = desc.find(value=int(lbl)).name exp.append('{}:{:0.4f}'.format(name, prop)) return '\n'.join(exp) def expectedProbOutput(mask, atlas): props = atlas.maskValues(mask) labels = [l.index for l in atlas.desc.labels] exp = [] for lbl, prop in zip(labels, props): if prop > 0: exp.append('{}:{:0.4f}'.format(desc.labels[int(lbl)].name, prop)) return '\n'.join(exp) fslatlases.rescanAtlases() capture = CaptureStdout() atlases = fslatlases.listAtlases() with tempdir() as td: maskfile = op.join(td, 'mask.nii') for desc in atlases: # atlasquery always uses 2mm # resolution versions of atlases atlas2mm = fslatlases.loadAtlas(desc.atlasID, resolution=2) # Test with 1mm and 2mm masks for res in [1, 2]: atlasimg = fslatlases.loadAtlas(desc.atlasID, resolution=res) maskimg = make_random_mask(maskfile, atlasimg.shape[:3], atlasimg.voxToWorldMat) cmd = 'ohi -a "{}" -m {}'.format(desc.name, maskfile) print(cmd) capture.reset() with capture: fslatlasq.main(shlex.split(cmd)) if isinstance(atlasimg, fslatlases.LabelAtlas): expected = expectedLabelOutput(maskimg, atlas2mm) elif isinstance(atlasimg, fslatlases.ProbabilisticAtlas): expected = expectedProbOutput(maskimg, atlas2mm) assert capture.stdout.strip() == expected
def _get_atlas(aid, use_label, res): return fslatlases.loadAtlas(aid, loadSummary=use_label, resolution=res)
def setup_mtestimation(subject_dir, rois=[ 'wm', ]): """ Perform the initial processing needed for estimation of the MT Effect. This includes: - Creating sub-directories for storing the results - Finding T1 and mbPCASL directories and scans - Split mbPCASL sequence into its constituent components - Run fsl_anat - Create a json to keep track of important files and directories """ # get subject name subject_name = subject_dir.parts[-1] print(subject_name) # create results directories asl_dir = subject_dir / 'ASL' calib0_dir = asl_dir / 'Calib/Calib0' calib1_dir = asl_dir / 'Calib/Calib1' create_dirs([asl_dir, calib0_dir, calib1_dir]) # obtain calibration images from ASL sequence mbpcasl_dir = list( (subject_dir / f'{subject_name}_V1_B/scans').glob('**/*mbPCASLhr'))[0] mbpcasl = mbpcasl_dir / f'resources/NIFTI/files/{subject_name}_V1_B_mbPCASLhr_PA.nii.gz' calib0_name = calib0_dir / 'calib0.nii.gz' calib1_name = calib1_dir / 'calib1.nii.gz' fslroi(str(mbpcasl), calib0_name, 88, 1) fslroi(str(mbpcasl), calib1_name, 89, 1) # initialise dict json_name = asl_dir / 'ASL.json' important_dict = { "calib_dir": str(calib0_dir.parent), "calib0_dir": str(calib0_dir), "calib1_dir": str(calib1_dir), "calib0_img": str(calib0_name), "calib1_img": str(calib1_dir), "json_name": str(json_name) } # structural directory t1_dir = list( (subject_dir / f'{subject_name}_V1_A/scans').glob('**/*T1w'))[0] struc_dir = t1_dir / 'resources/NIFTI/files' struc_name = list(struc_dir.glob(f'**/{subject_name}_*.nii.gz'))[0] fsl_anat_dir = struc_dir / 'ASL/struc' calib0struct_dir = struc_dir / 'ASL/Calib/Calib0' calib1struct_dir = struc_dir / 'ASL/Calib/Calib1' create_dirs([calib0struct_dir, calib1struct_dir]) # run fsl_anat fsl_anat(str(struc_name), str(fsl_anat_dir), clobber=True, nosubcortseg=True) fsl_anat_dir = fsl_anat_dir.parent / f'{fsl_anat_dir.stem}.anat' t1_name = fsl_anat_dir / 'T1_biascorr.nii.gz' t1_brain_name = fsl_anat_dir / 'T1_biascorr_brain.nii.gz' # get ventricles mask if csf if 'csf' in rois: # initialise atlas list atlases.rescanAtlases() harv_ox_prob_2mm = atlases.loadAtlas('harvardoxford-subcortical', resolution=2.0) vent_img = Image(harv_ox_prob_2mm.data[:, :, :, 2] + harv_ox_prob_2mm.data[:, :, :, 13], header=harv_ox_prob_2mm.header) vent_img = fslmaths(vent_img).thr(0.1).bin().ero().run(LOAD) # we already have registration from T1 to MNI struc2mni_warp = fsl_anat_dir / 'MNI_to_T1_nonlin_field.nii.gz' # apply warp to ventricles image vent_t1_name = fsl_anat_dir / 'ventricles_mask.nii.gz' applywarp(vent_img, str(t1_brain_name), str(vent_t1_name), warp=str(struc2mni_warp)) # re-threshold vent_t1 = fslmaths(str(vent_t1_name)).thr( PVE_THRESHOLDS['csf']).bin().run(LOAD) # mask pve estimate by ventricles mask fslmaths(str(fsl_anat_dir / PVE_NAMES['csf'])).mas(vent_t1).run( str(vent_t1_name)) # bias-field correction for calib_name in (calib0_name, calib1_name): calib_name_stem = calib_name.stem.split('.')[0] # run bet betted_m0 = bet(str(calib_name), LOAD) # create directories to save results fast_dir = calib_name.parent / 'FAST' biascorr_dir = calib_name.parent / 'BiasCorr' create_dirs([fast_dir, biascorr_dir]) # run FAST on brain-extracted m0 image fast_base = fast_dir / calib_name_stem fast(betted_m0['output'], out=str(fast_base), type=3, b=True, nopve=True) bias_name = fast_dir / f'{calib_name_stem}_bias.nii.gz' # apply bias field to original m0 image (i.e. not BETted) biascorr_name = biascorr_dir / f'{calib_name_stem}_restore.nii.gz' fslmaths(str(calib_name)).div(str(bias_name)).run(str(biascorr_name)) # obtain registration from structural to calibration image mask_dir = biascorr_name.parent / 'masks' create_dirs([ mask_dir, ]) cmd = [ 'asl_reg', f'-i {biascorr_name}', f'-s {t1_name}', f'--sbet {t1_brain_name}', f'-o {mask_dir}' ] subprocess.run(" ".join(cmd), shell=True) # apply transformation to the pve map for tissue in rois: roi_dir = mask_dir / tissue create_dirs([ roi_dir, ]) if tissue == 'combined': # check that gm and wm pves already exist gm_pve = mask_dir / 'gm/pve_gm.nii.gz' wm_pve = mask_dir / 'wm/pve_wm.nii.gz' if not (gm_pve.exists() and wm_pve.exists()): raise Exception("WM and GM PVEs don't exist.") gm_mask_name = roi_dir / 'gm_mask.nii.gz' wm_mask_name = roi_dir / 'wm_mask.nii.gz' fslmaths(str(gm_pve)).thr(PVE_THRESHOLDS[tissue]).bin().run( str(gm_mask_name)) fslmaths(str(wm_pve)).thr(PVE_THRESHOLDS[tissue]).bin().run( str(wm_mask_name)) gm_masked_name = roi_dir / f'{calib_name_stem}_gm_masked' wm_masked_name = roi_dir / f'{calib_name_stem}_wm_masked' fslmaths(str(biascorr_name)).mul(str(gm_mask_name)).run( str(gm_masked_name)) fslmaths(str(biascorr_name)).mul(str(wm_mask_name)).run( str(wm_masked_name)) # update dictionary new_dict = { f'{calib_name_stem}_{tissue}_masked': [str(gm_masked_name), str(wm_masked_name)] } else: if tissue == 'csf': pve_struct_name = vent_t1_name else: pve_struct_name = fsl_anat_dir / PVE_NAMES[tissue] pve_asl_name = roi_dir / f'pve_{tissue}.nii.gz' struct2asl_name = mask_dir / 'struct2asl.mat' applyxfm(str(pve_struct_name), str(biascorr_name), str(struct2asl_name), str(pve_asl_name)) # threshold and binarise the ASL-space pve map mask_name = roi_dir / f'{tissue}_mask.nii.gz' fslmaths(str(pve_asl_name)).thr( PVE_THRESHOLDS[tissue]).bin().run(str(mask_name)) # apply the mask to the calibration image masked_name = mask_dir / f'{tissue}_masked.nii.gz' fslmaths(str(biascorr_name)).mul(str(mask_name)).run( str(masked_name)) # update dictionary new_dict = { f'{calib_name_stem}_{tissue}_masked': str(masked_name) } important_dict.update(new_dict) # save json with open(json_name, 'w') as fp: json.dump(important_dict, fp, sort_keys=True, indent=4) return (subject_dir, 0)