From 549b87c68dd72461d1b4e3abedf8e639b1fe0bde Mon Sep 17 00:00:00 2001 From: vicory Date: Wed, 23 May 2018 15:04:28 -0400 Subject: [PATCH 1/4] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b583eb5..8b8cd03 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# TimeLapse-OpticalFlow \ No newline at end of file +# TimeLapse-OpticalFlow + +This repo currently consists of: +* Two small c++ programs: one for registering two images, one for interpolating between two registered images +* A python wrapper script to create a TimeLapse from a set of images + +C++ build dependencies are ITK and OpenCV. + +Locations of some programs/data are hard-coded in the Python wrapper, those will need to be changed depending on where its running. From 43b64611f76e91f9a1f42ce86b10686a6b4da013 Mon Sep 17 00:00:00 2001 From: Stephen Aylward Date: Mon, 30 Jul 2018 14:30:08 -0400 Subject: [PATCH 2/4] ENH: Uses linear discriminant analysis to improve registration Converts the color channels to grayscale prior to registration. Grayscale image is the weighted linear combination of color channels that best separate the pixels under the mask from the pixels around the mask. --- timeLapseRegistrator.py | 286 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 timeLapseRegistrator.py diff --git a/timeLapseRegistrator.py b/timeLapseRegistrator.py new file mode 100644 index 0000000..20892bc --- /dev/null +++ b/timeLapseRegistrator.py @@ -0,0 +1,286 @@ +#!/usr/bin/python + +# This program will register a sequence of photographs to align a masked +# region + +import os +import imp +import fnmatch +import argparse +from subprocess import run +from shutil import copyfile + +import itk + +def ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, + convert, iteration, portion ): + edge_scale = "5" + + file_list = [] + for root, dirnames, filenames in os.walk( folder ): + for filename in fnmatch.filter( filenames, glob ): + file_list.append( os.path.join( root, filename ) ) + number_of_files = len( file_list ) + + print( "Number of files = " + str( number_of_files ) ) + + #Make a training mask + run( ["ImageMath", maskFile, "-M", "1", "10", "255", "0", + "-a", "1", "-1", maskFile, + "-a", "0.5", "1", maskFile, + "-W", "0", tmpDir+"/maskPlus.mha" ] ) + run( [convert, "-separate", file_list[0], tmpDir+"/base.png" ] ) + run( ["EnhanceUsingDiscriminantAnalysis", + "--saveBasisInfo", tmpDir+"/basis.mlda", + "--objectId", "255,127", + "--labelmap", tmpDir+"/maskPlus.mha", + tmpDir+"/base-0.png,"+tmpDir+"/base-1.png,"+tmpDir+"/base-2.png", + tmpDir+"/baseEnh"] ) + + for i, input_file in enumerate( file_list ): + + baseFile = input_file + if iteration == 0: + curFile = input_file + else: + curFile = input_file + "-I" + str( iteration-1 ) + \ + "_RegAtlas.png" + run( [convert, "-separate", curFile, tmpDir+"/curFile.png" ] ) + + print( "*** " + baseFile ) + + if i>0 and i Date: Tue, 11 Sep 2018 13:44:23 -0400 Subject: [PATCH 3/4] ENH: Streamlined registration script and simplified workflow --- timeLapseRegistrator.py | 291 +++++++++++++--------------------------- 1 file changed, 93 insertions(+), 198 deletions(-) diff --git a/timeLapseRegistrator.py b/timeLapseRegistrator.py index 20892bc..224c59c 100644 --- a/timeLapseRegistrator.py +++ b/timeLapseRegistrator.py @@ -12,210 +12,137 @@ import itk -def ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, - convert, iteration, portion ): +def ApplyTimeLapseRegistrator( inDir, outDir, glob, maskFile, tmpDir, convert ): + # TO DO: This scale should be proportional to the mask size or adjusted + # based on some similar logic edge_scale = "5" file_list = [] - for root, dirnames, filenames in os.walk( folder ): + filename_list = [] + for root, dirnames, filenames in os.walk( inDir ): for filename in fnmatch.filter( filenames, glob ): + filename_list.append( filename ) file_list.append( os.path.join( root, filename ) ) + break number_of_files = len( file_list ) print( "Number of files = " + str( number_of_files ) ) - #Make a training mask - run( ["ImageMath", maskFile, "-M", "1", "10", "255", "0", - "-a", "1", "-1", maskFile, - "-a", "0.5", "1", maskFile, - "-W", "0", tmpDir+"/maskPlus.mha" ] ) - run( [convert, "-separate", file_list[0], tmpDir+"/base.png" ] ) + # Make a training mask + # Label pixels in the mask at 255 and pixels just outside that + # region as 127. + run( ["ImageMath", maskFile, "-M", "1", "30", "255", "0", + "-a", "1", "-1", maskFile, + "-a", "0.5", "1", maskFile, + "-W", "0", tmpDir+"/maskPlus.mha" ] ) + + # Register everything with the time 0 image + atlasFile = file_list[0] + run( [convert, atlasFile, outDir + "/" + filename_list[0] + "_RegAtlas.png" ] ) + + # Optimize use of color images + # Split the image into color channels and determine optimum + # linear combination of color channels that best distinguish + # the pixels in the mask from the pixels just outside of the mask + run( [convert, "-separate", atlasFile, tmpDir+"/base.png" ] ) run( ["EnhanceUsingDiscriminantAnalysis", - "--saveBasisInfo", tmpDir+"/basis.mlda", - "--objectId", "255,127", - "--labelmap", tmpDir+"/maskPlus.mha", - tmpDir+"/base-0.png,"+tmpDir+"/base-1.png,"+tmpDir+"/base-2.png", - tmpDir+"/baseEnh"] ) - - for i, input_file in enumerate( file_list ): - - baseFile = input_file - if iteration == 0: - curFile = input_file - else: - curFile = input_file + "-I" + str( iteration-1 ) + \ - "_RegAtlas.png" - run( [convert, "-separate", curFile, tmpDir+"/curFile.png" ] ) - - print( "*** " + baseFile ) - - if i>0 and i0: + tmpCurFile = tmpDir + "/" + filename_list[i] + outCurFile = outDir + "/" + filename_list[i] + + preTransformFile = tmpDir + "/" + filename_list[i-1] + "_RegAtlas.tfm" # Blend color channels using basis run( [convert, "-separate", curFile, tmpDir+"/base.png" ] ) run( ["EnhanceUsingDiscriminantAnalysis", "--loadBasisInfo", tmpDir+"/basis.mlda", "--objectId", "255,127", - tmpDir+"/base-0.png,"+tmpDir+"/base-1.png,"+tmpDir+"/base-2.png", - tmpDir+"/Temp1B"] ) - run( ["ImageMath", tmpDir+"/Temp1B.basis00.mha", "-b", edge_scale, - "-w", tmpDir+"/Temp1B.mha"] ) - - #run( ["ImageMath", curFile, "-B", edge_scale, "1", "0", "-w", - #tmpDir+"/TempX.mha"] ) - #run( ["ImageMath", curFile, "-B", edge_scale, "1", "1", "-w", - #tmpDir+"/TempY.mha"] ) - #run( ["ImageMath", tmpDir+"/TempX.mha", "-a", "0.5", "0.5", - #tmpDir+"/TempY.mha", - #"-p", "0", "-w", tmpDir+"/Temp1B.mha"] ) - #run( ["EnhanceContrastUsingAHE", curFile, - #tmpDir+"/Temp1B.mha"] ) + tmpDir + "/base-0.png," + \ + tmpDir + "/base-1.png," + \ + tmpDir + "/base-2.png", + tmpDir + "/cur"] ) + run( ["ImageMath", tmpDir+"/cur.basis00.mha", + "-b", edge_scale, + "-w", tmpDir + "/curB.mha"] ) - run( [convert, "-separate", preFile, tmpDir+"/base.png" ] ) - run( ["EnhanceUsingDiscriminantAnalysis", - "--loadBasisInfo", tmpDir+"/basis.mlda", - "--objectId", "255,127", - tmpDir+"/base-0.png,"+tmpDir+"/base-1.png,"+tmpDir+"/base-2.png", - tmpDir+"/TempPreB"] ) - run( ["ImageMath", tmpDir+"/TempPreB.basis00.mha", "-b", edge_scale, - "-w", tmpDir+"/TempPreB.mha"] ) - # - #run( ["ImageMath", preFile, "-B", edge_scale, "1", "0", "-w", - #tmpDir+"/TempX.mha"] ) - #run( ["ImageMath", preFile, "-B", edge_scale, "1", "1", "-w", - #tmpDir+"/TempY.mha"] ) - #run( ["ImageMath", tmpDir+"/TempX.mha", "-a", "0.5", "0.5", - #tmpDir+"/TempY.mha", - #"-p", "0", "-w", tmpDir+"/TempPreB.mha"] ) - #run( ["EnhanceContrastUsingAHE", preFile, - #tmpDir+"/TempPreB.mha"] ) - - run( [convert, "-separate", postFile, tmpDir+"/base.png" ] ) - run( ["EnhanceUsingDiscriminantAnalysis", - "--loadBasisInfo", tmpDir+"/basis.mlda", - "--objectId", "255,127", - tmpDir+"/base-0.png,"+tmpDir+"/base-1.png,"+tmpDir+"/base-2.png", - tmpDir+"/TempPostB"] ) - run( ["ImageMath", tmpDir+"/TempPostB.basis00.mha", "-b", edge_scale, - "-w", tmpDir+"/TempPostB.mha"] ) - #run( ["ImageMath", postFile, "-B", edge_scale, "1", "0", "-w", - #tmpDir+"/TempX.mha"] ) - #run( ["ImageMath", postFile, "-B", edge_scale, "1", "1", "-w", - #tmpDir+"/TempY.mha"] ) - #run( ["ImageMath", tmpDir+"/TempX.mha", "-a", "0.5", "0.5", - #tmpDir+"/TempY.mha", - #"-p", "0", "-w", tmpDir+"/TempPostB.mha"] ) - #run( ["EnhanceContrastUsingAHE", postFile, - #tmpDir+"/TempPostB.mha"] ) - - # Define the atlas (blurred) for the current file - run( ["RegisterImages", tmpDir+"/TempPreB.mha", - tmpDir+"/TempPostB.mha", - "--resampledImage", tmpDir+"/AtlasB.mha", - "--saveTransform", tmpDir+"/AtlasB.tfm", - "--registration", "PipelineAffine", - "--initialization", "ImageCenters", - "--metric", "MeanSqrd", - "--expectedRotation", "0.001", - "--expectedScale", "0.01", - "--expectedSkew", "0.0001", - "--expectedOffset", "60", - "--fixedImageMask", preMaskFile, - "--affineSamplingRatio", "0.5", - "--affineMaxIterations", "1000", - "--rigidSamplingRatio", "0.5", - "--rigidMaxIterations", "1000"] ) - #"--resampledImagePortion", "0.5", - #"--skipInitialRandomSearch", - #"--loadTransform", tmpDir+"/AtlasB.tfm", - - run( ["ImageMath", tmpDir+"/AtlasB.mha", - "-a", "0.4", "0.6", tmpDir+"/TempPreB.mha", - "-w", tmpDir+"/AtlasB.mha"] ) - - # Register the pre image (blurred) to the atlas - run( ["RegisterImages", tmpDir+"/TempPreB.mha", - tmpDir+"/AtlasB.mha", - "--resampledImage", tmpDir+"/AtlasBRegTempPreB.mha", - "--saveTransform", tmpDir+"/AtlasBRegTempPreB.tfm", - "--registration", "PipelineAffine", - "--initialization", "ImageCenters", - "--metric", "MeanSqrd", - "--expectedRotation", "0.001", - "--expectedScale", "0.01", - "--expectedSkew", "0.0001", - "--expectedOffset", "10", - "--fixedImageMask", preMaskFile, - "--affineSamplingRatio", "0.5", - "--affineMaxIterations", "1000", - "--rigidSamplingRatio", "0.5", - "--rigidMaxIterations", "1000"] ) - #"--skipInitialRandomSearch", - #"--loadTransform", tmpDir+"/AtlasBRegTempPreB.tfm", - - - # Produce a mask for the atlas - # by applying the pre image to atlas transform to the - # pre image mask - run( ["RegisterImages", tmpDir+"/AtlasB.mha", preMaskFile, - "--resampledImage", tmpDir+"/MaskRegAtlasB.mha", - "--loadTransform", tmpDir+"/AtlasBRegTempPreB.tfm", - "--invertLoadedTransform", - "--interpolation", "NearestNeighbor", - "--registration", "None", - "--rigidMaxIterations", "0"] ) - #"--initialization", "None", - # Register the current image (blurred) to the atlas run( ["RegisterImages", tmpDir+"/AtlasB.mha", - tmpDir+"/Temp1B.mha", - "--resampledImage", tmpDir+"/Temp1BRegAtlasB.mha", - "--saveTransform", tmpDir+"/RegAtlas.tfm", - "--resampledImagePortion", str( portion ), - "--registration", "PipelineAffine", - "--metric", "MeanSqrd", - "--initialization", "ImageCenters", + tmpDir+"/curB.mha", + "--resampledImage", tmpCurFile + "_RegAtlasB.mha", + "--saveTransform", tmpCurFile + "_RegAtlas.tfm", + "--registration", "Affine", + "--loadTransform", preTransformFile, + "--initialization", "None", "--expectedRotation", "0.001", "--expectedScale", "0.01", "--expectedSkew", "0.0001", "--expectedOffset", "60", - "--fixedImageMask", tmpDir+"/MaskRegAtlasB.mha", + "--fixedImageMask", regMaskFile, "--affineSamplingRatio", "0.5", "--affineMaxIterations", "1000", "--rigidSamplingRatio", "0.5", "--rigidMaxIterations", "1000"] ) - #"--skipInitialRandomSearch", - #"--loadTransform", tmpDir+"/RegAtlas.tfm", + #"--metric", "MeanSqrd", # Apply the image to atlas transform to the current image (raw) - run( [convert, "-separate", curFile, tmpDir+"/base.png" ] ) - run( ["RegisterImages", tmpDir+"/AtlasB.mha", + run( ["RegisterImages", atlasFile, tmpDir+"/base-0.png", "--resampledImage", tmpDir+"/RegAtlas-0.mha", - "--loadTransform",tmpDir+"/RegAtlas.tfm", + "--loadTransform",tmpCurFile + "_RegAtlas.tfm", "--registration", "None", "--initialization", "None", "--rigidMaxIterations", "0"] ) run( ["RegisterImages", tmpDir+"/AtlasB.mha", tmpDir+"/base-1.png", "--resampledImage", tmpDir+"/RegAtlas-1.mha", - "--loadTransform",tmpDir+"/RegAtlas.tfm", + "--loadTransform",tmpCurFile + "_RegAtlas.tfm", "--registration", "None", "--initialization", "None", "--rigidMaxIterations", "0"] ) run( ["RegisterImages", tmpDir+"/AtlasB.mha", tmpDir+"/base-2.png", "--resampledImage", tmpDir+"/RegAtlas-2.mha", - "--loadTransform",tmpDir+"/RegAtlas.tfm", + "--loadTransform",tmpCurFile + "_RegAtlas.tfm", "--registration", "None", "--initialization", "None", "--rigidMaxIterations", "0"] ) @@ -231,46 +158,14 @@ def ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, tmpDir+"/RegAtlas-2.png", "-colorspace", "sRGB", "-type", "truecolor", - baseFile + "-I" + str( iteration ) + "_RegAtlas.png"] ) - - # Make the atlas make the registered current image mask - run( ["ImageMath", tmpDir+"/MaskRegAtlasB.mha", - "-W", "0", - baseFile + "-I" + str( iteration ) + "_Mask.png"] ) - - else: - # First image we don't perform a registration - run( [convert, baseFile, - baseFile + "-I" + str( iteration ) + "_RegAtlas.png"] ) - run( [convert, maskFile, - baseFile + "-I" + str( iteration ) + "_Mask.png"] ) - - # Generate an identity transform - #run( ["RegisterImages", baseFile, maskFile, - #"--saveTransform", tmpDir+"/identity.tfm", - #"--registration", "None", - #"--initialization", "None", - #"--rigidMaxIterations", "0"] ) - #copyfile( tmpDir+"/identity.tfm", tmpDir+"/AtlasB.tfm" ) - #copyfile( tmpDir+"/identity.tfm", - #tmpDir+"/AtlasBRegTempPreB.tfm" ) - #copyfile( tmpDir+"/identity.tfm", tmpDir+"/MaskRegAtlasB.tfm" ) - #copyfile( tmpDir+"/identity.tfm", tmpDir+"/RegAtlas.tfm" ) - + outCurFile + "_RegAtlas.png"] ) -def ApplyTimeLapseRegistrator( folder, glob, maskFile, tmpDir, convert ): - # Make three registration passes through the data. - ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, - convert, 0, 1.0 ) - #ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, - # convert, 1, 1.0 ) - #ApplyTimeLapseRegistratorIteration( folder, glob, maskFile, tmpDir, - # convert, 2, 1.0 ) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Apply timeLapseRegistrator to files in a folder.' ) - parser.add_argument( 'folder', help='Folder to apply the script to.' ) + parser.add_argument( 'inputFolder', help='Folder to apply the script to.' ) + parser.add_argument( 'outputFolder', help='Folder to save the results to.' ) parser.add_argument( '--glob', help='Glob to find files recursively in the given folder.', default='*.???' ) @@ -282,5 +177,5 @@ def ApplyTimeLapseRegistrator( folder, glob, maskFile, tmpDir, convert ): parser.add_argument( '--tmpDir', help='Folder for Temp files.', default='tmp' ) args = parser.parse_args() - ApplyTimeLapseRegistrator( args.folder, args.glob, args.maskFile, - args.tmpDir, args.convert ) + ApplyTimeLapseRegistrator( args.inputFolder, args.outputFolder, + args.glob, args.maskFile, args.tmpDir, args.convert ) From fcc39854349a6594cb5f2ac824fcb40d0c7e6417 Mon Sep 17 00:00:00 2001 From: Stephen Aylward Date: Tue, 11 Sep 2018 13:47:36 -0400 Subject: [PATCH 4/4] ENH: Files created in support of simplified registration and workflow --- ProcessInputDir.sh | 19 +++++++++++++++ timeLapseMovieMaker.py | 39 +++++++++++++++++++++++++++++++ timeLapseOpticFlowInterpolator.py | 34 +++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 ProcessInputDir.sh create mode 100644 timeLapseMovieMaker.py create mode 100644 timeLapseOpticFlowInterpolator.py diff --git a/ProcessInputDir.sh b/ProcessInputDir.sh new file mode 100644 index 0000000..8780966 --- /dev/null +++ b/ProcessInputDir.sh @@ -0,0 +1,19 @@ +#! /bin/sh -f +rm -rf ${1}/tmp +rm -rf ${1}/tmpOutput +mkdir ${1}/tmp +mkdir ${1}/tmpOutput + +python timeLapseRegistrator.py --glob "*.jpg" --convert /c/Program\ Files/ImageMagick-7.0.3-Q16/convert.exe --maskFile ${1}/mask.jpg --tmpDir ${1}/tmp ${1}/input ${1}/tmpOutput +python timeLapseOpticFlowInterpolator.py --glob "*_RegAtlas.png" --interp /c/src/TimeLapse-OpticalFlow-Release/InterpByOpticalFlow.exe ./${1}/tmpOutput +python timeLapseMovieMaker.py --convert /c/Program\ Files/ImageMagick-7.0.3-Q16/convert.exe --glob "*_RegAtlas_?.png" ${1}/tmpOutput ${1}/movie.gif +convert -coalesce ${1}/movie.gif ${1}/tmp/movie%04d.png +ffmpeg -r 10 -i ${1}/tmp/movie%04d.png -vcodec mpeg4 -y ${1}/movie.mp4 + +rm -rf ${1}/tmpOutputOrg +cp -r ${1}/input ${1}/tmpOutputOrg + +python timeLapseOpticFlowInterpolator.py --glob "*.jpg" --interp /c/src/TimeLapse-OpticalFlow-Release/InterpByOpticalFlow.exe ${1}/tmpOutputOrg +python timeLapseMovieMaker.py --convert /c/Program\ Files/ImageMagick-7.0.3-Q16/convert.exe --glob "*_?.jpg" ${1}/tmpOutputOrg ${1}/movie-org.gif +convert -coalesce ${1}/movie-org.gif ${1}/tmpOutputOrg/movie-org%04d.png +ffmpeg -r 10 -i ${1}/tmpOutputOrg/movie-org%04d.png -vcodec mpeg4 -y ${1}/movie-org.mp4 diff --git a/timeLapseMovieMaker.py b/timeLapseMovieMaker.py new file mode 100644 index 0000000..c04a005 --- /dev/null +++ b/timeLapseMovieMaker.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +# This program will convert sequence of images into a movie + +import os +import imp +import fnmatch +import argparse +from subprocess import run + +def ApplyTimeLapseMovieMaker( convert, folder, glob, speed, movie ): + command_string = [convert, "-delay", speed, "-loop", "0"] + for root, dirnames, filenames in os.walk( folder ): + for filename in fnmatch.filter( filenames, glob ): + command_string.append( str( os.path.join( root, filename ) ) ) + break # Only use files in top level subdir + command_string.append( movie ) + + print( command_string ) + + run( command_string ) + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Apply timeLapseMovieMaker to files in a folder.' ) + parser.add_argument( 'folder', help='Folder to apply the script to.' ) + parser.add_argument( 'movie', help='Output movie name.' ) + parser.add_argument( '--convert', + help='Path to the convert executable from ImageMagick.', + default='convert' ) + parser.add_argument( '--glob', + help='Glob to find files recursively in the given folder.', + default='*.???' ) + parser.add_argument( '--speed', + help='playback speedup.', + default='10' ) + args = parser.parse_args() + ApplyTimeLapseMovieMaker( args.convert, args.folder, args.glob, + args.speed, args.movie ) diff --git a/timeLapseOpticFlowInterpolator.py b/timeLapseOpticFlowInterpolator.py new file mode 100644 index 0000000..f738788 --- /dev/null +++ b/timeLapseOpticFlowInterpolator.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +import os +import fnmatch +import argparse +import shutil as sh +from subprocess import run + +def ApplyTimeLapseOpticFlowInterpolator(folder, glob, interp): + file_list = [] + for root, dirnames, filenames in os.walk(folder): + for filename in fnmatch.filter(filenames, glob): + file_list.append(os.path.join(root, filename)) + break # only use files in first subdir + number_of_files = len(file_list) + print("Number of files = " + str(number_of_files)) + for i, input_file in enumerate(file_list): + fileName1 = input_file + if i