def resampleToCommon(xfm, FH, statsGroup, statsKernels, nlinFH): blurs = [] if isinstance(statsKernels, list): blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): blurs.append(float(i)) else: print "Improper type of blurring kernels specified for stats calculation: " + str(statsKernels) sys.exit() pipeline = Pipeline() outputDirectory = FH.statsDir filesToResample = [] for b in blurs: filesToResample.append(statsGroup.relativeJacobians[b]) if statsGroup.absoluteJacobians: filesToResample.append(statsGroup.absoluteJacobians[b]) for f in filesToResample: outputBase = fh.removeBaseAndExtension(f).split(".mnc")[0] outputFile = fh.createBaseName(outputDirectory, outputBase + "_common" + ".mnc") logFile = fh.logFromFile(FH.logDir, outputFile) targetAndLike=nlinFH.getLastBasevol() res = ma.mincresample(f, targetAndLike, likeFile=targetAndLike, transform=xfm, output=outputFile, logFile=logFile, argArray=["-sinc"]) pipeline.addStage(res) return pipeline
class SetResolution: def __init__(self, filesToResample, resolution): """During initialization make sure all files are resampled at resolution we'd like to use for each pipeline stage """ self.p = Pipeline() for FH in filesToResample: dirForOutput = self.getOutputDirectory(FH) currentRes = volumeFromFile(FH.getLastBasevol()).separations if not abs(abs(currentRes[0]) - abs(resolution)) < 0.01: crop = ma.autocrop(resolution, FH, defaultDir=dirForOutput) self.p.addStage(crop) mask = FH.getMask() if mask: #Need to resample the mask as well. cropMask = ma.mincresampleMask(FH, FH, outputLocation=FH, likeFile=FH) self.p.addStage(cropMask) def getOutputDirectory(self, FH): """Sets output directory based on whether or not we have a full RegistrationPipeFH class or we are just using RegistrationFHBase""" if isinstance(FH, rfh.RegistrationPipeFH): outputDir = "resampled" else: outputDir = FH.basedir return outputDir
def resampleToCommon(xfm, FH, statsGroup, statsKernels, nlinFH): blurs = [] if isinstance(statsKernels, list): blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): blurs.append(float(i)) else: print("Improper type of blurring kernels specified for stats calculation: " + str(statsKernels)) sys.exit() pipeline = Pipeline() outputDirectory = FH.statsDir filesToResample = [] for b in blurs: filesToResample.append(statsGroup.relativeJacobians[b]) if statsGroup.absoluteJacobians: filesToResample.append(statsGroup.absoluteJacobians[b]) for f in filesToResample: outputBase = removeBaseAndExtension(f).split(".mnc")[0] outputFile = createBaseName(outputDirectory, outputBase + "_common" + ".mnc") logFile = fh.logFromFile(FH.logDir, outputFile) targetAndLike=nlinFH.getLastBasevol() res = ma.mincresample(f, targetAndLike, likeFile=targetAndLike, transform=xfm, output=outputFile, logFile=logFile, argArray=["-sinc"]) pipeline.addStage(res) return pipeline
def maskFiles(FH, isAtlas, numAtlases=1): """ Assume that if there is more than one atlas, multiple masks were generated and we need to perform a voxel_vote. Otherwise, assume we are using inputLabels from crossing with only one atlas. """ #MF TODO: Make this more general to handle pairwise option. p = Pipeline() if not isAtlas: if numAtlases > 1: voxel = voxelVote(FH, False, True) p.addStage(voxel) mincMathInput = voxel.outputFiles[0] else: mincMathInput = FH.returnLabels(True)[0] FH.setMask(mincMathInput) else: mincMathInput = FH.getMask() mincMathOutput = fh.createBaseName(FH.resampledDir, FH.basename) mincMathOutput += "_masked.mnc" logFile = fh.logFromFile(FH.logDir, mincMathOutput) cmd = ["mincmath"] + ["-clobber"] + ["-mult"] cmd += [InputFile(mincMathInput)] + [InputFile(FH.getLastBasevol())] cmd += [OutputFile(mincMathOutput)] mincMath = CmdStage(cmd) mincMath.setLogFile(LogFile(logFile)) p.addStage(mincMath) FH.setLastBasevol(mincMathOutput) return (p)
def maskFiles(FH, isAtlas, numAtlases=1): """ Assume that if there is more than one atlas, multiple masks were generated and we need to perform a voxel_vote. Otherwise, assume we are using inputLabels from crossing with only one atlas. """ #MF TODO: Make this more general to handle pairwise option. p = Pipeline() if not isAtlas: if numAtlases > 1: voxel = voxelVote(FH, False, True) p.addStage(voxel) mincMathInput = voxel.outputFiles[0] else: mincMathInput = FH.returnLabels(True)[0] FH.setMask(mincMathInput) else: mincMathInput = FH.getMask() mincMathOutput = fh.createBaseName(FH.resampledDir, FH.basename) mincMathOutput += "_masked.mnc" logFile = fh.logFromFile(FH.logDir, mincMathOutput) cmd = ["mincmath"] + ["-clobber"] + ["-mult"] cmd += [InputFile(mincMathInput)] + [InputFile(FH.getLastBasevol())] cmd += [OutputFile(mincMathOutput)] mincMath = CmdStage(cmd) mincMath.setLogFile(LogFile(logFile)) p.addStage(mincMath) FH.setLastBasevol(mincMathOutput) return(p)
class LabelAndFileResampling: def __init__(self, inputPipeFH, templatePipeFH, name="initial", createMask=False): self.p = Pipeline() self.name = name if createMask: resampleDefault = "tmp" labelsDefault = "tmp" else: resampleDefault = "resampled" labelsDefault = "labels" # Resample all inputLabels inputLabelArray = templatePipeFH.returnLabels(True) if len(inputLabelArray) > 0: """ for the initial registration, resulting labels should be added to inputLabels array for subsequent pairwise registration otherwise labels should be added to labels array for voting """ if self.name == "initial": addOutputToInputLabels = True else: addOutputToInputLabels = False for i in range(len(inputLabelArray)): """Note: templatePipeFH and inputPipeFH have the reverse order from how they are passed into this function. This is intentional because the mincresample classes use the first argument as the one from which to get the file to be resampled. Here, either the mask or labels to be resampled come from the template.""" if createMask: resampleStage = ma.mincresampleMask(templatePipeFH, inputPipeFH, defaultDir=labelsDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH, labelIndex=i, setInputLabels=addOutputToInputLabels) else: resampleStage = ma.mincresampleLabels(templatePipeFH, inputPipeFH, defaultDir=labelsDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH, labelIndex=i, setInputLabels=addOutputToInputLabels) self.p.addStage(resampleStage) # resample files resampleStage = ma.mincresample(templatePipeFH, inputPipeFH, defaultDir=resampleDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH) self.p.addStage(resampleStage)
class FullIterativeLSQ12Nlin: """Does a full iterative LSQ12 and NLIN. Basically iterative model building starting from LSQ6 and without stats at the end. Designed to be called as part of a larger application. Specifying an initModel is optional, all other arguments are mandatory.""" def __init__(self, inputs, dirs, options, avgPrefix=None, initModel=None): self.inputs = inputs self.dirs = dirs self.options = options self.avgPrefix = avgPrefix self.initModel = initModel self.nlinFH = None self.p = Pipeline() self.buildPipeline() def buildPipeline(self): lsq12LikeFH = None if self.initModel: lsq12LikeFH = self.initModel[0] elif self.options.lsq12_likeFile: lsq12LikeFH = self.options.lsq12_likeFile lsq12module = lsq12.FullLSQ12(self.inputs, self.dirs.lsq12Dir, likeFile=lsq12LikeFH, maxPairs=self.options.lsq12_max_pairs, lsq12_protocol=self.options.lsq12_protocol, subject_matter=self.options.lsq12_subject_matter) lsq12module.iterate() self.p.addPipeline(lsq12module.p) self.lsq12Params = lsq12module.lsq12Params if lsq12module.lsq12AvgFH.getMask()== None: if self.initModel: lsq12module.lsq12AvgFH.setMask(self.initModel[0].getMask()) if not self.avgPrefix: self.avgPrefix = self.options.pipeline_name nlinModule = nlin.initializeAndRunNLIN(self.dirs.lsq12Dir, self.inputs, self.dirs.nlinDir, avgPrefix=self.avgPrefix, createAvg=False, targetAvg=lsq12module.lsq12AvgFH, nlin_protocol=self.options.nlin_protocol, reg_method=self.options.reg_method) self.p.addPipeline(nlinModule.p) self.nlinFH = nlinModule.nlinAverages[-1] self.nlinParams = nlinModule.nlinParams self.initialTarget = nlinModule.initialTarget # Now we need the full transform to go back to LSQ6 space for i in self.inputs: linXfm = lsq12module.lsq12AvgXfms[i] nlinXfm = i.getLastXfm(self.nlinFH) outXfm = st.createOutputFileName(i, nlinXfm, "transforms", "_with_additional.xfm") xc = ma.xfmConcat([linXfm, nlinXfm], outXfm, fh.logFromFile(i.logDir, outXfm)) self.p.addStage(xc) i.addAndSetXfmToUse(self.nlinFH, outXfm)
class HierarchicalMinctracc: """Default HierarchicalMinctracc currently does: 1. 2 lsq12 stages with a blur of 0.25 2. 5 nlin stages with a blur of 0.25 3. 1 nlin stage with no blur""" def __init__(self, inputPipeFH, templatePipeFH, steps=[1,0.5,0.5,0.2,0.2,0.1], blurs=[0.25,0.25,0.25,0.25,0.25, -1], gradients=[False, False, True, False, True, False], iterations=[60,60,60,10,10,4], simplexes=[3,3,3,1.5,1.5,1], w_translations=0.2, linearparams = {'type' : "lsq12", 'simplex' : 1, 'step' : 1}, defaultDir="tmp"): self.p = Pipeline() for b in blurs: #MF TODO: -1 case is also handled in blur. Need here for addStage. #Fix this redundancy and/or better design? if b != -1: tblur = ma.blur(templatePipeFH, b, gradient=True) iblur = ma.blur(inputPipeFH, b, gradient=True) self.p.addStage(tblur) self.p.addStage(iblur) # Do standard LSQ12 alignment prior to non-linear stages lsq12reg = lsq12.LSQ12(inputPipeFH, templatePipeFH, defaultDir=defaultDir) self.p.addPipeline(lsq12reg.p) # create the nonlinear registrations for i in range(len(steps)): """For the final stage, make sure the output directory is transforms.""" if i == (len(steps) - 1): defaultDir = "transforms" nlinStage = ma.minctracc(inputPipeFH, templatePipeFH, defaultDir=defaultDir, blur=blurs[i], gradient=gradients[i], iterations=iterations[i], step=steps[i], similarity=0.8, w_translations=w_translations, simplex=simplexes[i]) self.p.addStage(nlinStage)
def getXfms(nlinFH, subjects, space, mbmDir, time=None): """For each file in the build-model registration (associated with the specified time point), do the following: 1. Find the to-native.xfm for that file. 2. Find the matching subject at the specified time point 3. Set this xfm to be the last xfm from nlin average to subject from step #2. 4. Find the -from-native.xfm file. 5. Set this xfm to be the last xfm from subject to nlin. Note: assume that the names in processedDir match beginning file names for each subject We are also assuming subjects is either a dictionary or a list. """ """First handle subjects if dictionary or list""" if isinstance(subjects, list): inputs = subjects elif isinstance(subjects, dict): inputs = [] for s in subjects: inputs.append(subjects[s][time]) else: logger.error("getXfms only takes a dictionary or list of subjects. Incorrect type has been passed. Exiting...") sys.exit() pipeline = Pipeline() baseNames = walk(mbmDir).next()[1] for b in baseNames: if space == "lsq6": xfmToNative = abspath(mbmDir + "/" + b + "/transforms/" + b + "-final-to_lsq6.xfm") elif space == "lsq12": xfmToNative = abspath(mbmDir + "/" + b + "/transforms/" + b + "-final-nlin.xfm") xfmFromNative = abspath(mbmDir + "/" + b + "/transforms/" + b + "_inv_nonlinear.xfm") elif space == "native": xfmToNative = abspath(mbmDir + "/" + b + "/transforms/" + b + "-to-native.xfm") xfmFromNative = abspath(mbmDir + "/" + b + "/transforms/" + b + "-from-native.xfm") else: logger.error("getXfms can only retrieve transforms to and from native, lsq6 or lsq12 space. Invalid parameter has been passed.") sys.exit() for inputFH in inputs: if fnmatch.fnmatch(inputFH.getLastBasevol(), "*" + b + "*"): if space=="lsq6": ix = ma.xfmInvert(xfmToNative, inputFH) pipeline.addStage(ix) xfmFromNative = ix.outputFiles[0] nlinFH.setLastXfm(inputFH, xfmToNative) inputFH.setLastXfm(nlinFH, xfmFromNative) return pipeline
class LinearHierarchicalMinctracc: """Default LinearHierarchicalMinctracc class Assumes lsq6 registration using the identity transform""" def __init__(self, inputPipeFH, templatePipeFH, blurs=[1, 0.5, 0.3]): self.p = Pipeline() self.inputPipeFH = inputPipeFH self.templatePipeFH = templatePipeFH self.blurFiles(blurs) def blurFiles(self, blurs): for b in blurs: if b != -1: tblur = ma.blur(self.templatePipeFH, b, gradient=True) iblur = ma.blur(self.inputPipeFH, b, gradient=True) self.p.addStage(tblur) self.p.addStage(iblur)
def maskFiles(FH, isAtlas, numAtlases=1): """ Assume that if there is more than one atlas, multiple masks were generated and we need to perform a voxel_vote. Otherwise, assume we are using inputLabels from crossing with only one atlas. """ #MF TODO: Make this more general to handle pairwise option. p = Pipeline() if not isAtlas: if numAtlases > 1: voxel = voxelVote(FH, False, True) p.addStage(voxel) mincMathInput = voxel.outputFiles[0] else: mincMathInput = FH.returnLabels(True)[0] FH.setMask(mincMathInput) else: mincMathInput = FH.getMask() mincMathOutput = fh.createBaseName(FH.resampledDir, FH.basename) mincMathOutput += "_masked.mnc" logFile = fh.logFromFile(FH.logDir, mincMathOutput) cmd = ["mincmath"] + ["-clobber"] + ["-mult"] # In response to issue #135 # the order of the input files to mincmath matters. By default the # first input files is used as a "like file" for the output file. # We should make sure that the mask is not used for that, because # it has an image range from 0 to 1; not something we want to be # set for the masked output file # average mask cmd += [InputFile(FH.getLastBasevol())] + [InputFile(mincMathInput)] cmd += [OutputFile(mincMathOutput)] mincMath = CmdStage(cmd) mincMath.setLogFile(LogFile(logFile)) p.addStage(mincMath) FH.setLastBasevol(mincMathOutput) return (p)
def maskFiles(FH, isAtlas, numAtlases=1): """ Assume that if there is more than one atlas, multiple masks were generated and we need to perform a voxel_vote. Otherwise, assume we are using inputLabels from crossing with only one atlas. """ #MF TODO: Make this more general to handle pairwise option. p = Pipeline() if not isAtlas: if numAtlases > 1: voxel = voxelVote(FH, False, True) p.addStage(voxel) mincMathInput = voxel.outputFiles[0] else: mincMathInput = FH.returnLabels(True)[0] FH.setMask(mincMathInput) else: mincMathInput = FH.getMask() mincMathOutput = fh.createBaseName(FH.resampledDir, FH.basename) mincMathOutput += "_masked.mnc" logFile = fh.logFromFile(FH.logDir, mincMathOutput) cmd = ["mincmath"] + ["-clobber"] + ["-mult"] # In response to issue #135 # the order of the input files to mincmath matters. By default the # first input files is used as a "like file" for the output file. # We should make sure that the mask is not used for that, because # it has an image range from 0 to 1; not something we want to be # set for the masked output file # average mask cmd += [InputFile(FH.getLastBasevol())] + [InputFile(mincMathInput)] cmd += [OutputFile(mincMathOutput)] mincMath = CmdStage(cmd) mincMath.setLogFile(LogFile(logFile)) p.addStage(mincMath) FH.setLastBasevol(mincMathOutput) return(p)
class LongitudinalStatsConcatAndResample: """ For each subject: 1. Calculate stats (displacement, absolute jacobians, relative jacobians) between i and i+1 time points 2. Calculate transform from subject to common space (nlinFH) and invert it. For most subjects this will require some amount of transform concatenation. 3. Calculate the stats (displacement, absolute jacobians, relative jacobians) from common space to each timepoint. """ def __init__(self, subjects, timePoint, nlinFH, statsKernels, commonName): self.subjects = subjects self.timePoint = timePoint self.nlinFH = nlinFH self.blurs = [] self.setupBlurs(statsKernels) self.commonName = commonName self.p = Pipeline() self.buildPipeline() def setupBlurs(self, statsKernels): if isinstance(statsKernels, list): self.blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): self.blurs.append(float(i)) else: print("Improper type of blurring kernels specified for stats calculation: " + str(statsKernels)) sys.exit() def statsCalculation(self, inputFH, targetFH, xfm=None, useChainStats=True): """If useChainStats=True, calculate stats between input and target. This happens for all i to i+1 calcs. If useChainStats=False, calculate stats in the standard way, from target to input, We do this, when we go from the common space to all others. """ if useChainStats: stats = st.CalcChainStats(inputFH, targetFH, self.blurs) else: stats = st.CalcStats(inputFH, targetFH, self.blurs) self.p.addPipeline(stats.p) """If an xfm is specified, resample all to this common space""" if xfm: if not self.nlinFH: likeFH = targetFH else: likeFH = self.nlinFH res = resampleToCommon(xfm, inputFH, stats.statsGroup, self.blurs, likeFH) self.p.addPipeline(res) def statsAndConcat(self, s, i, count, beforeAvg=True): """Construct array to common space for this timepoint. This builds upon arrays from previous calls.""" if beforeAvg: xfm = s[i].getLastXfm(s[i+1]) else: xfm = s[i].getLastXfm(s[i-1]) """Set this transform as last xfm from input to nlin and calculate nlin to s[i] stats""" if self.nlinFH: self.xfmToCommon.insert(0, xfm) """ Concat transforms to get xfmToCommon and calculate statistics Note that inverted transform, which is what we want, is calculated in the statistics module. """ xtc = createBaseName(s[i].transformsDir, s[i].basename + "_to_" + self.commonName + ".xfm") xc = ma.xfmConcat(self.xfmToCommon, xtc, fh.logFromFile(s[i].logDir, xtc)) self.p.addStage(xc) # here in order to visually inspect the alignment with the common # time point, we should resample this subject: inputResampledToCommon = createBaseName(s[i].resampledDir, s[i].basename + "_to_" + self.commonName + ".mnc") logToCommon = fh.logFromFile(s[i].logDir, inputResampledToCommon) resampleCmd = ma.mincresample(s[i], self.nlinFH, likeFile=self.nlinFH, transform=xtc, output=inputResampledToCommon, logFile=logToCommon, argArray=["-sinc"]) self.p.addStage(resampleCmd) s[i].addAndSetXfmToUse(self.nlinFH, xtc) self.statsCalculation(s[i], self.nlinFH, xfm=None, useChainStats=False) else: xtc=None """Calculate i to i+1 stats for all but final timePoint""" if count - i > 1: self.statsCalculation(s[i], s[i+1], xfm=xtc, useChainStats=True) def buildPipeline(self): for subj in self.subjects: s = self.subjects[subj] count = len(s) """Wherever iterative model building was run, the indiv --> nlin xfm is stored in the group with the name "final". We need to use this group for to get the transform and do the stats calculation, and then reset to the current group. Calculate stats first from average to timepoint included in average""" if self.timePoint == -1: # This means that we used the last file for each of the subjects # to create the common average. This will be a variable time # point, so we have to determine it for each of the input files timePointToUse = len(s) - 1 else: timePointToUse = self.timePoint currGroup = s[timePointToUse].currentGroupIndex index = s[timePointToUse].getGroupIndex("final") xfmToNlin = s[timePointToUse].getLastXfm(self.nlinFH, groupIndex=index) if xfmToNlin: self.xfmToCommon = [xfmToNlin] else: self.xfmToCommon = [] if self.nlinFH: s[timePointToUse].currentGroupIndex = index self.statsCalculation(s[timePointToUse], self.nlinFH, xfm=None, useChainStats=False) s[timePointToUse].currentGroupIndex = currGroup """Next: If timepoint included in average is NOT final timepoint, also calculate i to i+1 stats.""" if count - timePointToUse > 1: self.statsCalculation(s[timePointToUse], s[timePointToUse+1], xfm=xfmToNlin, useChainStats=True) if not timePointToUse - 1 < 0: """ Average happened at time point other than first time point. Loop over points prior to average.""" for i in reversed(range(timePointToUse)): self.statsAndConcat(s, i, count, beforeAvg=True) # Loop over points after average. If average is at first time point, this loop # will hit all time points (other than first). If average is at subsequent time # point, it hits all time points not covered previously. # # xfmToCommon (possibly) needs to be reset: if the average time point is not the first time point # then the array xfmToCommon now contains a list of transformations from the first time point to the # average. For instance if the average time point is time point 3, then xfmToCommon now contains: # [ time_0_to_time_1.xfm, time_1_to_time_2.xfm, time_2_to_average_at_time_point2.xfm ] if xfmToNlin: self.xfmToCommon = [xfmToNlin] else: self.xfmToCommon = [] for i in range(timePointToUse + 1, count): self.statsAndConcat(s, i, count, beforeAvg=False)
class FullIterativeLSQ12Nlin: """Does a full iterative LSQ12 and NLIN. Basically iterative model building starting from LSQ6 and without stats at the end. Designed to be called as part of a larger application. Specifying an initModel is optional, all other arguments are mandatory.""" def __init__(self, inputs, dirs, options, avgPrefix=None, initModel=None, fileResolution=None): self.inputs = inputs self.dirs = dirs self.options = options self.avgPrefix = avgPrefix self.initModel = initModel self.nlinFH = None self.providedResolution = fileResolution self.p = Pipeline() self.buildPipeline() def buildPipeline(self): lsq12LikeFH = None resolutionForLSQ12 = None if self.initModel: lsq12LikeFH = self.initModel[0] elif self.options.lsq12_likeFile: lsq12LikeFH = self.options.lsq12_likeFile if lsq12LikeFH == None and self.options.lsq12_subject_matter == None and self.providedResolution == None: print("\nError: the FullIterativeLSQ12Nlin module was called without specifying either an initial model, nor an lsq12_subject_matter. Currently that means that the code can not determine the resolution at which the registrations should be run. Please specify one of the two. Exiting\n") sys.exit() if not (lsq12LikeFH == None): resolutionForLSQ12 = rf.returnFinestResolution(lsq12LikeFH) if resolutionForLSQ12 == None and self.providedResolution == None: print("\nError: the resolution at which the LSQ12 and the NLIN registration should be run could not be determined from either the initial model nor the LSQ12 like file. Please provide the fileResolution to the FullIterativeLSQ12Nlin module. Exiting\n") sys.exit() if resolutionForLSQ12 == None and self.providedResolution: resolutionForLSQ12 = self.providedResolution lsq12module = lsq12.FullLSQ12(self.inputs, self.dirs.lsq12Dir, queue_type=self.options.queue_type, likeFile=lsq12LikeFH, maxPairs=self.options.lsq12_max_pairs, lsq12_protocol=self.options.lsq12_protocol, subject_matter=self.options.lsq12_subject_matter, resolution=resolutionForLSQ12) lsq12module.iterate() self.p.addPipeline(lsq12module.p) self.lsq12Params = lsq12module.lsq12Params if lsq12module.lsq12AvgFH.getMask()== None: if self.initModel: lsq12module.lsq12AvgFH.setMask(self.initModel[0].getMask()) if not self.avgPrefix: self.avgPrefix = self.options.pipeline_name # same as in MBM.py: # for now we can use the same resolution for the NLIN stages as we did for the # LSQ12 stage. At some point we should look into the subject matter option... nlinModule = nlin.initializeAndRunNLIN(self.dirs.lsq12Dir, self.inputs, self.dirs.nlinDir, avgPrefix=self.avgPrefix, createAvg=False, targetAvg=lsq12module.lsq12AvgFH, nlin_protocol=self.options.nlin_protocol, reg_method=self.options.reg_method, resolution=resolutionForLSQ12) self.p.addPipeline(nlinModule.p) self.nlinFH = nlinModule.nlinAverages[-1] self.nlinParams = nlinModule.nlinParams self.initialTarget = nlinModule.initialTarget # Now we need the full transform to go back to LSQ6 space for i in self.inputs: linXfm = lsq12module.lsq12AvgXfms[i] nlinXfm = i.getLastXfm(self.nlinFH) outXfm = st.createOutputFileName(i, nlinXfm, "transforms", "_with_additional.xfm") xc = ma.xfmConcat([linXfm, nlinXfm], outXfm, fh.logFromFile(i.logDir, outXfm)) self.p.addStage(xc) i.addAndSetXfmToUse(self.nlinFH, outXfm)
class LongitudinalStatsConcatAndResample: """ For each subject: 1. Calculate stats (displacement, absolute jacobians, relative jacobians) between i and i+1 time points 2. Calculate transform from subject to common space (nlinFH) and invert it. For most subjects this will require some amount of transform concatenation. 3. Calculate the stats (displacement, absolute jacobians, relative jacobians) from common space to each timepoint. """ def __init__(self, subjects, timePoint, nlinFH, statsKernels, commonName): self.subjects = subjects self.timePoint = timePoint self.nlinFH = nlinFH self.blurs = [] self.setupBlurs(statsKernels) self.commonName = commonName self.p = Pipeline() self.buildPipeline() def setupBlurs(self, statsKernels): if isinstance(statsKernels, list): self.blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): self.blurs.append(float(i)) else: print "Improper type of blurring kernels specified for stats calculation: " + str(statsKernels) sys.exit() def statsCalculation(self, inputFH, targetFH, xfm=None, useChainStats=True): """If useChainStats=True, calculate stats between input and target. This happens for all i to i+1 calcs. If useChainStats=False, calculate stats in the standard way, from target to input, We do this, when we go from the common space to all others. """ if useChainStats: stats = st.CalcChainStats(inputFH, targetFH, self.blurs) else: stats = st.CalcStats(inputFH, targetFH, self.blurs) self.p.addPipeline(stats.p) """If an xfm is specified, resample all to this common space""" if xfm: if not self.nlinFH: likeFH = targetFH else: likeFH = self.nlinFH res = resampleToCommon(xfm, inputFH, stats.statsGroup, self.blurs, likeFH) self.p.addPipeline(res) def statsAndConcat(self, s, i, count, beforeAvg=True): """Construct array to common space for this timepoint. This builds upon arrays from previous calls.""" if beforeAvg: xfm = s[i].getLastXfm(s[i+1]) else: xfm = s[i].getLastXfm(s[i-1]) """Set this transform as last xfm from input to nlin and calculate nlin to s[i] stats""" if self.nlinFH: self.xfmToCommon.insert(0, xfm) """ Concat transforms to get xfmToCommon and calculate statistics Note that inverted transform, which is what we want, is calculated in the statistics module. """ xtc = fh.createBaseName(s[i].transformsDir, s[i].basename + "_to_" + self.commonName + ".xfm") xc = ma.xfmConcat(self.xfmToCommon, xtc, fh.logFromFile(s[i].logDir, xtc)) self.p.addStage(xc) s[i].addAndSetXfmToUse(self.nlinFH, xtc) self.statsCalculation(s[i], self.nlinFH, xfm=None, useChainStats=False) else: xtc=None """Calculate i to i+1 stats for all but final timePoint""" if count - i > 1: self.statsCalculation(s[i], s[i+1], xfm=xtc, useChainStats=True) def buildPipeline(self): for subj in self.subjects: s = self.subjects[subj] count = len(s) """Wherever iterative model building was run, the indiv --> nlin xfm is stored in the group with the name "final". We need to use this group for to get the transform and do the stats calculation, and then reset to the current group. Calculate stats first from average to timepoint included in average""" currGroup = s[self.timePoint].currentGroupIndex index = s[self.timePoint].getGroupIndex("final") xfmToNlin = s[self.timePoint].getLastXfm(self.nlinFH, groupIndex=index) if xfmToNlin: self.xfmToCommon = [xfmToNlin] else: self.xfmToCommon = [] if self.nlinFH: s[self.timePoint].currentGroupIndex = index self.statsCalculation(s[self.timePoint], self.nlinFH, xfm=None, useChainStats=False) s[self.timePoint].currentGroupIndex = currGroup """Next: If timepoint included in average is NOT final timepoint, also calculate i to i+1 stats.""" if count - self.timePoint > 1: self.statsCalculation(s[self.timePoint], s[self.timePoint+1], xfm=xfmToNlin, useChainStats=True) if not self.timePoint - 1 < 0: """ Average happened at time point other than first time point. Loop over points prior to average.""" for i in reversed(range(self.timePoint)): self.statsAndConcat(s, i, count, beforeAvg=True) """ Loop over points after average. If average is at first time point, this loop will hit all time points (other than first). If average is at subsequent time point, it hits all time points not covered previously. xfmToCommon needs to be reset.""" if xfmToNlin: self.xfmToCommon = [xfmToNlin] else: self.xfmToCommon = [] for i in range(self.timePoint + 1, count): self.statsAndConcat(s, i, count, beforeAvg=False)
class mincresampleFileAndMask(object): """ If the input file to mincresample(CmdStage) is a file handler, and there is a mask associated with the file, the most intuitive thing to do is to resample both the file and the mask. However, a true atom/command stage can only create a single stage, and a such mincresample(CmdStage) can not resample both. When using a file handler, the mask file associated with it is used behind the scenes without the user explicitly specifying this behaviour. That's why it is important that the mask always remains current/up-to-date. The best way to do that is to automatically resample the associated mask when the main file is being resampled. And that is where this class comes in. It serves as a wrapper around mincresample(CmdStage) and mincresampleMask(CmdStage). It will check whether the input file is a file handler, and if so, will resample the mask that is associated with it (if it exists). This class is not truly an atom/command stage, so technically should not live in the minc_atoms module. It is still kept here because in essence it serves as a single indivisible stage. (and because the user is more likely to call/find it when looking for the mincresample stage) """ def __init__(self, inFile, targetFile, nameForStage=None, **kwargs): self.p = Pipeline() self.outputFiles = [ ] # this will contain the outputFiles from the mincresample of the main MINC file self.outputFilesMask = [ ] # this will contain the outputFiles from the mincresample of the mask belonging to the main MINC file # the first step is to simply run the mincresample command: fileRS = mincresample(inFile, targetFile, **kwargs) if (nameForStage): fileRS.name = nameForStage self.p.addStage(fileRS) self.outputFiles = fileRS.outputFiles # initialize the array of outputs for the mask in case there is none to be resampled self.outputFilesMask = [None] * len(self.outputFiles) # next up, is this a file handler, and if so is there a mask that needs to be resampled? if (isFileHandler(inFile)): if (inFile.getMask()): # there is a mask associated with this file, should be updated # we have to watch out in terms of interpolation arguments, if # the original resample command asked for "-sinc" or "-tricubic" # for instance, we should remove that argument for the mask resampling # these options would reside in the argArray... maskArgs = copy.deepcopy(kwargs) if maskArgs.has_key("argArray"): argList = maskArgs["argArray"] for i in range(len(argList)): if (re.match("-sinc", argList[i]) or re.match("-trilinear", argList[i]) or re.match("-tricubic", argList[i])): del argList[i] maskArgs["argArray"] = argList # if the output file for the mincresample command was already # specified, add "_mask.mnc" to it if maskArgs.has_key("output"): maskArgs["output"] = re.sub(".mnc", "_mask.mnc", maskArgs["output"]) maskRS = mincresampleMask(inFile, targetFile, **maskArgs) if (nameForStage): maskRS.name = nameForStage + "--mask--" self.p.addStage(maskRS) self.outputFilesMask = maskRS.outputFiles
class FullIterativeLSQ12Nlin: """Does a full iterative LSQ12 and NLIN. Basically iterative model building starting from LSQ6 and without stats at the end. Designed to be called as part of a larger application. Specifying an initModel is optional, all other arguments are mandatory.""" def __init__(self, inputs, dirs, options, avgPrefix=None, initModel=None): self.inputs = inputs self.dirs = dirs self.options = options self.avgPrefix = avgPrefix self.initModel = initModel self.nlinFH = None self.p = Pipeline() self.buildPipeline() def buildPipeline(self): lsq12LikeFH = None resolutionForLSQ12 = None if self.initModel: lsq12LikeFH = self.initModel[0] elif self.options.lsq12_likeFile: lsq12LikeFH = self.options.lsq12_likeFile if lsq12LikeFH == None and self.options.lsq12_subject_matter == None: print "\nError: the FullIterativeLSQ12Nlin module was called without specifying either an initial model, nor an lsq12_subject_matter. Currently that means that the code can not determine the resolution at which the registrations should be run. Please specify one of the two. Exiting\n" sys.exit() if not (lsq12LikeFH == None): resolutionForLSQ12 = rf.returnFinestResolution(lsq12LikeFH) lsq12module = lsq12.FullLSQ12( self.inputs, self.dirs.lsq12Dir, likeFile=lsq12LikeFH, maxPairs=self.options.lsq12_max_pairs, lsq12_protocol=self.options.lsq12_protocol, subject_matter=self.options.lsq12_subject_matter, resolution=resolutionForLSQ12) lsq12module.iterate() self.p.addPipeline(lsq12module.p) self.lsq12Params = lsq12module.lsq12Params if lsq12module.lsq12AvgFH.getMask() == None: if self.initModel: lsq12module.lsq12AvgFH.setMask(self.initModel[0].getMask()) if not self.avgPrefix: self.avgPrefix = self.options.pipeline_name # same as in MBM.py: # for now we can use the same resolution for the NLIN stages as we did for the # LSQ12 stage. At some point we should look into the subject matter option... nlinModule = nlin.initializeAndRunNLIN( self.dirs.lsq12Dir, self.inputs, self.dirs.nlinDir, avgPrefix=self.avgPrefix, createAvg=False, targetAvg=lsq12module.lsq12AvgFH, nlin_protocol=self.options.nlin_protocol, reg_method=self.options.reg_method, resolution=resolutionForLSQ12) self.p.addPipeline(nlinModule.p) self.nlinFH = nlinModule.nlinAverages[-1] self.nlinParams = nlinModule.nlinParams self.initialTarget = nlinModule.initialTarget # Now we need the full transform to go back to LSQ6 space for i in self.inputs: linXfm = lsq12module.lsq12AvgXfms[i] nlinXfm = i.getLastXfm(self.nlinFH) outXfm = st.createOutputFileName(i, nlinXfm, "transforms", "_with_additional.xfm") xc = ma.xfmConcat([linXfm, nlinXfm], outXfm, fh.logFromFile(i.logDir, outXfm)) self.p.addStage(xc) i.addAndSetXfmToUse(self.nlinFH, outXfm)
class CalcStats(object): """Statistics calculation between an input and target. This class calculates multiple displacement fields, relative and absolute jacobians. General functionality as follows: 1. Class instantiated with input, target and statsKernels. Note that here, the statsKernels specified are blurs used to smooth the displacement fields prior to additional calculations. They may be a string of comma separated values or an array of doubles. 2. An additional transform may also be included to calculate absolute jacobians to a different space, as is described in the __init__ function, documentation and elsewhere in the code. 3. If needed, invert transform between input and target in setupXfms(). This is necessary as this class assumes that the target is the reference space, from which all stats are calculated. 4. Call fullStatsCalc. This calculates linear and pure nonlinear displacement before calculating jacobians. 5. Ability to recenter displacements using an average may be re-added in the future. """ def __init__(self, inputFH, targetFH, statsKernels, additionalXfm=None): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.blurs = [] self.setupBlurs(statsKernels) self.statsGroup = StatsGroup() self.setupXfms() """ additionalXfm is an optional transform that may be specified. If it is, it is concatenated with the lastXfm from input to target. This additional transform must also be in the same direction as the lastXfm (e.g. input to target) Example usage: if the lastXfm from input to target goes from lsq12 to nlin space and you would like to calculate the absolute jacobians to lsq6 space, the additional transform specified may be the lsq6 to lsq12 transform from input to target. """ self.additionalXfm = additionalXfm self.fullStatsCalc() def setupBlurs(self, statsKernels): if isinstance(statsKernels, list): self.blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): self.blurs.append(float(i)) else: print "Improper type of blurring kernels specified for stats calculation: " + str(statsKernels) sys.exit() def setupXfms(self): self.xfm = self.inputFH.getLastXfm(self.targetFH) if not self.xfm: print "Cannot calculate statistics. No transform between input and target specified." print "Input: " + self.inputFH.getLastBasevol() print "Target: " + self.targetFH.getLastBasevol() sys.exit() else: self.invXfm = self.targetFH.getLastXfm(self.inputFH) if not self.invXfm: xi = xfmInvert(self.xfm, FH=self.inputFH) self.p.addStage(xi) self.invXfm = xi.outputFiles[0] def fullStatsCalc(self): self.linAndNlinDisplacement() self.calcDetAndLogDet(useFullDisp=False) # Calculate relative jacobians self.calcDetAndLogDet(useFullDisp=True) # Calculate absolute jacobians def calcFullDisplacement(self): """Calculate full displacement from target to input. If an additionaXfm is specified, it is concatenated to self.xfm here """ if self.additionalXfm: outXfm = createOutputFileName(self.inputFH, self.xfm, "transforms", "_with_additional.xfm") xc = xfmConcat([self.additionalXfm, self.xfm], outXfm, fh.logFromFile(self.inputFH.logDir, outXfm)) self.p.addStage(xc) xi = xfmInvert(xc.outputFiles[0], FH=self.inputFH) self.p.addStage(xi) fullDisp = mincDisplacement(self.targetFH, self.inputFH, transform=xi.outputFiles[0]) else: fullDisp = mincDisplacement(self.targetFH, self.inputFH, transform=self.invXfm) self.p.addStage(fullDisp) self.fullDisp = fullDisp.outputFiles[0] def calcNlinDisplacement(self): """Calculate pure non-linear displacement from target to input 1. Concatenate self.invXfm (target to input xfm) and self.linearPartOfNlinXfm 2. Compute mincDisplacement on this transform. """ pureNlinXfm = createOutputFileName(self.inputFH, self.invXfm, "transforms", "_pure_nlin.xfm") xc = xfmConcat([self.invXfm, self.linearPartOfNlinXfm], pureNlinXfm, fh.logFromFile(self.inputFH.logDir, pureNlinXfm)) self.p.addStage(xc) nlinDisp = mincDisplacement(self.targetFH, self.inputFH, transform=pureNlinXfm) self.p.addStage(nlinDisp) self.nlinDisp = nlinDisp.outputFiles[0] def linAndNlinDisplacement(self): """ Calculation of full and pure non-linear displacements. The former is used to calculate absolute jacobians, the latter to calculate relative. The direction of the transforms and displacements is defined in each subclass. """ #1. Calculate linear part of non-linear xfm from input to target. # This is necessary prior to calculating the pure nonlinear displacement lpnl = linearPartofNlin(self.inputFH, self.targetFH) self.p.addStage(lpnl) self.linearPartOfNlinXfm = lpnl.outputFiles[0] # 2. Calculate the pure non-linear displacement self.calcNlinDisplacement() # 3. Calculate the full displacement self.calcFullDisplacement() def calcDetAndLogDet(self, useFullDisp=False): if useFullDisp: dispToUse = self.fullDisp #absolute jacobians else: dispToUse = self.nlinDisp #relative jacobians """Insert -1 at beginning of blurs array to include the calculation of unblurred jacobians.""" self.blurs.insert(0,-1) for b in self.blurs: """Create base name for determinant calculation.""" outputBase = fh.removeBaseAndExtension(dispToUse).split("_displacement")[0] """Calculate smoothed deformation field for all blurs other than -1""" if b != -1: fwhm = "--fwhm=" + str(b) outSmooth = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_smooth_displacement_fwhm" + str(b) + ".mnc") cmd = ["smooth_vector", "--clobber", "--filter", fwhm, InputFile(dispToUse), OutputFile(outSmooth)] smoothVec = CmdStage(cmd) smoothVec.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outSmooth))) self.p.addStage(smoothVec) """Set input for determinant calculation.""" inputDet = outSmooth nameAddendum = "_fwhm" + str(b) else: inputDet = dispToUse nameAddendum = "" outputDet = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_determinant" + nameAddendum + ".mnc") outDetShift = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_det_plus1" + nameAddendum + ".mnc") if useFullDisp: #absolute jacobians outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_absolute_log_determinant" + nameAddendum + ".mnc") else: #relative jacobians outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_relative_log_determinant" + nameAddendum + ".mnc") """Calculate the determinant, then add 1 (per mincblob weirdness)""" cmd = ["mincblob", "-clobber", "-determinant", InputFile(inputDet), OutputFile(outputDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outputDet))) self.p.addStage(det) cmd = ["mincmath", "-clobber", "-2", "-const", str(1), "-add", InputFile(outputDet), OutputFile(outDetShift)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outDetShift))) self.p.addStage(det) """Calculate log determinant (jacobian) and add to statsGroup.""" cmd = ["mincmath", "-clobber", "-2", "-log", InputFile(outDetShift), OutputFile(outLogDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outLogDet))) self.p.addStage(det) if useFullDisp: self.statsGroup.absoluteJacobians[b] = outLogDet else: self.statsGroup.relativeJacobians[b] = outLogDet
class LSQ12ANTSNlin: """Class that runs a basic LSQ12 registration, followed by a single mincANTS call. Currently used in MAGeT, registration_chain and pairwise_nlin.""" def __init__(self, inputFH, targetFH, lsq12_protocol=None, nlin_protocol=None, subject_matter=None, defaultDir="tmp"): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.lsq12_protocol = lsq12_protocol self.nlin_protocol = nlin_protocol self.subject_matter = subject_matter self.defaultDir = defaultDir if ((self.lsq12_protocol == None and self.subject_matter==None) or self.nlin_protocol == None): # always base the resolution to be used on the target for the registrations self.fileRes = rf.returnFinestResolution(self.targetFH) else: self.fileRes = None self.buildPipeline() def buildPipeline(self): # Run lsq12 registration prior to non-linear self.lsq12Params = mp.setLSQ12MinctraccParams(self.fileRes, subject_matter=self.subject_matter, reg_protocol=self.lsq12_protocol) lsq12reg = lsq12.LSQ12(self.inputFH, self.targetFH, blurs=self.lsq12Params.blurs, step=self.lsq12Params.stepSize, gradient=self.lsq12Params.useGradient, simplex=self.lsq12Params.simplex, w_translations=self.lsq12Params.w_translations, defaultDir=self.defaultDir) self.p.addPipeline(lsq12reg.p) #Resample using final LSQ12 transform and reset last base volume. res = ma.mincresample(self.inputFH, self.targetFH, likeFile=self.targetFH, argArray=["-sinc"]) self.p.addStage(res) self.inputFH.setLastBasevol(res.outputFiles[0]) lsq12xfm = self.inputFH.getLastXfm(self.targetFH) #Get registration parameters from nlin protocol, blur and register #Assume a SINGLE generation here. self.nlinParams = mp.setOneGenMincANTSParams(self.fileRes, reg_protocol=self.nlin_protocol) for b in self.nlinParams.blurs: for j in b: #Note that blurs for ANTS params in an array of arrays. if j != -1: self.p.addStage(ma.blur(self.targetFH, j, gradient=True)) self.p.addStage(ma.blur(self.inputFH, j, gradient=True)) sp = ma.mincANTS(self.inputFH, self.targetFH, defaultDir=self.defaultDir, blur=self.nlinParams.blurs[0], gradient=self.nlinParams.gradient[0], similarity_metric=self.nlinParams.similarityMetric[0], weight=self.nlinParams.weight[0], iterations=self.nlinParams.iterations[0], radius_or_histo=self.nlinParams.radiusHisto[0], transformation_model=self.nlinParams.transformationModel[0], regularization=self.nlinParams.regularization[0], useMask=self.nlinParams.useMask[0]) self.p.addStage(sp) nlinXfm = sp.outputFiles[0] #Reset last base volume to original input for future registrations. self.inputFH.setLastBasevol(setToOriginalInput=True) #Concatenate transforms to get final lsq12 + nlin. Register volume handles naming and setting of lastXfm output = self.inputFH.registerVolume(self.targetFH, "transforms") xc = ma.xfmConcat([lsq12xfm, nlinXfm], output, fh.logFromFile(self.inputFH.logDir, output)) self.p.addStage(xc)
class LSQ12(object): """Basic LSQ12 class. This class takes an input FileHandler and a targetFileHandler as required inputs. A series of minctracc calls will then produce the 12-parameter alignment. The number of minctracc calls and their parameters are controlled by four further arguments to the constructor: blurs: an array of floats containing the FWHM of the blurring kernel to be used for each call gradient: an array of booleans stating whether we should use the blur (False) or gradient (True) of each blur step: an array of floats containing the step used by minctracc in each call simplex: an array of floats containing the simplex used by minctracc in each call. The number of entries in those three (blurs, step, simplex) input arguments determines the number of minctracc calls executed in this module. For example, the following call: LSQ12(inputFH, targetFH, blurs=[10,5,2], gradient=[False,True,True], step=[4,4,4], simplex=[20,20,20]) will result in three successive minctracc calls, each initialized with the output transform of the previous call. """ def __init__(self, inputFH, targetFH, blurs=[0.3, 0.2, 0.15], step=[1,0.5,0.333333333333333], gradient=[False,True,False], simplex=[3,1.5,1], defaultDir="tmp"): # TO DO: Might want to take this out and pass in # of generations, since # checking happens there. if len(blurs) == len(step) == len(simplex): # do nothing - all lengths are the same and we're therefore happy pass else: logger.error("The same number of entries are required for blurs, step, and simplex in LSQ12") sys.exit() self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.blurs = blurs self.step = step self.blurs = blurs self.gradient = gradient self.simplex = simplex self.defaultDir = defaultDir self.blurFiles() self.buildPipeline() def blurFiles(self): for b in self.blurs: if b != -1: tblur = ma.blur(self.targetFH, b, gradient=True) iblur = ma.blur(self.inputFH, b, gradient=True) self.p.addStage(tblur) self.p.addStage(iblur) def buildPipeline(self): for i in range(len(self.blurs)): linearStage = ma.minctracc(self.inputFH, self.targetFH, blur=self.blurs[i], defaultDir=self.defaultDir, gradient=self.gradient[i], linearparam="lsq12", step=self.step[i], simplex=self.simplex[i]) self.p.addStage(linearStage)
class LSQ12ANTSNlin: """Class that runs a basic LSQ12 registration, followed by a single mincANTS call. Currently used in MAGeT, registration_chain and pairwise_nlin.""" def __init__(self, inputFH, targetFH, lsq12_protocol=None, nlin_protocol=None, subject_matter=None, defaultDir="tmp"): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.lsq12_protocol = lsq12_protocol self.nlin_protocol = nlin_protocol self.subject_matter = subject_matter self.defaultDir = defaultDir if ((self.lsq12_protocol == None and self.subject_matter==None) or self.nlin_protocol == None): self.fileRes = rf.returnFinestResolution(self.inputFH) else: self.fileRes = None self.buildPipeline() def buildPipeline(self): # Run lsq12 registration prior to non-linear self.lsq12Params = mp.setLSQ12MinctraccParams(self.fileRes, subject_matter=self.subject_matter, reg_protocol=self.lsq12_protocol) lsq12reg = lsq12.LSQ12(self.inputFH, self.targetFH, blurs=self.lsq12Params.blurs, step=self.lsq12Params.stepSize, gradient=self.lsq12Params.useGradient, simplex=self.lsq12Params.simplex, w_translations=self.lsq12Params.w_translations, defaultDir=self.defaultDir) self.p.addPipeline(lsq12reg.p) #Resample using final LSQ12 transform and reset last base volume. res = ma.mincresample(self.inputFH, self.targetFH, likeFile=self.targetFH, argArray=["-sinc"]) self.p.addStage(res) self.inputFH.setLastBasevol(res.outputFiles[0]) lsq12xfm = self.inputFH.getLastXfm(self.targetFH) #Get registration parameters from nlin protocol, blur and register #Assume a SINGLE generation here. self.nlinParams = mp.setOneGenMincANTSParams(self.fileRes, reg_protocol=self.nlin_protocol) for b in self.nlinParams.blurs: for j in b: #Note that blurs for ANTS params in an array of arrays. if j != -1: self.p.addStage(ma.blur(self.targetFH, j, gradient=True)) self.p.addStage(ma.blur(self.inputFH, j, gradient=True)) sp = ma.mincANTS(self.inputFH, self.targetFH, defaultDir=self.defaultDir, blur=self.nlinParams.blurs[0], gradient=self.nlinParams.gradient[0], similarity_metric=self.nlinParams.similarityMetric[0], weight=self.nlinParams.weight[0], iterations=self.nlinParams.iterations[0], radius_or_histo=self.nlinParams.radiusHisto[0], transformation_model=self.nlinParams.transformationModel[0], regularization=self.nlinParams.regularization[0], useMask=self.nlinParams.useMask[0]) self.p.addStage(sp) nlinXfm = sp.outputFiles[0] #Reset last base volume to original input for future registrations. self.inputFH.setLastBasevol(setToOriginalInput=True) #Concatenate transforms to get final lsq12 + nlin. Register volume handles naming and setting of lastXfm output = self.inputFH.registerVolume(self.targetFH, "transforms") xc = ma.xfmConcat([lsq12xfm, nlinXfm], output, fh.logFromFile(self.inputFH.logDir, output)) self.p.addStage(xc)
class FullLSQ12(object): """ This class takes an array of input file handlers along with an optionally specified protocol and does 12-parameter alignment and averaging of all of the pairs. Required arguments: inputArray = array of file handlers to be registered outputDir = an output directory to place the final average from this registration Optional arguments include: --likeFile = a file handler that can be used as a likeFile for resampling each input into the final lsq12 space. If none is specified, the input will be used --maxPairs = maximum number of pairs to register. If this pair is specified, then each subject will only be registered to a subset of the other subjects. --lsq2_protocol = an optional csv file to specify a protocol that overrides the defaults. --subject_matter = currently supports "mousebrain". If this is specified, the parameter for the minctracc registrations are set based on defaults for mouse brains instead of the file resolution. """ def __init__(self, inputArray, outputDir, likeFile=None, maxPairs=None, lsq12_protocol=None, subject_matter=None): self.p = Pipeline() """Initial inputs should be an array of fileHandlers with lastBasevol in lsq12 space""" self.inputs = inputArray """Output directory should be _nlin """ self.lsq12Dir = outputDir """likeFile for resampling""" self.likeFile = likeFile """Maximum number of pairs to calculate""" self.maxPairs = maxPairs """Final lsq12 average""" self.lsq12Avg = None """Final lsq12 average file handler (e.g. the file handler associated with lsq12Avg)""" self.lsq12AvgFH = None """ Dictionary of lsq12 average transforms, which will include one per input. Key is input file handler and value is string pointing to final average lsq12 transform for that particular subject. These xfms may be used subsequently for statistics calculations. """ self.lsq12AvgXfms = {} """Create the blurring resolution from the file resolution""" if (subject_matter == None and lsq12_protocol == None): self.fileRes = rf.returnFinestResolution(self.inputs[0]) else: self.fileRes = None """"Set up parameter array""" self.lsq12Params = mp.setLSQ12MinctraccParams( self.fileRes, subject_matter=subject_matter, reg_protocol=lsq12_protocol) self.blurs = self.lsq12Params.blurs self.stepSize = self.lsq12Params.stepSize self.useGradient = self.lsq12Params.useGradient self.simplex = self.lsq12Params.simplex self.w_translations = self.lsq12Params.w_translations self.generations = self.lsq12Params.generations # Create new lsq12 group for each input prior to registration for i in range(len(self.inputs)): self.inputs[i].newGroup(groupName="lsq12") def iterate(self): if not self.maxPairs: xfmsToAvg = {} lsq12ResampledFiles = {} for inputFH in self.inputs: """Create an array of xfms, to compute an average lsq12 xfm for each input""" xfmsToAvg[inputFH] = [] for targetFH in self.inputs: if inputFH != targetFH: lsq12 = LSQ12(inputFH, targetFH, blurs=self.blurs, step=self.stepSize, gradient=self.useGradient, simplex=self.simplex, w_translations=self.w_translations) self.p.addPipeline(lsq12.p) xfmsToAvg[inputFH].append(inputFH.getLastXfm(targetFH)) """Create average xfm for inputFH using xfmsToAvg array""" cmd = ["xfmavg"] for i in range(len(xfmsToAvg[inputFH])): cmd.append(InputFile(xfmsToAvg[inputFH][i])) avgXfmOutput = createBaseName( inputFH.transformsDir, inputFH.basename + "-avg-lsq12.xfm") cmd.append(OutputFile(avgXfmOutput)) xfmavg = CmdStage(cmd) xfmavg.setLogFile( LogFile(logFromFile(inputFH.logDir, avgXfmOutput))) self.p.addStage(xfmavg) self.lsq12AvgXfms[inputFH] = avgXfmOutput """ resample brain and add to array for mincAveraging""" if not self.likeFile: likeFile = inputFH else: likeFile = self.likeFile rslOutput = createBaseName( inputFH.resampledDir, inputFH.basename + "-resampled-lsq12.mnc") res = ma.mincresample(inputFH, inputFH, transform=avgXfmOutput, likeFile=likeFile, output=rslOutput, argArray=["-sinc"]) self.p.addStage(res) lsq12ResampledFiles[inputFH] = rslOutput """ After all registrations complete, setLastBasevol for each subject to be resampled file in lsq12 space. We can then call mincAverage on fileHandlers, as it will use the lastBasevol for each by default.""" for inputFH in self.inputs: inputFH.setLastBasevol(lsq12ResampledFiles[inputFH]) """ mincAverage all resampled brains and put in lsq12Directory""" self.lsq12Avg = abspath(self.lsq12Dir) + "/" + basename( self.lsq12Dir) + "-pairs.mnc" self.lsq12AvgFH = RegistrationPipeFH(self.lsq12Avg, basedir=self.lsq12Dir) avg = ma.mincAverage(self.inputs, self.lsq12AvgFH, output=self.lsq12Avg, defaultDir=self.lsq12Dir) self.p.addStage(avg) else: print "Registration using a specified number of max pairs not yet working. Check back soon!" sys.exit()
class NLINBase(object): """ This is the parent class for any iterative non-linear registration. Subclasses should extend the following methods: setDefaultParams() getGenerations() setParams() iterate() iterationLoop() """ def __init__(self, inputArray, targetFH, nlinOutputDir, nlin_protocol=None): self.p = Pipeline() """Initial inputs should be an array of fileHandlers with lastBasevol in lsq12 space""" self.inputs = inputArray """Initial target should be the file handler for the lsq12 average""" self.target = targetFH """Output directory should be _nlin """ self.nlinDir = nlinOutputDir """Empty array that we will fill with averages as we create them""" self.nlinAverages = [] """Create the blurring resolution from the file resolution""" try: # the attempt to access the minc volume will fail if it doesn't yet exist at pipeline creation self.fileRes = rf.getFinestResolution(self.target) except: # if it indeed failed, get resolution from the original file specified for # one of the input files, which should exist. # Can be overwritten by the user through specifying a nonlinear protocol. self.fileRes = rf.getFinestResolution(self.inputs[0].inputFileName) """ Set default parameters before checking to see if a non-linear protocol has been specified. This is done first, since a non-linear protocol may specify only a subset of the parameters, but all parameters must be set for the registration to run properly. After default parameters are set, check for a specified non-linear protocol and override these parameters accordingly. Currently, this protocol must be a csv file that uses a SEMI-COLON to separate the fields. Examples are: pydpiper_apps_testing/test_data/minctracc_example_protocol.csv pydpiper_apps_testing/test_data/mincANTS_example_protocol.csv Each row in the csv is a different input to the either a minctracc or mincANTS call Although the number of entries in each row (e.g. generations) is variable, the specific parameters are fixed. For example, one could specify a subset of the allowed parameters (e.g. blurs only) but could not rename any parameters or use additional ones that haven't already been defined without subclassing. See documentation for additional details. Note that if no protocol is specified, then defaults will be used. Based on the length of these parameter arrays, the number of generations is set. """ self.defaultParams() if nlin_protocol: self.setParams(nlin_protocol) self.generations = self.getGenerations() # Create new nlin group for each input prior to registration for i in range(len(self.inputs)): self.inputs[i].newGroup(groupName="nlin") def defaultParams(self): """Set default parameters for each registration type in subclasses.""" pass def setParams(self): """Override parameters based on specified non-linear protocol.""" pass def getGenerations(self): """Get number of generations based on length of parameter arrays. """ pass def addBlurStage(self): """ Add blurs to pipeline. Because blurs are handled differently by parameter arrays in minctracc and mincANTS subclasses, they are added to the pipeline via function call. """ pass def regAndResample(self): """Registration and resampling calls""" pass def iterate(self): for i in range(self.generations): nlinOutput = abspath(self.nlinDir) + "/" + "nlin-%g.mnc" % (i+1) nlinFH = RegistrationPipeFH(nlinOutput, mask=self.target.getMask(), basedir=self.nlinDir) self.addBlurStage(self.target, i) filesToAvg = [] for inputFH in self.inputs: self.addBlurStage(inputFH, i) self.regAndResample(inputFH, i, filesToAvg, nlinFH) """Because we don't reset lastBasevol on each inputFH, call mincAverage with files only. We create fileHandler first though, so we have log directory. This solution seems a bit hackish--may want to modify? Additionally, we are currently using the full RegistrationPipeFH class, but ultimately we'll want to create a third class that is somewhere between a full and base class. """ logBase = removeBaseAndExtension(nlinOutput) avgLog = createLogFile(nlinFH.logDir, logBase) avg = mincAverage(filesToAvg, nlinOutput, logFile=avgLog) self.p.addStage(avg) """Reset target for next iteration and add to array""" self.target = nlinFH self.nlinAverages.append(nlinFH) """Create a final nlin group to add to the inputFH. lastBasevol = by default, will grab the lastBasevol used in these calculations (e.g. lsq12) setLastXfm between final nlin average and inputFH will be set for stats calculations. """ if i == (self.generations -1): for inputFH in self.inputs: """NOTE: The last xfm being set below is NOT the result of a registration between inputFH and nlinFH, but rather is the output transform from the previous generation's average.""" finalXfm = inputFH.getLastXfm(self.nlinAverages[self.generations-2]) inputFH.newGroup(groupName="final") inputFH.setLastXfm(nlinFH, finalXfm)
class mincresampleFileAndMask(object): """ If the input file to mincresample(CmdStage) is a file handler, and there is a mask associated with the file, the most intuitive thing to do is to resample both the file and the mask. However, a true atom/command stage can only create a single stage, and a such mincresample(CmdStage) can not resample both. When using a file handler, the mask file associated with it is used behind the scenes without the user explicitly specifying this behaviour. That's why it is important that the mask always remains current/up-to-date. The best way to do that is to automatically resample the associated mask when the main file is being resampled. And that is where this class comes in. It serves as a wrapper around mincresample(CmdStage) and mincresampleMask(CmdStage). It will check whether the input file is a file handler, and if so, will resample the mask that is associated with it (if it exists). This class is not truly an atom/command stage, so technically should not live in the minc_atoms module. It is still kept here because in essence it serves as a single indivisible stage. (and because the user is more likely to call/find it when looking for the mincresample stage) """ def __init__(self, inFile, targetFile, nameForStage=None, **kwargs): self.p = Pipeline() self.outputFiles = [] # this will contain the outputFiles from the mincresample of the main MINC file self.outputFilesMask = [] # this will contain the outputFiles from the mincresample of the mask belonging to the main MINC file # the first step is to simply run the mincresample command: fileRS = mincresample(inFile, targetFile, **kwargs) if(nameForStage): fileRS.name = nameForStage self.p.addStage(fileRS) self.outputFiles = fileRS.outputFiles # initialize the array of outputs for the mask in case there is none to be resampled self.outputFilesMask = [None] * len(self.outputFiles) # next up, is this a file handler, and if so is there a mask that needs to be resampled? if(isFileHandler(inFile)): if(inFile.getMask()): # there is a mask associated with this file, should be updated # we have to watch out in terms of interpolation arguments, if # the original resample command asked for "-sinc" or "-tricubic" # for instance, we should remove that argument for the mask resampling # these options would reside in the argArray... maskArgs = copy.deepcopy(kwargs) if(maskArgs["argArray"]): argList = maskArgs["argArray"] for i in range(len(argList)): if(re.match("-sinc", argList[i]) or re.match("-trilinear", argList[i]) or re.match("-tricubic", argList[i]) ): del argList[i] maskArgs["argArray"] = argList # if the output file for the mincresample command was already # specified, add "_mask.mnc" to it if(maskArgs["output"]): maskArgs["output"] = re.sub(".mnc", "_mask.mnc", maskArgs["output"]) maskRS = mincresampleMask(inFile, targetFile, **maskArgs) if(nameForStage): maskRS.name = nameForStage + "--mask--" self.p.addStage(maskRS) self.outputFilesMask = maskRS.outputFiles
class initializeAndRunNLIN(object): """Class to setup target average (if needed), instantiate correct version of NLIN class, and run NLIN registration.""" def __init__( self, targetOutputDir, #Output directory for files related to initial target (often _lsq12) inputFiles, nlinDir, avgPrefix, #Prefix for nlin-1.mnc, ... nlin-k.mnc createAvg=True, #True=call mincAvg, False=targetAvg already exists targetAvg=None, #Optional path to initial target - passing name does not guarantee existence targetMask=None, #Optional path to mask for initial target nlin_protocol=None, reg_method=None): self.p = Pipeline() self.targetOutputDir = targetOutputDir self.inputFiles = inputFiles self.nlinDir = nlinDir self.avgPrefix = avgPrefix self.createAvg = createAvg self.targetAvg = targetAvg self.targetMask = targetMask self.nlin_protocol = nlin_protocol self.reg_method = reg_method # setup initialTarget (if needed) and initialize non-linear module self.setupTarget() self.initNlinModule() #iterate through non-linear registration and setup averages self.nlinModule.iterate() self.p.addPipeline(self.nlinModule.p) self.nlinAverages = self.nlinModule.nlinAverages self.nlinParams = self.nlinModule.nlinParams def setupTarget(self): if self.targetAvg: if isinstance(self.targetAvg, str): self.initialTarget = RegistrationPipeFH( self.targetAvg, mask=self.targetMask, basedir=self.targetOutputDir) self.outputAvg = self.targetAvg elif isinstance(self.targetAvg, RegistrationPipeFH): self.initialTarget = self.targetAvg self.outputAvg = self.targetAvg.getLastBasevol() if not self.initialTarget.getMask(): if self.targetMask: self.initialTarget.setMask(self.targetMask) else: print "You have passed a target average that is neither a string nor a file handler: " + str( self.targetAvg) print "Exiting..." else: self.targetAvg = abspath( self.targetOutputDir) + "/" + "initial-target.mnc" self.initialTarget = RegistrationPipeFH( self.targetAvg, mask=self.targetMask, basedir=self.targetOutputDir) self.outputAvg = self.targetAvg if self.createAvg: avg = mincAverage(self.inputFiles, self.initialTarget, output=self.outputAvg, defaultDir=self.targetOutputDir) self.p.addStage(avg) def initNlinModule(self): if self.reg_method == "mincANTS": self.nlinModule = NLINANTS(self.inputFiles, self.initialTarget, self.nlinDir, self.avgPrefix, self.nlin_protocol) elif self.reg_method == "minctracc": self.nlinModule = NLINminctracc(self.inputFiles, self.initialTarget, self.nlinDir, self.avgPrefix, self.nlin_protocol) else: logger.error("Incorrect registration method specified: " + self.reg_method) sys.exit()
class HierarchicalMinctracc: """Default HierarchicalMinctracc currently does: 1. A standard three stage LSQ12 alignment. (See defaults for LSQ12 module.) 2. A six generation non-linear minctracc alignment. To override these defaults, lsq12 and nlin protocols may be specified. """ def __init__(self, inputFH, targetFH, lsq12_protocol=None, nlin_protocol=None, includeLinear = True, subject_matter = None, defaultDir="tmp"): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.lsq12_protocol = lsq12_protocol self.nlin_protocol = nlin_protocol self.includeLinear = includeLinear self.subject_matter = subject_matter self.defaultDir = defaultDir if ((self.lsq12_protocol == None and self.subject_matter==None) or self.nlin_protocol == None): self.fileRes = rf.returnFinestResolution(self.inputFH) else: self.fileRes = None self.buildPipeline() def buildPipeline(self): # Do LSQ12 alignment prior to non-linear stages if desired if self.includeLinear: self.lsq12Params = mp.setLSQ12MinctraccParams(self.fileRes, subject_matter=self.subject_matter, reg_protocol=self.lsq12_protocol) lsq12reg = lsq12.LSQ12(self.inputFH, self.targetFH, blurs=self.lsq12Params.blurs, step=self.lsq12Params.stepSize, gradient=self.lsq12Params.useGradient, simplex=self.lsq12Params.simplex, w_translations=self.lsq12Params.w_translations, defaultDir=self.defaultDir) self.p.addPipeline(lsq12reg.p) # create the nonlinear registrations self.nlinParams = mp.setNlinMinctraccParams(self.fileRes, reg_protocol=self.nlin_protocol) for b in self.nlinParams.blurs: if b != -1: self.p.addStage(ma.blur(self.inputFH, b, gradient=True)) self.p.addStage(ma.blur(self.targetFH, b, gradient=True)) for i in range(len(self.nlinParams.stepSize)): #For the final stage, make sure the output directory is transforms. if i == (len(self.nlinParams.stepSize) - 1): self.defaultDir = "transforms" nlinStage = ma.minctracc(self.inputFH, self.targetFH, defaultDir=self.defaultDir, blur=self.nlinParams.blurs[i], gradient=self.nlinParams.useGradient[i], iterations=self.nlinParams.iterations[i], step=self.nlinParams.stepSize[i], w_translations=self.nlinParams.w_translations[i], simplex=self.nlinParams.simplex[i], optimization=self.nlinParams.optimization[i]) self.p.addStage(nlinStage)
class createQualityControlImages(object): """ This class takes a list of input files and creates a set of quality control (verification) images. Optionally these images can be combined in a single montage image for easy viewing If the inputFiles are fileHandler, the last base volume will be used to create the images from. The scaling factor corresponds to the the mincpik -scale parameter """ def __init__(self, inputFiles, createMontage=True, montageOutPut=None, scalingFactor=20, message="lsq6"): self.p = Pipeline() self.individualImages = [] self.individualImagesLabeled = [] self.message = message if createMontage and montageOutPut == None: print("\nError: createMontage is specified in createQualityControlImages, but no output name for the montage is provided. Exiting...\n") sys.exit() # for each of the input files, run a mincpik call and create # a triplane image. for inFile in inputFiles: if isFileHandler(inFile): # create command using last base vol inputToMincpik = inFile.getLastBasevol() outputMincpik = createBaseName(inFile.tmpDir, removeBaseAndExtension(inputToMincpik) + "_QC_image.png") cmd = ["mincpik", "-clobber", "-scale", scalingFactor, "-triplanar", InputFile(inputToMincpik), OutputFile(outputMincpik)] mincpik = CmdStage(cmd) mincpik.setLogFile(LogFile(logFromFile(inFile.logDir, outputMincpik))) self.p.addStage(mincpik) self.individualImages.append(outputMincpik) # we should add a label to each of the individual images # so it will be easier for the user to identify what # which images potentially fail outputConvert = createBaseName(inFile.tmpDir, removeBaseAndExtension(inputToMincpik) + "_QC_image_labeled.png") cmdConvert = ["convert", "-label", inFile.basename, InputFile(outputMincpik), OutputFile(outputConvert)] convertAddLabel = CmdStage(cmdConvert) convertAddLabel.setLogFile(LogFile(logFromFile(inFile.logDir, outputConvert))) self.p.addStage(convertAddLabel) self.individualImagesLabeled.append(outputConvert) # if montageOutput is specified, create the overview image if createMontage: cmdmontage = ["montage", "-geometry", "+2+2"] \ + map(InputFile, self.individualImagesLabeled) + [OutputFile(montageOutPut)] montage = CmdStage(cmdmontage) montage.setLogFile(splitext(montageOutPut)[0] + ".log") message_to_print = "\n* * * * * * *\nPlease consider the following verification " message_to_print += "image, which shows a slice through all input " message_to_print += "files %s. " % self.message message_to_print += "\n%s\n" % (montageOutPut) message_to_print += "* * * * * * *\n" # the hook needs a return. Given that "print" does not return # anything, we need to encapsulate the print statement in a # function (which in this case will return None, but that's fine) def printMessageForMontage(): print(message_to_print) montage.finished_hooks.append( lambda : printMessageForMontage()) self.p.addStage(montage)
class FullLSQ12(object): """ This class takes an array of input file handlers along with an optionally specified protocol and does 12-parameter alignment and averaging of all of the pairs. Required arguments: inputArray = array of file handlers to be registered outputDir = an output directory to place the final average from this registration Optional arguments include: --likeFile = a file handler that can be used as a likeFile for resampling each input into the final lsq12 space. If none is specified, the input will be used --maxPairs = maximum number of pairs to register. If this pair is specified, then each subject will only be registered to a subset of the other subjects. --lsq2_protocol = an optional csv file to specify a protocol that overrides the defaults. --subject_matter = currently supports "mousebrain". If this is specified, the parameter for the minctracc registrations are set based on defaults for mouse brains instead of the file resolution. """ def __init__(self, inputArray, outputDir, likeFile=None, maxPairs=None, lsq12_protocol=None, subject_matter=None): self.p = Pipeline() """Initial inputs should be an array of fileHandlers with lastBasevol in lsq12 space""" self.inputs = inputArray """Output directory should be _nlin """ self.lsq12Dir = outputDir """likeFile for resampling""" self.likeFile=likeFile """Maximum number of pairs to calculate""" self.maxPairs = maxPairs """Final lsq12 average""" self.lsq12Avg = None """Final lsq12 average file handler (e.g. the file handler associated with lsq12Avg)""" self.lsq12AvgFH = None """ Dictionary of lsq12 average transforms, which will include one per input. Key is input file handler and value is string pointing to final average lsq12 transform for that particular subject. These xfms may be used subsequently for statistics calculations. """ self.lsq12AvgXfms = {} # what sort of subject matter do we deal with? self.subject_matter = subject_matter """Create the blurring resolution from the file resolution""" try: self.fileRes = rf.getFinestResolution(self.inputs[0]) except: # if this fails (because file doesn't exist when pipeline is created) grab from # initial input volume, which should exist. self.fileRes = rf.getFinestResolution(self.inputs[0].inputFileName) """ Similarly to LSQ6 and NLIN modules, an optional SEMI-COLON delimited csv may be specified to override the default registration protocol. An example protocol may be found in: Note that if no protocol is specified, then defaults will be used. Based on the length of these parameter arrays, the number of generations is set. """ self.defaultParams() if lsq12_protocol: self.setParams(lsq12_protocol) self.generations = self.getGenerations() # Create new lsq12 group for each input prior to registration for i in range(len(self.inputs)): self.inputs[i].newGroup(groupName="lsq12") def defaultParams(self): """ Default minctracc parameters based on resolution of file, unless a particular subject matter was provided """ blurfactors = [ 5, 10.0/3.0, 2.5] stepfactors = [50.0/3.0, 25.0/3.0, 5.5] simplexfactors = [ 50, 25, 50.0/3.0] if(self.subject_matter == "mousebrain"): # the default for mouse brains should be: # blurs: 0.3 0.2 0.15 # steps: 1 0.5 0.333 # simplex: 3 1.5 1 self.blurs = [0.3, 0.2, 0.15] self.stepSize= [1, 0.5, 1.0/3.0] self.simplex= [3, 1.5, 1] else: self.blurs = [i * self.fileRes for i in blurfactors] self.stepSize=[i * self.fileRes for i in stepfactors] self.simplex=[i * self.fileRes for i in simplexfactors] self.useGradient=[False,True,False] def setParams(self, lsq12_protocol): """Set parameters from specified protocol""" """Read parameters into array from csv.""" inputCsv = open(abspath(lsq12_protocol), 'rb') csvReader = csv.reader(inputCsv, delimiter=';', skipinitialspace=True) params = [] for r in csvReader: params.append(r) """initialize arrays """ self.blurs = [] self.stepSize = [] self.useGradient = [] self.simplex = [] """Parse through rows and assign appropriate values to each parameter array. Everything is read in as strings, but in some cases, must be converted to floats, booleans or gradients. """ for p in params: if p[0]=="blur": """Blurs must be converted to floats.""" for i in range(1,len(p)): self.blurs.append(float(p[i])) elif p[0]=="step": """Steps are strings but must be converted to a float.""" for i in range(1,len(p)): self.stepSize.append(float(p[i])) elif p[0]=="gradient": """Gradients must be converted to bools.""" for i in range(1,len(p)): if p[i]=="True" or p[i]=="TRUE": self.useGradient.append(True) elif p[i]=="False" or p[i]=="FALSE": self.useGradient.append(False) elif p[0]=="simplex": """Simplex must be converted to an int.""" for i in range(1,len(p)): self.simplex.append(int(p[i])) else: print "Improper parameter specified for minctracc protocol: " + str(p[0]) print "Exiting..." sys.exit() def getGenerations(self): arrayLength = len(self.blurs) errorMsg = "Array lengths in lsq12 minctracc protocol do not match." if (len(self.stepSize) != arrayLength or len(self.useGradient) != arrayLength or len(self.simplex) != arrayLength): print errorMsg raise else: return arrayLength def iterate(self): if not self.maxPairs: xfmsToAvg = {} lsq12ResampledFiles = {} for inputFH in self.inputs: """Create an array of xfms, to compute an average lsq12 xfm for each input""" xfmsToAvg[inputFH] = [] for targetFH in self.inputs: if inputFH != targetFH: lsq12 = LSQ12(inputFH, targetFH, self.blurs, self.stepSize, self.useGradient, self.simplex) self.p.addPipeline(lsq12.p) xfmsToAvg[inputFH].append(inputFH.getLastXfm(targetFH)) """Create average xfm for inputFH using xfmsToAvg array""" cmd = ["xfmavg"] for i in range(len(xfmsToAvg[inputFH])): cmd.append(InputFile(xfmsToAvg[inputFH][i])) avgXfmOutput = createBaseName(inputFH.transformsDir, inputFH.basename + "-avg-lsq12.xfm") cmd.append(OutputFile(avgXfmOutput)) xfmavg = CmdStage(cmd) xfmavg.setLogFile(LogFile(logFromFile(inputFH.logDir, avgXfmOutput))) self.p.addStage(xfmavg) self.lsq12AvgXfms[inputFH] = avgXfmOutput """ resample brain and add to array for mincAveraging""" if not self.likeFile: likeFile=inputFH else: likeFile=self.likeFile rslOutput = createBaseName(inputFH.resampledDir, inputFH.basename + "-resampled-lsq12.mnc") res = ma.mincresample(inputFH, inputFH, transform=avgXfmOutput, likeFile=likeFile, output=rslOutput, argArray=["-sinc"]) self.p.addStage(res) lsq12ResampledFiles[inputFH] = rslOutput """ After all registrations complete, setLastBasevol for each subject to be resampled file in lsq12 space. We can then call mincAverage on fileHandlers, as it will use the lastBasevol for each by default.""" for inputFH in self.inputs: inputFH.setLastBasevol(lsq12ResampledFiles[inputFH]) """ mincAverage all resampled brains and put in lsq12Directory""" self.lsq12Avg = abspath(self.lsq12Dir) + "/" + basename(self.lsq12Dir) + "-pairs.mnc" self.lsq12AvgFH = RegistrationPipeFH(self.lsq12Avg, basedir=self.lsq12Dir) avg = ma.mincAverage(self.inputs, self.lsq12AvgFH, output=self.lsq12Avg, defaultDir=self.lsq12Dir) self.p.addStage(avg) else: print "Registration using a specified number of max pairs not yet working. Check back soon!" sys.exit()
class initializeAndRunNLIN(object): """Class to setup target average (if needed), instantiate correct version of NLIN class, and run NLIN registration.""" def __init__(self, targetOutputDir, #Output directory for files related to initial target (often _lsq12) inputFiles, nlinDir, avgPrefix, #Prefix for nlin-1.mnc, ... nlin-k.mnc createAvg=True, #True=call mincAvg, False=targetAvg already exists targetAvg=None, #Optional path to initial target - passing name does not guarantee existence targetMask=None, #Optional path to mask for initial target nlin_protocol=None, reg_method=None): self.p = Pipeline() self.targetOutputDir = targetOutputDir self.inputFiles = inputFiles self.nlinDir = nlinDir self.avgPrefix = avgPrefix self.createAvg = createAvg self.targetAvg = targetAvg self.targetMask = targetMask self.nlin_protocol = nlin_protocol self.reg_method = reg_method # setup initialTarget (if needed) and initialize non-linear module self.setupTarget() self.initNlinModule() #iterate through non-linear registration and setup averages self.nlinModule.iterate() self.p.addPipeline(self.nlinModule.p) self.nlinAverages = self.nlinModule.nlinAverages self.nlinParams = self.nlinModule.nlinParams def setupTarget(self): if self.targetAvg: if isinstance(self.targetAvg, str): self.initialTarget = RegistrationPipeFH(self.targetAvg, mask=self.targetMask, basedir=self.targetOutputDir) self.outputAvg = self.targetAvg elif isinstance(self.targetAvg, RegistrationPipeFH): self.initialTarget = self.targetAvg self.outputAvg = self.targetAvg.getLastBasevol() if not self.initialTarget.getMask(): if self.targetMask: self.initialTarget.setMask(self.targetMask) else: print "You have passed a target average that is neither a string nor a file handler: " + str(self.targetAvg) print "Exiting..." else: self.targetAvg = abspath(self.targetOutputDir) + "/" + "initial-target.mnc" self.initialTarget = RegistrationPipeFH(self.targetAvg, mask=self.targetMask, basedir=self.targetOutputDir) self.outputAvg = self.targetAvg if self.createAvg: avg = mincAverage(self.inputFiles, self.initialTarget, output=self.outputAvg, defaultDir=self.targetOutputDir) self.p.addStage(avg) def initNlinModule(self): if self.reg_method=="mincANTS": self.nlinModule = NLINANTS(self.inputFiles, self.initialTarget, self.nlinDir, self.avgPrefix, self.nlin_protocol) elif self.reg_method=="minctracc": self.nlinModule = NLINminctracc(self.inputFiles, self.initialTarget, self.nlinDir, self.avgPrefix, self.nlin_protocol) else: logger.error("Incorrect registration method specified: " + self.reg_method) sys.exit()
class NLINBase(object): """ This is the parent class for any iterative non-linear registration. Subclasses should extend the following methods: addBlurStage() regAndResample() """ def __init__(self, inputArray, targetFH, nlinOutputDir, avgPrefix, nlin_protocol): self.p = Pipeline() """Initial inputs should be an array of fileHandlers with lastBasevol in lsq12 space""" self.inputs = inputArray """Initial target should be the file handler for the lsq12 average""" self.target = targetFH """Output directory should be _nlin """ self.nlinDir = nlinOutputDir """Prefix to pre-pend to averages at each generation""" self.avgPrefix = avgPrefix """Empty array that we will fill with averages as we create them""" self.nlinAverages = [] """Create the blurring resolution from the file resolution""" if nlin_protocol==None: self.fileRes = rf.returnFinestResolution(self.inputs[0]) else: self.fileRes = None # Create new nlin group for each input prior to registration for i in range(len(self.inputs)): self.inputs[i].newGroup(groupName="nlin") def addBlurStage(self): """ Add blurs to pipeline. Because blurs are handled differently by parameter arrays in minctracc and mincANTS subclasses, they are added to the pipeline via function call. """ pass def regAndResample(self): """Registration and resampling calls""" pass def iterate(self): for i in range(self.generations): outputName = "nlin-%g.mnc" % (i+1) if self.avgPrefix: outputName = str(self.avgPrefix) + "-" + outputName nlinOutput = abspath(self.nlinDir) + "/" + outputName nlinFH = RegistrationPipeFH(nlinOutput, mask=self.target.getMask(), basedir=self.nlinDir) self.addBlurStage(self.target, i) filesToAvg = [] for inputFH in self.inputs: self.addBlurStage(inputFH, i) self.regAndResample(inputFH, i, filesToAvg, nlinFH) """Because we don't reset lastBasevol on each inputFH, call mincAverage with files only. We create fileHandler first though, so we have log directory. This solution seems a bit hackish--may want to modify? Additionally, we are currently using the full RegistrationPipeFH class, but ultimately we'll want to create a third class that is somewhere between a full and base class. """ logBase = removeBaseAndExtension(nlinOutput) avgLog = createLogFile(nlinFH.logDir, logBase) avg = mincAverage(filesToAvg, nlinOutput, logFile=avgLog) self.p.addStage(avg) """Reset target for next iteration and add to array""" self.target = nlinFH self.nlinAverages.append(nlinFH) """Create a final nlin group to add to the inputFH. lastBasevol = by default, will grab the lastBasevol used in these calculations (e.g. lsq12) setLastXfm between final nlin average and inputFH will be set for stats calculations. """ if i == (self.generations -1): for inputFH in self.inputs: """NOTE: The last xfm being set below is NOT the result of a registration between inputFH and nlinFH, but rather is the output transform from the previous generation's average.""" finalXfm = inputFH.getLastXfm(self.nlinAverages[self.generations-2]) inputFH.newGroup(groupName="final") inputFH.setLastXfm(nlinFH, finalXfm)
class CalcStats(object): """Statistics calculation between an input and target. This class calculates multiple displacement fields, relative and absolute jacobians. General functionality as follows: 1. Class instantiated with input, target and statsKernels. Note that here, the statsKernels specified are blurs used to smooth the displacement fields prior to additional calculations. They may be a string of comma separated values or an array of doubles. 2. An additional transform may also be included to calculate absolute jacobians to a different space, as is described in the __init__ function, documentation and elsewhere in the code. 3. If needed, invert transform between input and target in setupXfms(). This is necessary as this class assumes that the target is the reference space, from which all stats are calculated. 4. Call fullStatsCalc. This calculates linear and pure nonlinear displacement before calculating jacobians. 5. Ability to recenter displacements using an average may be re-added in the future. """ def __init__(self, inputFH, targetFH, statsKernels, additionalXfm=None): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.blurs = [] self.setupBlurs(statsKernels) self.statsGroup = StatsGroup() self.setupXfms() """ additionalXfm is an optional transform that may be specified. If it is, it is concatenated with the lastXfm from input to target. This additional transform must also be in the same direction as the lastXfm (e.g. input to target) Example usage: if the lastXfm from input to target goes from lsq12 to nlin space and you would like to calculate the absolute jacobians to lsq6 space, the additional transform specified may be the lsq6 to lsq12 transform from input to target. """ self.additionalXfm = additionalXfm self.fullStatsCalc() def setupBlurs(self, statsKernels): if isinstance(statsKernels, list): self.blurs = statsKernels elif isinstance(statsKernels, str): for i in statsKernels.split(","): self.blurs.append(float(i)) else: print("Improper type of blurring kernels specified for stats calculation: " + str(statsKernels)) sys.exit() def setupXfms(self): self.xfm = self.inputFH.getLastXfm(self.targetFH) if not self.xfm: print("Cannot calculate statistics. No transform between input and target specified.") print("Input: " + self.inputFH.getLastBasevol()) print("Target: " + self.targetFH.getLastBasevol()) sys.exit() else: self.invXfm = self.targetFH.getLastXfm(self.inputFH) if not self.invXfm: xi = xfmInvert(self.xfm, FH=self.inputFH) self.p.addStage(xi) self.invXfm = xi.outputFiles[0] def fullStatsCalc(self): self.linAndNlinDisplacement() self.calcDetAndLogDet(useFullDisp=False) # Calculate relative jacobians self.calcDetAndLogDet(useFullDisp=True) # Calculate absolute jacobians def calcFullDisplacement(self): """Calculate full displacement from target to input. If an additionaXfm is specified, it is concatenated to self.xfm here """ if self.additionalXfm: outXfm = createOutputFileName(self.inputFH, self.xfm, "transforms", "_with_additional.xfm") xc = xfmConcat([self.additionalXfm, self.xfm], outXfm, fh.logFromFile(self.inputFH.logDir, outXfm)) self.p.addStage(xc) xi = xfmInvert(xc.outputFiles[0], FH=self.inputFH) self.p.addStage(xi) fullDisp = mincDisplacement(self.targetFH, self.inputFH, transform=xi.outputFiles[0]) else: fullDisp = mincDisplacement(self.targetFH, self.inputFH, transform=self.invXfm) self.p.addStage(fullDisp) self.fullDisp = fullDisp.outputFiles[0] def calcNlinDisplacement(self): """Calculate pure non-linear displacement from target to input 1. Concatenate self.invXfm (target to input xfm) and self.linearPartOfNlinXfm 2. Compute mincDisplacement on this transform. """ pureNlinXfm = createOutputFileName(self.inputFH, self.invXfm, "transforms", "_pure_nlin.xfm") xc = xfmConcat([self.invXfm, self.linearPartOfNlinXfm], pureNlinXfm, fh.logFromFile(self.inputFH.logDir, pureNlinXfm)) self.p.addStage(xc) nlinDisp = mincDisplacement(self.targetFH, self.inputFH, transform=pureNlinXfm) self.p.addStage(nlinDisp) self.nlinDisp = nlinDisp.outputFiles[0] def linAndNlinDisplacement(self): """ Calculation of full and pure non-linear displacements. The former is used to calculate absolute jacobians, the latter to calculate relative. The direction of the transforms and displacements is defined in each subclass. """ #1. Calculate linear part of non-linear xfm from input to target. # This is necessary prior to calculating the pure nonlinear displacement lpnl = linearPartofNlin(self.inputFH, self.targetFH) self.p.addStage(lpnl) self.linearPartOfNlinXfm = lpnl.outputFiles[0] # 2. Calculate the pure non-linear displacement self.calcNlinDisplacement() # 3. Calculate the full displacement self.calcFullDisplacement() def calcDetAndLogDet(self, useFullDisp=False): if useFullDisp: dispToUse = self.fullDisp #absolute jacobians else: dispToUse = self.nlinDisp #relative jacobians """Insert -1 at beginning of blurs array to include the calculation of unblurred jacobians.""" self.blurs.insert(0,-1) for b in self.blurs: """Create base name for determinant calculation.""" outputBase = fh.removeBaseAndExtension(dispToUse).split("_displacement")[0] """Calculate smoothed deformation field for all blurs other than -1""" if b != -1: fwhm = "--fwhm=" + str(b) outSmooth = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_smooth_displacement_fwhm" + str(b) + ".mnc") cmd = ["smooth_vector", "--clobber", "--filter", fwhm, InputFile(dispToUse), OutputFile(outSmooth)] smoothVec = CmdStage(cmd) smoothVec.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outSmooth))) self.p.addStage(smoothVec) """Set input for determinant calculation.""" inputDet = outSmooth nameAddendum = "_fwhm" + str(b) else: inputDet = dispToUse nameAddendum = "" outputDet = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_determinant" + nameAddendum + ".mnc") outDetShift = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_det_plus1" + nameAddendum + ".mnc") if useFullDisp: #absolute jacobians outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_absolute_log_determinant" + nameAddendum + ".mnc") else: #relative jacobians outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_relative_log_determinant" + nameAddendum + ".mnc") """Calculate the determinant, then add 1 (per mincblob weirdness)""" cmd = ["mincblob", "-clobber", "-determinant", InputFile(inputDet), OutputFile(outputDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outputDet))) self.p.addStage(det) cmd = ["mincmath", "-clobber", "-2", "-const", str(1), "-add", InputFile(outputDet), OutputFile(outDetShift)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outDetShift))) self.p.addStage(det) """Calculate log determinant (jacobian) and add to statsGroup.""" cmd = ["mincmath", "-clobber", "-2", "-log", InputFile(outDetShift), OutputFile(outLogDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outLogDet))) self.p.addStage(det) if useFullDisp: self.statsGroup.absoluteJacobians[b] = outLogDet else: self.statsGroup.relativeJacobians[b] = outLogDet
class HierarchicalMinctracc: """Default HierarchicalMinctracc currently does: 1. A standard three stage LSQ12 alignment. (See defaults for LSQ12 module.) 2. A six generation non-linear minctracc alignment. To override these defaults, lsq12 and nlin protocols may be specified. """ def __init__(self, inputFH, targetFH, lsq12_protocol=None, nlin_protocol=None, includeLinear = True, subject_matter = None, defaultDir="tmp"): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.lsq12_protocol = lsq12_protocol self.nlin_protocol = nlin_protocol self.includeLinear = includeLinear self.subject_matter = subject_matter self.defaultDir = defaultDir if ((self.lsq12_protocol == None and self.subject_matter==None) or self.nlin_protocol == None): # the resolution of the registration should be based on the target self.fileRes = rf.returnFinestResolution(self.targetFH) else: self.fileRes = None self.buildPipeline() def buildPipeline(self): # Do LSQ12 alignment prior to non-linear stages if desired if self.includeLinear: self.lsq12Params = mp.setLSQ12MinctraccParams(self.fileRes, subject_matter=self.subject_matter, reg_protocol=self.lsq12_protocol) lsq12reg = lsq12.LSQ12(self.inputFH, self.targetFH, blurs=self.lsq12Params.blurs, step=self.lsq12Params.stepSize, gradient=self.lsq12Params.useGradient, simplex=self.lsq12Params.simplex, w_translations=self.lsq12Params.w_translations, defaultDir=self.defaultDir) self.p.addPipeline(lsq12reg.p) # create the nonlinear registrations self.nlinParams = mp.setNlinMinctraccParams(self.fileRes, reg_protocol=self.nlin_protocol) for b in self.nlinParams.blurs: if b != -1: self.p.addStage(ma.blur(self.inputFH, b, gradient=True)) self.p.addStage(ma.blur(self.targetFH, b, gradient=True)) for i in range(len(self.nlinParams.stepSize)): #For the final stage, make sure the output directory is transforms. if i == (len(self.nlinParams.stepSize) - 1): self.defaultDir = "transforms" nlinStage = ma.minctracc(self.inputFH, self.targetFH, defaultDir=self.defaultDir, blur=self.nlinParams.blurs[i], gradient=self.nlinParams.useGradient[i], iterations=self.nlinParams.iterations[i], step=self.nlinParams.stepSize[i], w_translations=self.nlinParams.w_translations[i], simplex=self.nlinParams.simplex[i], memory=self.nlinParams.memory[i] if self.nlinParams.memory else None, optimization=self.nlinParams.optimization[i]) self.p.addStage(nlinStage)
class LabelAndFileResampling: def __init__(self, inputPipeFH, templatePipeFH, name="initial", createMask=False): self.p = Pipeline() self.name = name if createMask: resampleDefault = "tmp" labelsDefault = "tmp" else: resampleDefault = "resampled" labelsDefault = "labels" # Resample all inputLabels inputLabelArray = templatePipeFH.returnLabels(True) if len(inputLabelArray) > 0: """ for the initial registration, resulting labels should be added to inputLabels array for subsequent pairwise registration otherwise labels should be added to labels array for voting """ if self.name == "initial": addOutputToInputLabels = True else: addOutputToInputLabels = False for i in range(len(inputLabelArray)): """Note: templatePipeFH and inputPipeFH have the reverse order from how they are passed into this function. This is intentional because the mincresample classes use the first argument as the one from which to get the file to be resampled. Here, either the mask or labels to be resampled come from the template.""" if createMask: resampleStage = ma.mincresampleMask( templatePipeFH, inputPipeFH, defaultDir=labelsDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH, labelIndex=i, setInputLabels=addOutputToInputLabels) else: resampleStage = ma.mincresampleLabels( templatePipeFH, inputPipeFH, defaultDir=labelsDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH, labelIndex=i, setInputLabels=addOutputToInputLabels) self.p.addStage(resampleStage) # resample files resampleStage = ma.mincresample(templatePipeFH, inputPipeFH, defaultDir=resampleDefault, likeFile=inputPipeFH, argArray=["-invert"], outputLocation=inputPipeFH) self.p.addStage(resampleStage)
class NLINBase(object): """ This is the parent class for any iterative non-linear registration. Subclasses should extend the following methods: addBlurStage() regAndResample() """ def __init__(self, inputArray, targetFH, nlinOutputDir, avgPrefix, nlin_protocol): self.p = Pipeline() """Initial inputs should be an array of fileHandlers with lastBasevol in lsq12 space""" self.inputs = inputArray """Initial target should be the file handler for the lsq12 average""" self.target = targetFH """Output directory should be _nlin """ self.nlinDir = nlinOutputDir """Prefix to pre-pend to averages at each generation""" self.avgPrefix = avgPrefix """Empty array that we will fill with averages as we create them""" self.nlinAverages = [] """Create the blurring resolution from the file resolution""" if nlin_protocol == None: self.fileRes = rf.returnFinestResolution(self.inputs[0]) else: self.fileRes = None # Create new nlin group for each input prior to registration for i in range(len(self.inputs)): self.inputs[i].newGroup(groupName="nlin") def addBlurStage(self): """ Add blurs to pipeline. Because blurs are handled differently by parameter arrays in minctracc and mincANTS subclasses, they are added to the pipeline via function call. """ pass def regAndResample(self): """Registration and resampling calls""" pass def iterate(self): for i in range(self.generations): outputName = "nlin-%g.mnc" % (i + 1) if self.avgPrefix: outputName = str(self.avgPrefix) + "-" + outputName nlinOutput = abspath(self.nlinDir) + "/" + outputName nlinFH = RegistrationPipeFH(nlinOutput, mask=self.target.getMask(), basedir=self.nlinDir) self.addBlurStage(self.target, i) filesToAvg = [] for inputFH in self.inputs: self.addBlurStage(inputFH, i) self.regAndResample(inputFH, i, filesToAvg, nlinFH) """Because we don't reset lastBasevol on each inputFH, call mincAverage with files only. We create fileHandler first though, so we have log directory. This solution seems a bit hackish--may want to modify? Additionally, we are currently using the full RegistrationPipeFH class, but ultimately we'll want to create a third class that is somewhere between a full and base class. """ logBase = removeBaseAndExtension(nlinOutput) avgLog = createLogFile(nlinFH.logDir, logBase) avg = mincAverage(filesToAvg, nlinOutput, logFile=avgLog) self.p.addStage(avg) """Reset target for next iteration and add to array""" self.target = nlinFH self.nlinAverages.append(nlinFH) """Create a final nlin group to add to the inputFH. lastBasevol = by default, will grab the lastBasevol used in these calculations (e.g. lsq12) setLastXfm between final nlin average and inputFH will be set for stats calculations. """ if i == (self.generations - 1): for inputFH in self.inputs: """NOTE: The last xfm being set below is NOT the result of a registration between inputFH and nlinFH, but rather is the output transform from the previous generation's average.""" finalXfm = inputFH.getLastXfm( self.nlinAverages[self.generations - 2]) inputFH.newGroup(groupName="final") inputFH.setLastXfm(nlinFH, finalXfm)
class LSQ12(object): """Basic LSQ12 class. This class takes an input FileHandler and a targetFileHandler as required inputs. A series of minctracc calls will then produce the 12-parameter alignment. The number of minctracc calls and their parameters are controlled by four further arguments to the constructor: blurs: an array of floats containing the FWHM of the blurring kernel to be used for each call gradient: an array of booleans stating whether we should use the blur (False) or gradient (True) of each blur step: an array of floats containing the step used by minctracc in each call simplex: an array of floats containing the simplex used by minctracc in each call. The number of entries in those three (blurs, step, simplex) input arguments determines the number of minctracc calls executed in this module. For example, the following call: LSQ12(inputFH, targetFH, blurs=[10,5,2], gradient=[False,True,True], step=[4,4,4], simplex=[20,20,20]) will result in three successive minctracc calls, each initialized with the output transform of the previous call. """ def __init__(self, inputFH, targetFH, blurs=[0.3, 0.2, 0.15], step=[1, 0.5, 0.333333333333333], gradient=[False, True, False], simplex=[3, 1.5, 1], w_translations=[0.4, 0.4, 0.4], defaultDir="tmp"): # TO DO: Might want to take this out and pass in # of generations, since # checking happens there. if len(blurs) == len(step) == len(simplex): # do nothing - all lengths are the same and we're therefore happy pass else: logger.error( "The same number of entries are required for blurs, step, and simplex in LSQ12" ) sys.exit() self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.blurs = blurs self.step = step self.blurs = blurs self.gradient = gradient self.simplex = simplex self.w_translations = w_translations self.defaultDir = defaultDir self.blurFiles() self.buildPipeline() def blurFiles(self): for b in self.blurs: if b != -1: tblur = ma.blur(self.targetFH, b, gradient=True) iblur = ma.blur(self.inputFH, b, gradient=True) self.p.addStage(tblur) self.p.addStage(iblur) def buildPipeline(self): for i in range(len(self.blurs)): linearStage = ma.minctracc(self.inputFH, self.targetFH, blur=self.blurs[i], defaultDir=self.defaultDir, gradient=self.gradient[i], linearparam="lsq12", step=self.step[i], w_translations=self.w_translations[i], simplex=self.simplex[i]) self.p.addStage(linearStage)
class CalcStats(object): """Statistics calculation between an input and target. This class calculates multiple displacement fields, jacobians and scaled jacobians. It should be called once for each inputFH in your pipeline. General functionality as follows: 1. Class instantiated with input, target and blurs. May optionally specify array of input file handlers so that re-centering can be appropriately calculated. A scalingFactor may also be specified, for calculating the scaled jacobian determinants, as is described in the __init__ function and elsewhere in the code. 2. If needed, invert transform between input and target in setupXfms() 3. Call fullStatsCalc in calling class, which calculates linear and pure nonlinear displacement, as well as re-centering average, before calculating determinants and log determinants. 4. Alternate usage is to call calcFullDisplacement followed by calcDetAndLogDet, which will use full displacement (rather than just non-linear component) for calculating determinants. """ def __init__(self, inputFH, targetFH, blurs, inputArray=None, scalingFactor=None): self.p = Pipeline() self.inputFH = inputFH self.targetFH = targetFH self.blurs = blurs self.statsGroup = StatsGroup() self.setupXfms() """Optional inputArray used to calculate an average displacement and use for recentering.""" if inputArray: self.dispToAvg = [] self.setupDispArray(inputArray) else: self.dispToAvg = None """ Specify an optional xfm to be used when calculating the scaled jacobians. This jacobian will then be concatenated with the self.linearXfm, the linear portion of the final non-linear transform from input to target. A """ self.scalingFactor = scalingFactor def setupXfms(self): self.xfm = self.inputFH.getLastXfm(self.targetFH) if not self.xfm: print "Cannot calculate statistics. No transform between input and target specified." sys.exit() """Check for existence of inverse transform. If it doesn't exist, create it. """ self.invXfm = self.targetFH.getLastXfm(self.inputFH) if not self.invXfm: xi = xfmInvert(self.xfm, FH=self.inputFH) self.p.addStage(xi) self.invXfm = xi.outputFiles[0] def setupDispArray(self, inputArray): """NOTE: inputArray must be an array of file handlers. """ for iFH in inputArray: """Check to see if invXfm exists. If not, create name (but we don't actually need to construct the command here, as this will happen in its own CalcStats class)""" xfm = iFH.getLastXfm(self.targetFH) invXfm = self.targetFH.getLastXfm(iFH) if not invXfm: invXfm = createInvXfmName(iFH, xfm) nlinXfm = createPureNlinXfmName(iFH, invXfm) """Here we are assuming that the pure nlin displacement goes in the tmp directory. If we change this when the actual calculation is done, we do it here too. """ nlinDisp = setDispName(iFH, nlinXfm, "tmp") self.dispToAvg.append(nlinDisp) def fullStatsCalc(self): self.linAndNlinDisplacement() self.calcDetAndLogDet() def calcFullDisplacement(self): """Calculates the full displacement from the target to the source without removing the linear part""" fullDisp = mincDisplacement(self.targetFH, self.inputFH, transform=self.invXfm) self.p.addStage(fullDisp) self.fullDisp = fullDisp.outputFiles[0] def linAndNlinDisplacement(self): """ The function calculates both the linear and nonlinear portions of the displacement, in order to find pure nonlinear. Common space here is the target (usually an average of some sort). We also recentre pure non linear displacement. """ """Calculate linear part of non-linear xfm from input to target""" lpnl = linearPartofNlin(self.inputFH, self.targetFH) self.p.addStage(lpnl) self.linearXfm = lpnl.outputFiles[0] """Calculate full displacement from target to input""" self.calcFullDisplacement() """Calculate pure non-linear displacement from target to input 1. Concatenate linear and inverse target to input transform to get pure_nlin xfm 2. Compute mincDisplacement on this transform. """ nlinXfm = createPureNlinXfmName(self.inputFH, self.invXfm) xc = xfmConcat([self.linearXfm, self.invXfm], nlinXfm, fh.logFromFile(self.inputFH.logDir, nlinXfm)) self.p.addStage(xc) nlinDisp = mincDisplacement(self.targetFH, self.inputFH, transform=nlinXfm) self.p.addStage(nlinDisp) self.nlinDisp = nlinDisp.outputFiles[0] """Calculate average displacement and re-center non-linear displacement if an array of input file handlers was specified on instantiation. """ if self.dispToAvg: """Calculate average inverse displacement""" avgOutput = abspath(self.targetFH.basedir) + "/" + "average_inv_pure_displacement.mnc" logBase = fh.removeBaseAndExtension(avgOutput) avgLog = fh.createLogFile(self.targetFH.basedir, logBase) avg = mincAverageDisp(self.dispToAvg, avgOutput, logFile=avgLog) self.p.addStage(avg) """Centre pure nlin displacement by subtracting average from existing""" centredBase = fh.removeBaseAndExtension(self.nlinDisp).split("_displacement")[0] centredOut = fh.createBaseName(self.inputFH.statsDir, centredBase + "_centred_displacement.mnc") cmd = ["mincmath", "-clobber", "-sub", InputFile(self.nlinDisp), InputFile(avgOutput), OutputFile(centredOut)] centredDisp = CmdStage(cmd) centredDisp.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, centredOut))) self.p.addStage(centredDisp) """Reset centred displacement to be self.nlinDisp""" self.nlinDisp = centredOut def calcDetAndLogDet(self, useFullDisp=False): #Lots of repetition here--let's see if we can't make some functions. """useFullDisp indicates whether or not to use full displacement field or non-linear component only""" if useFullDisp: dispToUse = self.fullDisp else: dispToUse = self.nlinDisp """Insert -1 at beginning of blurs array to include the calculation of unblurred jacobians.""" self.blurs.insert(0,-1) for b in self.blurs: """Calculate default output filenames and set input for determinant calculation.""" outputBase = fh.removeBaseAndExtension(dispToUse).split("_displacement")[0] inputDet = dispToUse outputDet = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_determinant.mnc") outDetShift = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_det_plus1.mnc") outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_log_determinant.mnc") outLogDetScaled = fh.createBaseName(self.inputFH.statsDir, outputBase + "_log_determinant_scaled.mnc") """Calculate smoothed deformation field for all blurs other than -1""" if b != -1: fwhm = "--fwhm=" + str(b) outSmooth = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_smooth_displacement_fwhm" + str(b) + ".mnc") cmd = ["smooth_vector", "--clobber", "--filter", fwhm, InputFile(dispToUse), OutputFile(outSmooth)] smoothVec = CmdStage(cmd) smoothVec.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outSmooth))) self.p.addStage(smoothVec) """Override file name defaults for each blur and set input for determinant calculation.""" inputDet = outSmooth outputDet = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_determinant_fwhm" + str(b) + ".mnc") outDetShift = fh.createBaseName(self.inputFH.tmpDir, outputBase + "_det_plus1_fwhm" + str(b) + ".mnc") outLogDet = fh.createBaseName(self.inputFH.statsDir, outputBase + "_log_determinant_fwhm" + str(b) + ".mnc") outLogDetScaled = fh.createBaseName(self.inputFH.statsDir, outputBase + "_log_determinant_scaled_fwhm" + str(b) + ".mnc") """Calculate the determinant, then add 1 (per mincblob weirdness)""" cmd = ["mincblob", "-clobber", "-determinant", InputFile(inputDet), OutputFile(outputDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outputDet))) self.p.addStage(det) cmd = ["mincmath", "-clobber", "-2", "-const", str(1), "-add", InputFile(outputDet), OutputFile(outDetShift)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outDetShift))) self.p.addStage(det) """Calculate log determinant (jacobian) and add to statsGroup.""" cmd = ["mincmath", "-clobber", "-2", "-log", InputFile(outDetShift), OutputFile(outLogDet)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outLogDet))) self.p.addStage(det) self.statsGroup.jacobians[b] = outLogDet """If self.linearXfm present, calculate scaled log determinant (scaled jacobian) and add to statsGroup""" if not useFullDisp: """ If self.scaleFactor is specified, then concatenate this additional transform with self.linearXfm. Typically, this will come from an LSQ12 registration, but may come from another alignment. """ if self.scalingFactor: toConcat = [self.scalingFactor, self.linearXfm] self.fullLinearXfm = fh.createBaseName(self.inputFH.transformsDir, self.inputFH.basename + "_full_linear.xfm") logFile=fh.logFromFile(self.inputFH.logDir, fh.removeBaseAndExtension(self.fullLinearXfm)) concat = xfmConcat(toConcat, self.fullLinearXfm, logFile=logFile) self.p.addStage(concat) else: self.fullLinearXfm = self.linearXfm cmd = ["scale_voxels", "-clobber", "-invert", "-log", InputFile(self.fullLinearXfm), InputFile(outLogDet), OutputFile(outLogDetScaled)] det = CmdStage(cmd) det.setLogFile(LogFile(fh.logFromFile(self.inputFH.logDir, outLogDetScaled))) self.p.addStage(det) self.statsGroup.scaledJacobians[b] = outLogDetScaled else: self.statsGroup.scaledJacobians = None