def maget(imgs: List[MincAtom], options, prefix, output_dir, build_model_xfms=None): # FIXME prefix, output_dir aren't used !! s = Stages() maget_options = options.maget.maget resolution = options.registration.resolution # TODO or get_resolution_from_file(...) -- only if file always exists! pipeline_sub_dir = os.path.join( options.application.output_directory, options.application.pipeline_name + "_atlases") atlases = get_atlases(maget_options, pipeline_sub_dir) lsq12_conf = get_linear_configuration_from_options( options.maget.lsq12, transform_type=LinearTransType.lsq12, file_resolution=resolution) nlin_component = get_nonlinear_component(options.maget.nlin.reg_method) # TODO should this be here or outside `maget` call? #imgs = [s.defer(nlin_component.ToMinc.from_mnc(img)) for img in imgs] #nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol, # next(iter(options.maget.nlin.flags_.nlin_protocol)), # reg_method=options.maget.nlin.reg_method, # file_resolution=resolution) if maget_options.mask or maget_options.mask_only: # this used to return alignments but doesn't currently do so masked_img = s.defer( maget_mask( imgs=imgs, maget_options=options.maget, atlases=atlases, pipeline_sub_dir=pipeline_sub_dir + "_masking", # FIXME repeats all alignments!!! resolution=resolution)) # now propagate only the masked form of the images and atlases: imgs = masked_img #atlases = masked_atlases # TODO is this needed? if maget_options.mask_only: # register each input to each atlas, creating a mask return Result( stages=s, output=masked_img) # TODO rename `alignments` to `registrations`?? else: if maget_options.mask: del masked_img # this `del` is just to verify that we don't accidentally use this later, since these potentially # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same, # hash-consing will take care of things), just the masked images they create; can be removed later # if a sensible use is found # images with labels from atlases # N.B.: Even though we've already registered each image to each initial atlas, this happens again here, # but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options. # This is not 'work-efficient' in the sense that this computation happens twice (although # hopefully at greater precision the second time!), but the idea is to run a coarse initial # registration to get a mask and then do a better registration with that mask (though I'm not # sure exactly when this is faster than doing a single registration). # This _can_ allow the overall computation to finish more rapidly # (depending on the relative speed of the two alignment methods/parameters, # number of atlases and other templates used, number of cores available, etc.). atlas_labelled_imgs = ( pd.DataFrame({ 'img': img, 'label_file': s.defer( # can't use `label` in a pd.DataFrame index! mincresample_new( img=atlas.labels, xfm=s.defer( lsq12_nlin( source=img, target=atlas, nlin_module=nlin_component, lsq12_conf=lsq12_conf, nlin_options=options.maget.nlin.nlin_protocol, resolution=resolution, #nlin_conf=nlin_hierarchy, resample_source=False)).xfm, like=img, invert=True, interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range', '-labels'))) } for img in imgs for atlas in atlases)) if maget_options.pairwise: def choose_new_templates(ts, n): # currently silly, but we might implement a smarter method ... # FIXME what if there aren't enough other imgs around?! This silently goes weird ... return pd.Series( ts[:n + 1] ) # n+1 instead of n: choose one more since we won't use image as its own template ... # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates # (and just use all atlases) # TODO we could have a separate templates_csv (or --template-files f [f ...]) but you can just # run a separate MAGeT pipeline and #if maget_options.templates_csv: # templates = pd.read_csv(maget_options.templates_csv).template #else: templates = pd.DataFrame({ 'template': choose_new_templates(ts=imgs, n=maget_options.max_templates - len(atlases)) }) # note these images are the masked ones if masking was done ... # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter) labelled_templates = pd.merge(left=atlas_labelled_imgs, right=templates, left_on="img", right_on="template").drop('img', axis=1) # images with new labels from the templates imgs_and_templates = pd.merge( #left=atlas_labelled_imgs, left=pd.DataFrame({ "img": imgs }).assign(fake=1), right=labelled_templates.assign(fake=1), on='fake') #left_on='img', right_on='template') # TODO do select here instead of below? #if build_model_xfms is not None: # # use path instead of full mincatom as key in case we're reading these in from a CSV: # xfm_dict = { x.source.path : x.xfm for x in build_model_xfms } template_labelled_imgs = ( imgs_and_templates.rename( columns={'label_file': 'template_label_file'}) # don't register template to itself, since otherwise atlases would vote on that template twice .loc[lambda df: df.index.map(lambda ix: df.img[ix].path != df. template[ix].path)]. assign(label_file=lambda df: df.apply( axis=1, func=lambda row: s.defer( # TODO switch to uses of nlin_component.whatever(...) in several places below? mincresample_new( #nlin_component.Algorithms.resample( img=row.template_label_file, xfm=s.defer( lsq12_nlin( source=row.img, target=row.template, lsq12_conf=lsq12_conf, resolution=resolution, nlin_module=nlin_component, nlin_options=options.maget.nlin. nlin_protocol, #nlin_conf=nlin_hierarchy, resample_source=False)).xfm if build_model_xfms is None # use transforms from model building if we have them: else s.defer( xfmconcat( #nlin_component.Algorithms.concat( [ build_model_xfms[row.img.path], s.defer( xfminvert( #nlin_component.Algorithms.invert( build_model_xfms[row.template. path], subdir="tmp")) ])), like=row.img, invert=True, #use_nn_interpolation=True interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range', '-labels'))))) ) if len(imgs) > 1 else pd.DataFrame({ 'img': [], 'label_file': [] }) # ... as no distinct templates to align if only one image supplied (#320) imgs_with_all_labels = pd.concat([ atlas_labelled_imgs[['img', 'label_file']], template_labelled_imgs[['img', 'label_file']] ], ignore_index=True) else: imgs_with_all_labels = atlas_labelled_imgs #imgs_with_all_labels = imgs_with_all_labels.applymap( # lambda x: s.defer(nlin_component.ToMinc.to_mnc(x))) segmented_imgs = (imgs_with_all_labels.groupby('img').aggregate({ 'label_file': lambda resampled_label_files: list(resampled_label_files) }).rename(columns={ 'label_file': 'label_files' }).reset_index().assign(voted_labels=lambda df: df.apply( axis=1, func=lambda row: s.defer( voxel_vote(label_files=row.label_files, output_dir=os.path.join(row.img.pipeline_sub_dir, row.img.output_sub_dir), name=row.img.filename_wo_ext + "_voted")) )).apply(axis=1, func=lambda row: row.img._replace(labels=row.voted_labels))) return Result(stages=s, output=segmented_imgs)
def maget_mask(imgs : List[MincAtom], atlases, options): s = Stages() resample = np.vectorize(mincresample_new, excluded={"extra_flags"}) defer = np.vectorize(s.defer) lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12, LinearTransType.lsq12, options.registration.resolution) masking_nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.maget.masking_nlin_protocol, options.maget.maget.mask_method, options.registration.resolution) masking_alignments = pd.DataFrame({ 'img' : img, 'atlas' : atlas, 'xfm' : s.defer(lsq12_nlin(source=img, target=atlas, lsq12_conf=lsq12_conf, nlin_conf=masking_nlin_hierarchy, resample_source=False))} for img in imgs for atlas in atlases) # propagate a mask to each image using the above `alignments` as follows: # - for each image, voxel_vote on the masks propagated to that image to get a suitable mask # - run mincmath -clobber -mult <img> <voted_mask> to apply the mask to the files masked_img = ( masking_alignments .assign(resampled_mask=lambda df: defer(resample(img=df.atlas.apply(lambda x: x.mask), xfm=df.xfm.apply(lambda x: x.xfm), like=df.img, invert=True, interpolation=Interpolation.nearest_neighbour, postfix="-input-mask", subdir="tmp", # TODO annoying hack; fix mincresample(_mask) ...: #new_name_wo_ext=df.apply(lambda row: # "%s_to_%s-input-mask" % (row.atlas.filename_wo_ext, # row.img.filename_wo_ext), # axis=1), extra_flags=("-keep_real_range",)))) .groupby('img', sort=False, as_index=False) # sort=False: just for speed (might also need to implement more comparison methods on `MincAtom`s) .aggregate({'resampled_mask' : lambda masks: list(masks)}) .rename(columns={"resampled_mask" : "resampled_masks"}) .assign(voted_mask=lambda df: df.apply(axis=1, func=lambda row: s.defer(voxel_vote(label_files=row.resampled_masks, name="%s_voted_mask" % row.img.filename_wo_ext, output_dir=os.path.join(row.img.output_sub_dir, "tmp"))))) .assign(masked_img=lambda df: df.apply(axis=1, func=lambda row: s.defer(mincmath(op="mult", # img must precede mask here # for output image range to be correct: vols=[row.img, row.voted_mask], new_name="%s_masked" % row.img.filename_wo_ext, subdir="resampled"))))) #['img'] # resample the atlas images back to the input images: # (note: this doesn't modify `masking_alignments`, but only stages additional outputs) masking_alignments.assign(resampled_img=lambda df: defer(resample(img=df.atlas, xfm=df.xfm.apply(lambda x: x.xfm), subdir="tmp", # TODO delete this stupid hack: #new_name_wo_ext=df.apply(lambda row: # "%s_to_%s-resampled" % (row.atlas.filename_wo_ext, # row.img.filename_wo_ext), # axis=1), like=df.img, invert=True))) # replace the table of alignments with a new one with masked images masking_alignments = (pd.merge(left=masking_alignments.assign(unmasked_img=lambda df: df.img), right=masked_img, on=["img"], how="right", sort=False) .assign(img=lambda df: df.masked_img)) return Result(stages=s, output=masking_alignments)
def maget(imgs : List[MincAtom], options, prefix, output_dir, build_model_xfms=None): # FIXME prefix, output_dir aren't used !! s = Stages() maget_options = options.maget.maget resolution = options.registration.resolution # TODO or get_resolution_from_file(...) -- only if file always exists! pipeline_sub_dir = os.path.join(options.application.output_directory, options.application.pipeline_name + "_atlases") atlases = get_atlases(maget_options, pipeline_sub_dir) lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12, transform_type=LinearTransType.lsq12, file_resolution=resolution) nlin_component = get_nonlinear_component(options.maget.nlin.reg_method) # TODO should this be here or outside `maget` call? #imgs = [s.defer(nlin_component.ToMinc.from_mnc(img)) for img in imgs] #nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol, # next(iter(options.maget.nlin.flags_.nlin_protocol)), # reg_method=options.maget.nlin.reg_method, # file_resolution=resolution) if maget_options.mask or maget_options.mask_only: # this used to return alignments but doesn't currently do so masked_img = s.defer(maget_mask(imgs=imgs, maget_options=options.maget, atlases=atlases, pipeline_sub_dir=pipeline_sub_dir + "_masking", # FIXME repeats all alignments!!! resolution=resolution)) # now propagate only the masked form of the images and atlases: imgs = masked_img #atlases = masked_atlases # TODO is this needed? if maget_options.mask_only: # register each input to each atlas, creating a mask return Result(stages=s, output=masked_img) # TODO rename `alignments` to `registrations`?? else: if maget_options.mask: del masked_img # this `del` is just to verify that we don't accidentally use this later, since these potentially # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same, # hash-consing will take care of things), just the masked images they create; can be removed later # if a sensible use is found # images with labels from atlases # N.B.: Even though we've already registered each image to each initial atlas, this happens again here, # but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options. # This is not 'work-efficient' in the sense that this computation happens twice (although # hopefully at greater precision the second time!), but the idea is to run a coarse initial # registration to get a mask and then do a better registration with that mask (though I'm not # sure exactly when this is faster than doing a single registration). # This _can_ allow the overall computation to finish more rapidly # (depending on the relative speed of the two alignment methods/parameters, # number of atlases and other templates used, number of cores available, etc.). atlas_labelled_imgs = ( pd.DataFrame({ 'img' : img, 'label_file' : s.defer( # can't use `label` in a pd.DataFrame index! mincresample_new(img=atlas.labels, xfm=s.defer(lsq12_nlin(source=img, target=atlas, nlin_module=nlin_component, lsq12_conf=lsq12_conf, nlin_options=options.maget.nlin.nlin_protocol, resolution=resolution, #nlin_conf=nlin_hierarchy, resample_source=False)).xfm, like=img, invert=True, interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range', '-labels')))} for img in imgs for atlas in atlases) ) if maget_options.pairwise: def choose_new_templates(ts, n): # currently silly, but we might implement a smarter method ... # FIXME what if there aren't enough other imgs around?! This silently goes weird ... return pd.Series(ts[:n+1]) # n+1 instead of n: choose one more since we won't use image as its own template ... # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates # (and just use all atlases) # TODO we could have a separate templates_csv (or --template-files f [f ...]) but you can just # run a separate MAGeT pipeline and #if maget_options.templates_csv: # templates = pd.read_csv(maget_options.templates_csv).template #else: templates = pd.DataFrame({ 'template' : choose_new_templates(ts=imgs, n=maget_options.max_templates - len(atlases))}) # note these images are the masked ones if masking was done ... # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter) labelled_templates = pd.merge(left=atlas_labelled_imgs, right=templates, left_on="img", right_on="template").drop('img', axis=1) # images with new labels from the templates imgs_and_templates = pd.merge(#left=atlas_labelled_imgs, left=pd.DataFrame({ "img" : imgs }).assign(fake=1), right=labelled_templates.assign(fake=1), on='fake') #left_on='img', right_on='template') # TODO do select here instead of below? #if build_model_xfms is not None: # # use path instead of full mincatom as key in case we're reading these in from a CSV: # xfm_dict = { x.source.path : x.xfm for x in build_model_xfms } template_labelled_imgs = ( imgs_and_templates .rename(columns={ 'label_file' : 'template_label_file' }) # don't register template to itself, since otherwise atlases would vote on that template twice .loc[lambda df: df.index.map(lambda ix: df.img[ix].path != df.template[ix].path)] .assign(label_file=lambda df: df.apply(axis=1, func=lambda row: s.defer( # TODO switch to uses of nlin_component.whatever(...) in several places below? mincresample_new( #nlin_component.Algorithms.resample( img=row.template_label_file, xfm=s.defer( lsq12_nlin(source=row.img, target=row.template, lsq12_conf=lsq12_conf, resolution=resolution, nlin_module=nlin_component, nlin_options=options.maget.nlin.nlin_protocol, #nlin_conf=nlin_hierarchy, resample_source=False)).xfm if build_model_xfms is None # use transforms from model building if we have them: else s.defer( xfmconcat( #nlin_component.Algorithms.concat( [build_model_xfms[row.img.path], s.defer( xfminvert( #nlin_component.Algorithms.invert( build_model_xfms[row.template.path], subdir="tmp"))])), like=row.img, invert=True, #use_nn_interpolation=True interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range', '-labels') )))) ) if len(imgs) > 1 else pd.DataFrame({ 'img' : [], 'label_file' : [] }) # ... as no distinct templates to align if only one image supplied (#320) imgs_with_all_labels = pd.concat([atlas_labelled_imgs[['img', 'label_file']], template_labelled_imgs[['img', 'label_file']]], ignore_index=True) else: imgs_with_all_labels = atlas_labelled_imgs #imgs_with_all_labels = imgs_with_all_labels.applymap( # lambda x: s.defer(nlin_component.ToMinc.to_mnc(x))) segmented_imgs = ( imgs_with_all_labels .groupby('img') .aggregate({'label_file' : lambda resampled_label_files: list(resampled_label_files)}) .rename(columns={ 'label_file' : 'label_files' }) .reset_index() .assign(voted_labels=lambda df: df.apply(axis=1, func=lambda row: s.defer(voxel_vote(label_files=row.label_files, output_dir=os.path.join(row.img.pipeline_sub_dir, row.img.output_sub_dir), name=row.img.filename_wo_ext+"_voted")))) .apply(axis=1, func=lambda row: row.img._replace(labels=row.voted_labels)) ) return Result(stages=s, output=segmented_imgs)
def maget(imgs : List[MincAtom], options, prefix, output_dir): # FIXME prefix, output_dir aren't used !! s = Stages() maget_options = options.maget.maget pipeline_sub_dir = os.path.join(options.application.output_directory, options.application.pipeline_name + "_atlases") if maget_options.atlas_lib is None: raise ValueError("Need some atlases ...") #atlas_dir = os.path.join(output_dir, "input_atlases") ??? # TODO should alternately accept a CSV file ... atlas_library = read_atlas_dir(atlas_lib=maget_options.atlas_lib, pipeline_sub_dir=pipeline_sub_dir) if len(atlas_library) == 0: raise ValueError("No atlases found in specified directory '%s' ..." % options.maget.maget.atlas_lib) num_atlases_needed = min(maget_options.max_templates, len(atlas_library)) # TODO arbitrary; could choose atlases better ... atlases = atlas_library[:num_atlases_needed] # TODO issue a warning if not all atlases used or if more atlases requested than available? # TODO also, doesn't slicing with a higher number (i.e., if max_templates > n) go to the end of the list anyway? lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12, LinearTransType.lsq12, options.registration.resolution) masking_nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.maget.masking_nlin_protocol, options.maget.maget.mask_method, options.registration.resolution) nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol, options.maget.nlin.reg_method, options.registration.resolution) resample = np.vectorize(mincresample_new, excluded={"extra_flags"}) defer = np.vectorize(s.defer) # plan the basic registrations between all image-atlas pairs; store the result paths in a table masking_alignments = pd.DataFrame({ 'img' : img, 'atlas' : atlas, 'xfm' : s.defer(lsq12_nlin(source=img, target=atlas, lsq12_conf=lsq12_conf, nlin_conf=masking_nlin_hierarchy, resample_source=False))} for img in imgs for atlas in atlases) if maget_options.mask or maget_options.mask_only: masking_alignments = s.defer(maget_mask(imgs, atlases, options)) masked_atlases = atlases.apply(lambda atlas: s.defer(mincmath(op='mult', vols=[atlas, atlas.mask], subdir="resampled", new_name="%s_masked" % atlas.filename_wo_ext))) # now propagate only the masked form of the images and atlases: imgs = masking_alignments.img atlases = masked_atlases # TODO is this needed? if maget_options.mask_only: # register each input to each atlas, creating a mask return Result(stages=s, output=masking_alignments) # TODO rename `alignments` to `registrations`?? else: del masking_alignments # this `del` is just to verify that we don't accidentally use this later, since my intent is that these # coarser alignments shouldn't be re-used, just the masked images they create; can be removed later # if a sensible use is found if maget_options.pairwise: def choose_new_templates(ts, n): # currently silly, but we might implement a smarter method ... # FIXME what if there aren't enough other imgs around?! This silently goes weird ... return ts[:n+1] # n+1 instead of n: choose one more since we won't use image as its own template ... new_templates = choose_new_templates(ts=imgs, n=maget_options.max_templates) # note these images are the masked ones if masking was done ... # TODO write a function to do these alignments and the image->atlas one above # align the new templates chosen from the images to the initial atlases: new_template_to_atlas_alignments = ( pd.DataFrame({ 'img' : template, 'atlas' : atlas, 'xfm' : s.defer(lsq12_nlin(source=template, target=atlas, lsq12_conf=lsq12_conf, nlin_conf=nlin_hierarchy, resample_source=False))} for template in new_templates for atlas in atlases)) # ... and these atlases are multiplied by their masks (but is this necessary?) # label the new templates from resampling the atlas labels onto them: # TODO now vote on the labels to be used for the new templates ... # TODO extract into procedure? new_templates_labelled = ( new_template_to_atlas_alignments .assign(resampled_labels=lambda df: defer( resample(img=df.atlas.apply(lambda x: x.labels), xfm=df.xfm.apply(lambda x: x.xfm), interpolation=Interpolation.nearest_neighbour, extra_flags=("-keep_real_range",), like=df.img, invert=True))) .groupby('img', sort=False, as_index=False) .aggregate({'resampled_labels' : lambda labels: list(labels)}) .assign(voted_labels=lambda df: df.apply(axis=1, func=lambda row: s.defer(voxel_vote(label_files=row.resampled_labels, name="%s_template_labels" % row.img.filename_wo_ext, output_dir=os.path.join( row.img.pipeline_sub_dir, row.img.output_sub_dir, "labels")))))) # TODO write a procedure for this assign-groupby-aggregate-rename... # FIXME should be in above algebraic manipulation but MincAtoms don't support flexible immutable updating for row in pd.merge(left=new_template_to_atlas_alignments, right=new_templates_labelled, on=["img"], how="right", sort=False).itertuples(): row.img.labels = s.defer(mincresample_new(img=row.voted_labels, xfm=row.xfm.xfm, like=row.img, invert=True, interpolation=Interpolation.nearest_neighbour, #postfix="-input-labels", # this makes names really long ...: # TODO this doesn't work for running MAGeT on the nlin avg: #new_name_wo_ext="%s_on_%s" % # (row.voted_labels.filename_wo_ext, # row.img.filename_wo_ext), #postfix="_labels_via_%s" % row.xfm.xfm.filename_wo_ext, new_name_wo_ext="%s_via_%s" % (row.voted_labels.filename_wo_ext, row.xfm.xfm.filename_wo_ext), extra_flags=("-keep_real_range",))) # now that the new templates have been labelled, combine with the atlases: # FIXME use the masked atlases created earlier ?? all_templates = pd.concat([new_templates_labelled.img, atlases], ignore_index=True) # now take union of the resampled labels from the new templates with labels from the original atlases: #all_alignments = pd.concat([image_to_template_alignments, # alignments.rename(columns={ "atlas" : "template" })], # ignore_index=True, join="inner") else: all_templates = atlases # now register each input to each selected template # N.B.: Even though we've already registered each image to each initial atlas, this happens again here, # but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options. # This is not 'work-efficient' in the sense that this computation happens twice (although # hopefully at greater precision the second time!), but the idea is to run a coarse initial # registration to get a mask and then do a better registration with that mask (though I'm not # sure exactly when this is faster than doing a single registration). # This _can_ allow the overall computation to finish more rapidly # (depending on the relative speed of the two alignment methods/parameters, # number of atlases and other templates used, number of cores available, etc.). image_to_template_alignments = ( pd.DataFrame({ "img" : img, "template" : template_img, "xfm" : xfm } for img in imgs # TODO use the masked imgs here? for template_img in all_templates # FIXME delete this one alignment #labelled_templates[labelled_templates.img != img] # since equality is equality of filepaths (a bit dangerous) # TODO is there a more direct/faster way just to delete the template? for xfm in [s.defer(lsq12_nlin(source=img, target=template_img, lsq12_conf=lsq12_conf, nlin_conf=nlin_hierarchy))] ) ) # now do a voxel_vote on all resampled template labels, just as earlier with the masks voted = (image_to_template_alignments .assign(resampled_labels=lambda df: defer(resample(img=df.template.apply(lambda x: x.labels), # FIXME bug: at this point templates from template_alignments # don't have associated labels (i.e., `None`s) -- fatal xfm=df.xfm.apply(lambda x: x.xfm), interpolation=Interpolation.nearest_neighbour, extra_flags=("-keep_real_range",), like=df.img, invert=True))) .groupby('img', sort=False) # TODO the pattern groupby-aggregate(lambda x: list(x))-reset_index-assign is basically a hack # to do a groupby-assign with access to the group name; # see http://stackoverflow.com/a/30224447/849272 for a better solution # (note this pattern occurs several times in MAGeT and two-level code) .aggregate({'resampled_labels' : lambda labels: list(labels)}) .reset_index() .assign(voted_labels=lambda df: defer(np.vectorize(voxel_vote)(label_files=df.resampled_labels, output_dir=df.img.apply( lambda x: os.path.join( x.pipeline_sub_dir, x.output_sub_dir)))))) # TODO doing mincresample -invert separately for the img->atlas xfm for mask, labels is silly # (when Pydpiper's `mincresample` does both automatically)? # blargh, another destructive update ... for row in voted.itertuples(): row.img.labels = row.voted_labels # returning voted_labels as a column is slightly redundant, but possibly useful ... return Result(stages=s, output=voted) # voted.drop("voted_labels", axis=1))
def maget(imgs : List[MincAtom], options, prefix, output_dir): # FIXME prefix, output_dir aren't used !! s = Stages() maget_options = options.maget.maget resolution = options.registration.resolution # TODO or get_resolution_from_file(...) -- only if file always exists! pipeline_sub_dir = os.path.join(options.application.output_directory, options.application.pipeline_name + "_atlases") if maget_options.atlas_lib is None: raise ValueError("Need some atlases ...") # TODO should alternately accept a CSV file ... atlases = atlases_from_dir(atlas_lib=maget_options.atlas_lib, max_templates=maget_options.max_templates, pipeline_sub_dir=pipeline_sub_dir) lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12, transform_type=LinearTransType.lsq12, file_resolution=resolution) nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol, reg_method=options.maget.nlin.reg_method, file_resolution=resolution) if maget_options.mask or maget_options.mask_only: # this used to return alignments but doesn't currently do so masked_img = s.defer(maget_mask(imgs=imgs, maget_options=options.maget, atlases=atlases, pipeline_sub_dir=pipeline_sub_dir + "_masking", # FIXME repeats all alignments!!! resolution=resolution)) # now propagate only the masked form of the images and atlases: imgs = masked_img #atlases = masked_atlases # TODO is this needed? if maget_options.mask_only: # register each input to each atlas, creating a mask return Result(stages=s, output=masked_img) # TODO rename `alignments` to `registrations`?? else: if maget_options.mask: del masked_img # this `del` is just to verify that we don't accidentally use this later, since these potentially # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same, # hash-consing will take care of things), just the masked images they create; can be removed later # if a sensible use is found # images with labels from atlases # N.B.: Even though we've already registered each image to each initial atlas, this happens again here, # but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options. # This is not 'work-efficient' in the sense that this computation happens twice (although # hopefully at greater precision the second time!), but the idea is to run a coarse initial # registration to get a mask and then do a better registration with that mask (though I'm not # sure exactly when this is faster than doing a single registration). # This _can_ allow the overall computation to finish more rapidly # (depending on the relative speed of the two alignment methods/parameters, # number of atlases and other templates used, number of cores available, etc.). atlas_labelled_imgs = ( pd.DataFrame({ 'img' : img, 'label_file' : s.defer( # can't use `label` in a pd.DataFrame index! mincresample_new(img=atlas.labels, xfm=s.defer(lsq12_nlin(source=img, target=atlas, lsq12_conf=lsq12_conf, nlin_conf=nlin_hierarchy, resample_source=False)).xfm, like=img, invert=True, interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range',)))} for img in imgs for atlas in atlases) ) if maget_options.pairwise: def choose_new_templates(ts, n): # currently silly, but we might implement a smarter method ... # FIXME what if there aren't enough other imgs around?! This silently goes weird ... return pd.Series(ts[:n+1]) # n+1 instead of n: choose one more since we won't use image as its own template ... # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates # (and just use all atlases) templates = pd.DataFrame({ 'template' : choose_new_templates(ts=imgs, n=maget_options.max_templates - len(atlases))}) # note these images are the masked ones if masking was done ... # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter) labelled_templates = pd.merge(left=atlas_labelled_imgs, right=templates, left_on="img", right_on="template").drop('img', axis=1) # images with new labels from the templates imgs_and_templates = pd.merge(#left=atlas_labelled_imgs, left=pd.DataFrame({ "img" : imgs }).assign(fake=1), right=labelled_templates.assign(fake=1), on='fake') #left_on='img', right_on='template') # TODO do select here instead of below? template_labelled_imgs = ( imgs_and_templates .rename(columns={ 'label_file' : 'template_label_file' }) # don't register template to itself, since otherwise atlases would vote on that template twice .select(lambda ix: imgs_and_templates.img[ix].path != imgs_and_templates.template[ix].path) # TODO hardcoded name .assign(label_file=lambda df: df.apply(axis=1, func=lambda row: s.defer(mincresample_new(img=row.template_label_file, xfm=s.defer(lsq12_nlin(source=row.img, target=row.template, lsq12_conf=lsq12_conf, nlin_conf=nlin_hierarchy, resample_source=False)).xfm, like=row.img, invert=True, interpolation=Interpolation.nearest_neighbour, extra_flags=('-keep_real_range',))))) ) imgs_with_all_labels = pd.concat([atlas_labelled_imgs[['img', 'label_file']], template_labelled_imgs[['img', 'label_file']]], ignore_index=True) else: imgs_with_all_labels = atlas_labelled_imgs segmented_imgs = ( imgs_with_all_labels .groupby('img') .aggregate({'label_file' : lambda resampled_label_files: list(resampled_label_files)}) .rename(columns={ 'label_file' : 'label_files' }) .reset_index() .assign(voted_labels=lambda df: df.apply(axis=1, func=lambda row: s.defer(voxel_vote(label_files=row.label_files, output_dir=os.path.join(row.img.pipeline_sub_dir, row.img.output_sub_dir))))) .apply(axis=1, func=lambda row: row.img._replace(labels=row.voted_labels)) ) return Result(stages=s, output=segmented_imgs)