def testAlignmentTransformTranslate(self): record = nornir_imageregistration.AlignmentRecord((1, 1), 100, 0) transform = record.ToTransform([10, 10], [10, 10]) # OK, we should be able to map points TransformCheck(self, transform, [[4.5, 4.5]], [[5.5, 5.5]])
def testAlignmentTransformSizeMismatchWithRotation(self): record = nornir_imageregistration.AlignmentRecord((0, 0), 100, 90) self.assertEqual(round(record.rangle, 3), round(pi / 2.0, 3), "Degrees angle not converting to radians") transform = record.ToTransform([100, 100], [10, 10]) # OK, we should be able to map points TransformCheck(self, transform, [[2.5, 2.5]], [[51.5, 47.5]]) TransformCheck(self, transform, [[7.5, 7.5]], [[46.5, 52.5]])
def TestReadWriteTransform(self): '''A simple test of a transform which maps points from a 10,10 image to a 100,100 without translation or rotation''' WarpedImagePath = os.path.join(self.ImportedDataPath, "10x10.png") self.assertTrue(os.path.exists(WarpedImagePath), "Missing test input") FixedImagePath = os.path.join(self.ImportedDataPath, "1000x100.png") self.assertTrue(os.path.exists(FixedImagePath), "Missing test input") FixedSize = (100, 1000) WarpedSize = (10, 10) arecord = nornir_imageregistration.AlignmentRecord(peak=(0, 0), weight=100, angle=0) alignmentTransform = arecord.ToTransform(FixedSize, WarpedSize) self.assertEqual( FixedSize[0], nornir_imageregistration.GetImageSize(FixedImagePath)[0]) self.assertEqual( FixedSize[1], nornir_imageregistration.GetImageSize(FixedImagePath)[1]) self.assertEqual( WarpedSize[0], nornir_imageregistration.GetImageSize(WarpedImagePath)[0]) self.assertEqual( WarpedSize[1], nornir_imageregistration.GetImageSize(WarpedImagePath)[1]) TransformCheck(self, alignmentTransform, [[0, 0]], [[45, 495]]) TransformCheck(self, alignmentTransform, [[9, 9]], [[54, 504]]) # OK, try to save the stos file and reload it. Make sure the transforms match savedstosObj = arecord.ToStos(FixedImagePath, WarpedImagePath, PixelSpacing=1) self.assertIsNotNone(savedstosObj) stosfilepath = os.path.join(self.VolumeDir, 'TestRWScaleOnly.stos') savedstosObj.Save(stosfilepath) loadedStosObj = StosFile.Load(stosfilepath) self.assertIsNotNone(loadedStosObj) loadedTransform = nornir_imageregistration.transforms.LoadTransform( loadedStosObj.Transform) self.assertIsNotNone(loadedTransform) self.assertTrue( (alignmentTransform.points == loadedTransform.points).all(), "Transform different after save/load") TransformCheck(self, loadedTransform, [[0, 0]], [[45, 495]]) TransformCheck(self, alignmentTransform, [[9, 9]], [[54, 504]])
def testRotation(self): record = nornir_imageregistration.AlignmentRecord((0, 0), 100, 90) self.assertEqual(round(record.rangle, 3), round(pi / 2.0, 3), "Degrees angle not converting to radians") # Get the corners for a 10,10 image rotated 90 degrees predictedArray = np.array([[9, 0], [0, 0], [9, 9], [0, 9]]) # predictedArray[:, [0, 1]] = predictedArray[:, [1, 0]] # Swapped when GetTransformedCornerPoints switched to Y,X points Corners = record.GetTransformedCornerPoints([10, 10]) self.assertTrue((Corners == predictedArray).all()) transform = record.ToTransform([10, 10], [10, 10]) TransformCheck(self, transform, [[4.5, 4.5]], [[4.5, 4.5]]) TransformCheck(self, transform, [[0, 0]], [[9, 0]])
def testAlignmentRecord(self): record = nornir_imageregistration.AlignmentRecord((2.5, 0), 100, 90) self.assertEqual(round(record.rangle, 3), round(pi / 2.0, 3), "Degrees angle not converting to radians") # Get the corners for a 10,10 image rotated 90 degrees predictedArray = np.array([[11.5, 0], [2.5, 0], [11.5, 9], [2.5, 9]]) # predictedArray[:, [0, 1]] = predictedArray[:, [1, 0]] # Swapped when GetTransformedCornerPoints switched to Y,X points Corners = record.GetTransformedCornerPoints([10, 10]) self.assertTrue((Corners == predictedArray).all()) record = nornir_imageregistration.AlignmentRecord((-2.5, 2.5), 100, 90) self.assertEqual(round(record.rangle, 3), round(pi / 2.0, 3), "Degrees angle not converting to radians") # Get the corners for a 10,10 image rotated 90 degrees predictedArray = np.array([[6.5, 2.5], [-2.5, 2.5], [6.5, 11.5], [-2.5, 11.5]]) # predictedArray[:, [0, 1]] = predictedArray[:, [1, 0]] # Swapped when GetTransformedCornerPoints switched to Y,X points Corners = record.GetTransformedCornerPoints([10, 10]) self.assertTrue((Corners == predictedArray).all())
def testTranslate(self): peak = [3, 1] record = nornir_imageregistration.AlignmentRecord(peak, 100, 0) # Get the corners for a 10,10 translated 3x, 1y predictedArray = np.array([[3, 1], [3, 10], [12, 1], [12, 10]]) # predictedArray[:, [0, 1]] = predictedArray[:, [1, 0]] # Swapped when GetTransformedCornerPoints switched to Y,X points Corners = record.GetTransformedCornerPoints([10, 10]) self.assertTrue((Corners == predictedArray).all()) transform = record.ToTransform([10, 10], [10, 10]) TransformCheck(self, transform, [[0, 0]], [peak]) TransformCheck(self, transform, [[4.5, 4.5]], [[7.5, 5.5]])
def FindOffset(FixedImage, MovingImage, MinOverlap=0.0, MaxOverlap=1.0, FFT_Required=True, FixedImageShape=None, MovingImageShape=None): '''return an alignment record describing how the images overlap. The alignment record indicates how much the moving image must be rotated and translated to align perfectly with the FixedImage. If adjusting control points the peak can be added to the fixed image's control point, or subtracted from the warped image's control point (accounting for any transform used to create the warped image) to align the images. :param ndarray FixedImage: Target space we are registering into :param ndarray MovingImage: Source space we are coming from :param float MinOverlap: The minimum amount of overlap by area the registration must have :param float MaxOverlap: The maximum amount of overlap by area the registration must have :param bool FFT_Required: True by default, if False the input images are in FFT space already :param tuple FixedImageShape: Defaults to None, if specified it contains the size of the fixed image before padding. Used to calculate mask for valid overlap values. :param tuple MovingImageShape: Defaults to None, if specified it contains the size of the moving image before padding. Used to calculate mask for valid overlap values. ''' # Find peak requires both the fixed and moving images have equal size assert((FixedImage.shape[0] == MovingImage.shape[0]) and (FixedImage.shape[1] == MovingImage.shape[1])) #nornir_imageregistration.ShowGrayscale([FixedImage, MovingImage]) if FixedImageShape is None: FixedImageShape = FixedImage.shape if MovingImageShape is None: MovingImageShape = MovingImage.shape CorrelationImage = None if FFT_Required: CorrelationImage = ImagePhaseCorrelation(FixedImage, MovingImage) else: CorrelationImage = FFTPhaseCorrelation(FixedImage, MovingImage, delete_input=False) CorrelationImage = fftpack.fftshift(CorrelationImage) # Crop the areas that cannot overlap CorrelationImage -= CorrelationImage.min() CorrelationImage /= CorrelationImage.max() # Timer.Start('Find Peak') OverlapMask = nornir_imageregistration.GetOverlapMask(FixedImageShape, MovingImageShape, CorrelationImage.shape, MinOverlap, MaxOverlap) (peak, weight) = FindPeak(CorrelationImage, OverlapMask) del CorrelationImage record = nornir_imageregistration.AlignmentRecord(peak=peak, weight=weight) return record
def testAlignmentTransformSizeMismatch(self): '''An alignment record where the fixed and warped images are differenct sizes''' record = nornir_imageregistration.AlignmentRecord((0, 0), 100, 0) transform = record.ToTransform([100, 100], [10, 10]) # OK, we should be able to map points TransformCheck(self, transform, [[2.5, 2.5]], [[47.5, 47.5]]) TransformCheck(self, transform, [[7.5, 7.5]], [[52.5, 52.5]]) transform = record.ToTransform([100, 100], [10, 50]) # OK, we should be able to map points TransformCheck(self, transform, [[2.5, 2.5]], [[47.5, 27.5]]) TransformCheck(self, transform, [[7.5, 7.5]], [[52.5, 32.5]])
def TestTranslateReadWriteAlignment(self): WarpedImagePath = os.path.join( self.ImportedDataPath, "0017_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") self.assertTrue(os.path.exists(WarpedImagePath), "Missing test input") FixedImagePath = os.path.join( self.ImportedDataPath, "mini_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") self.assertTrue(os.path.exists(FixedImagePath), "Missing test input") peak = (20, 5) arecord = nornir_imageregistration.AlignmentRecord(peak, weight=100, angle=0) FixedSize = nornir_imageregistration.GetImageSize(FixedImagePath) WarpedSize = nornir_imageregistration.GetImageSize(WarpedImagePath) alignmentTransform = arecord.ToTransform(FixedSize, WarpedSize) TransformCheck(self, alignmentTransform, [[(WarpedSize[0] / 2.0), (WarpedSize[1] / 2.0)]], [[(FixedSize[0] / 2.0) + peak[0], (FixedSize[1] / 2.0) + peak[1]]]) # OK, try to save the stos file and reload it. Make sure the transforms match savedstosObj = arecord.ToStos(FixedImagePath, WarpedImagePath, PixelSpacing=1) self.assertIsNotNone(savedstosObj) stosfilepath = os.path.join(self.VolumeDir, '17-18_brute.stos') savedstosObj.Save(stosfilepath) loadedStosObj = StosFile.Load(stosfilepath) self.assertIsNotNone(loadedStosObj) loadedTransform = nornir_imageregistration.transforms.LoadTransform( loadedStosObj.Transform) self.assertIsNotNone(loadedTransform) self.assertTrue( (alignmentTransform.points == loadedTransform.points).all(), "Transform different after save/load")
def __tile_offset_remote(A_Filename, B_Filename, scaled_overlapping_source_rect_A, scaled_overlapping_source_rect_B, OffsetAdjustment, excess_scalar): ''' :param A_Filename: Path to tile A :param B_Filename: Path to tile B :param scaled_overlapping_source_rect_A: Region of overlap on tile A with tile B :param scaled_overlapping_source_rect_B: Region of overlap on tile B with tile A :param OffsetAdjustment: scaled_offset to account for the (center) position of tile B relative to tile A. If the overlapping rectangles are perfectly aligned the reported offset would be (0,0). OffsetAdjustment would be added to that (0,0) result to ensure Tile B remained in the same position. :param float excess_scalar: How much additional area should we pad the overlapping rectangles with. Return the offset required to align to image files. This function exists to minimize the inter-process communication ''' ShowImages = False A = nornir_imageregistration.LoadImage(A_Filename) B = nornir_imageregistration.LoadImage(B_Filename) # I had to add the .astype call above for DM4 support, but I recall it broke PMG input. Leave this comment here until the tests are passing # A = nornir_imageregistration.LoadImage(A_Filename) #.astype(dtype=np.float16) # B = nornir_imageregistration.LoadImage(B_Filename) #.astype(dtype=np.float16) # I tried a 1.0 overlap. It works better for light microscopy where the reported stage position is more precise # For TEM the stage position can be less reliable and the 1.5 scalar produces better results # For the latest version of the code that uses only the overlapping region 3 is appropriate because it allows the alignment point to be anywhere on the image without ambiguity OverlappingRegionA = __get_overlapping_image( A, scaled_overlapping_source_rect_A, excess_scalar=excess_scalar) OverlappingRegionB = __get_overlapping_image( B, scaled_overlapping_source_rect_B, excess_scalar=excess_scalar) if ShowImages: o_a = __get_overlapping_image(A, scaled_overlapping_source_rect_A, excess_scalar=1.0, cval=0) o_b = __get_overlapping_image(B, scaled_overlapping_source_rect_B, excess_scalar=1.0, cval=0) #nornir_imageregistration.ShowGrayscale([[OverlappingRegionA, OverlappingRegionB],[o_a,o_b]]) OverlappingRegionA = OverlappingRegionA.astype(np.float32) OverlappingRegionB = OverlappingRegionB.astype(np.float32) # It is fairly common to underflow when dividing float16 images, so just warn and move on. # I spent a day debugging why a mosaic was not building correctly to find the underflow # issue, so don't remove it. The underflow error removes one of the ties between a tile # and its neighbors. # Note error levelshould now be set in nornir_imageregistration.__init__ # old_float_err_settings = np.seterr(under='warn') # If the entire region is a solid color, then return an alignment record with no offset and a weight of zero if (OverlappingRegionA.min() == OverlappingRegionA.max()) or \ (OverlappingRegionA.max() == 0) or \ (OverlappingRegionB.min() == OverlappingRegionB.max()) or \ (OverlappingRegionB.max() == 0): return nornir_imageregistration.AlignmentRecord(peak=OffsetAdjustment, weight=0) OverlappingRegionA -= OverlappingRegionA.min() OverlappingRegionA /= OverlappingRegionA.max() OverlappingRegionB -= OverlappingRegionB.min() OverlappingRegionB /= OverlappingRegionB.max() # nornir_imageregistration.ShowGrayscale([OverlappingRegionA, OverlappingRegionB]) nornir_imageregistration.ShowGrayscale([[o_a, o_b],[OverlappingRegionA, OverlappingRegionB]]) record = nornir_imageregistration.FindOffset( OverlappingRegionA, OverlappingRegionB, FFT_Required=True ) #, FixedImageShape=scaled_overlapping_source_rect_A.shape, MovingImageShape=scaled_overlapping_source_rect_B.shape) #overlapping_rect_B_AdjustedToPeak = nornir_imageregistration.Rectangle.translate(scaled_overlapping_source_rect_B, -record.peak) #overlapping_rect_B_AdjustedToPeak = nornir_imageregistration.Rectangle.change_area(overlapping_rect_B_AdjustedToPeak, scaled_overlapping_source_rect_A.Size) #median_diff = __AlignmentScoreRemote(A, B, scaled_overlapping_source_rect_A, overlapping_rect_B_AdjustedToPeak) #nornir_imageregistration.ShowGrayscale([[OverlappingRegionA, OverlappingRegionB], [overlapping_rect_B_AdjustedToPeak, median_diff]]) #diff_weight = 1.0 - median_diff #np.seterr(**old_float_err_settings) #nornir_imageregistration.views.plot_aligned_images(record, o_a, o_b) adjusted_record = nornir_imageregistration.AlignmentRecord( np.array(record.peak) + OffsetAdjustment, record.weight) if ShowImages: nornir_imageregistration.views.plot_aligned_images(record, o_a, o_b) del o_a del o_b del A del B del OverlappingRegionA del OverlappingRegionB return adjusted_record
def _FindTileOffsets(tile_overlaps, excess_scalar, imageScale=None, existing_layout=None): '''Populates the OffsetToTile dictionary for tiles :param list tile_overlaps: List of all tile overlaps or dictionary whose values are tile overlaps :param float imageScale: downsample level if known. None causes it to be calculated. :param float excess_scalar: How much additional area should we pad the overlapping rectangles with. :return: A layout object describing the optimal adjustment for each tile to align with each neighboring tile ''' if imageScale is None: imageScale = 1.0 downsample = 1.0 / imageScale # idx = tileset.CreateSpatialMap([t.FixedBoundingBox for t in tiles], tiles) CalculationCount = 0 # _CalculateImageFFTs(tiles) #pool = nornir_pools.GetGlobalSerialPool() pool = nornir_pools.GetGlobalMultithreadingPool() tasks = list() layout = existing_layout if layout is None: layout = nornir_imageregistration.layout.Layout() list_tile_overlaps = tile_overlaps if isinstance(tile_overlaps, dict): list_tile_overlaps = list(tile_overlaps.values()) assert (isinstance(list_tile_overlaps, list)) for t in list_tile_overlaps: if not layout.Contains(t.A.ID): layout.CreateNode(t.A.ID, t.A.FixedBoundingBox.Center) if not layout.Contains(t.B.ID): layout.CreateNode(t.B.ID, t.B.FixedBoundingBox.Center) print("Starting tile alignment") for tile_overlap in list_tile_overlaps: t = pool.add_task( "Align %d -> %d" % (tile_overlap.ID[0], tile_overlap.ID[1]), __tile_offset_remote, tile_overlap.A.ImagePath, tile_overlap.B.ImagePath, tile_overlap.scaled_overlapping_source_rect_A, tile_overlap.scaled_overlapping_source_rect_B, tile_overlap.scaled_offset, excess_scalar) t.tile_overlap = tile_overlap tasks.append(t) CalculationCount += 1 # print("Start alignment %d -> %d" % (A.ID, B.ID)) for t in tasks: try: offset = t.wait_return() except FloatingPointError as e: # Very rarely the overlapping region is entirely one color and this error is thrown. print( "FloatingPointError: %d -> %d = %s -> Using stage coordinates." % (t.tile_overlap.A.ID, t.tile_overlap.B.ID, str(e))) # Create an alignment record using only stage position and a weight of zero offset = nornir_imageregistration.AlignmentRecord( peak=t.tile_overlap.scaled_offset, weight=0) tile_overlap = t.tile_overlap # Figure out what offset we found vs. what offset we expected PredictedOffset = tile_overlap.B.FixedBoundingBox.Center - tile_overlap.A.FixedBoundingBox.Center ActualOffset = offset.peak * downsample diff = ActualOffset - PredictedOffset distance = np.sqrt(np.sum(diff**2)) f_score = min(tile_overlap.feature_scores) final_weight = offset.weight * f_score print( "%d -> %d = feature score: %.04g align score: %.04g Final Weight: %.04g Dist: %.04g" % (tile_overlap.A.ID, tile_overlap.B.ID, f_score, offset.weight, final_weight, distance)) layout.SetOffset(tile_overlap.A.ID, tile_overlap.B.ID, ActualOffset, final_weight) pool.wait_completion() print(("Total offset calculations: " + str(CalculationCount))) return layout
def __RefineTileAlignmentRemote(A, B, scaled_overlapping_source_rect_A, scaled_overlapping_source_rect_B, OffsetAdjustment, imageScale, subregion_shape=None): if subregion_shape is None: subregion_shape = np.array([128, 128]) downsample = 1.0 / imageScale grid_dim = nornir_imageregistration.TileGridShape( scaled_overlapping_source_rect_A.Size, subregion_shape) # scaled_overlapping_source_rect_A = nornir_imageregistration.Rectangle.change_area(scaled_overlapping_source_rect_A, grid_dim * subregion_shape) # scaled_overlapping_source_rect_B = nornir_imageregistration.Rectangle.change_area(scaled_overlapping_source_rect_B, grid_dim * subregion_shape) overlapping_rect = nornir_imageregistration.Rectangle.overlap_rect( A.FixedBoundingBox, B.FixedBoundingBox) overlapping_rect = nornir_imageregistration.Rectangle.change_area( overlapping_rect, grid_dim * subregion_shape * downsample) ATransformedImageData = nornir_imageregistration.assemble_tiles.TransformTile( transform=A.Transform, imagefullpath=A.ImagePath, distanceImage=None, target_space_scale=imageScale, TargetRegion=overlapping_rect.ToArray()) BTransformedImageData = nornir_imageregistration.assemble_tiles.TransformTile( transform=B.Transform, imagefullpath=B.ImagePath, distanceImage=None, target_space_scale=imageScale, TargetRegion=overlapping_rect.ToArray()) # I tried a 1.0 overlap. It works better for light microscopy where the reported stage position is more precise # For TEM the stage position can be less reliable and the 1.5 scalar produces better results # OverlappingRegionA = __get_overlapping_image(A, scaled_overlapping_source_rect_A,excess_scalar=1.0) # OverlappingRegionB = __get_overlapping_image(B, scaled_overlapping_source_rect_B,excess_scalar=1.0) A_image = nornir_imageregistration.RandomNoiseMask( ATransformedImageData.image, ATransformedImageData.centerDistanceImage < np.finfo( ATransformedImageData.centerDistanceImage.dtype).max, Copy=True) B_image = nornir_imageregistration.RandomNoiseMask( BTransformedImageData.image, BTransformedImageData.centerDistanceImage < np.finfo( BTransformedImageData.centerDistanceImage.dtype).max, Copy=True) # OK, create tiles from the overlapping regions # A_image = nornir_imageregistration.ReplaceImageExtramaWithNoise(ATransformedImageData.image) # B_image = nornir_imageregistration.ReplaceImageExtramaWithNoise(BTransformedImageData.image) # nornir_imageregistration.ShowGrayscale([A_image,B_image]) A_tiles = nornir_imageregistration.ImageToTiles(A_image, subregion_shape, cval='random') B_tiles = nornir_imageregistration.ImageToTiles(B_image, subregion_shape, cval='random') # grid_dim = nornir_imageregistration.TileGridShape(ATransformedImageData.image.shape, subregion_shape) refine_dtype = np.dtype([('SourceY', 'f4'), ('SourceX', 'f4'), ('TargetY', 'f4'), ('TargetX', 'f4'), ('Weight', 'f4'), ('Angle', 'f4')]) point_pairs = np.empty(grid_dim, dtype=refine_dtype) net_displacement = np.empty( (grid_dim.prod(), 3), dtype=np.float32 ) # Amount the refine points are moved from the defaultf cell_center_offset = subregion_shape / 2.0 for iRow in range(0, grid_dim[0]): for iCol in range(0, grid_dim[1]): subregion_offset = ( np.array([iRow, iCol]) * subregion_shape ) + cell_center_offset # Position subregion coordinate space source_tile_offset = subregion_offset + scaled_overlapping_source_rect_A.BottomLeft # Position tile image coordinate space global_offset = OffsetAdjustment + subregion_offset # Position subregion in tile's mosaic space if not (iRow, iCol) in A_tiles or not (iRow, iCol) in B_tiles: net_displacement[(iRow * grid_dim[1]) + iCol, :] = np.array( [0, 0, 0]) point_pairs[iRow, iCol] = np.array( (source_tile_offset[0], source_tile_offset[1], 0 + global_offset[0], 0 + global_offset[1], 0, 0), dtype=refine_dtype) continue try: record = nornir_imageregistration.FindOffset(A_tiles[iRow, iCol], B_tiles[iRow, iCol], FFT_Required=True) except: net_displacement[(iRow * grid_dim[1]) + iCol, :] = np.array( [0, 0, 0]) point_pairs[iRow, iCol] = np.array( (source_tile_offset[0], source_tile_offset[1], 0 + global_offset[0], 0 + global_offset[1], 0, 0), dtype=refine_dtype) continue adjusted_record = nornir_imageregistration.AlignmentRecord( np.array(record.peak) * downsample, record.weight) # print(str(record)) if np.any(np.isnan(record.peak)): net_displacement[(iRow * grid_dim[1]) + iCol, :] = np.array( [0, 0, 0]) point_pairs[iRow, iCol] = np.array( (source_tile_offset[0], source_tile_offset[1], 0 + global_offset[0], 0 + global_offset[1], 0, record.angle), dtype=refine_dtype) else: net_displacement[(iRow * grid_dim[1]) + iCol, :] = np.array([ adjusted_record.peak[0], adjusted_record.peak[1], record.weight ]) point_pairs[iRow, iCol] = np.array( (source_tile_offset[0], source_tile_offset[1], adjusted_record.peak[0] + global_offset[0], adjusted_record.peak[1] + global_offset[1], record.weight, record.angle), dtype=refine_dtype) # TODO: return two sets of point pairs, one for each tile, with the offsets divided by two so both tiles are warping to improve the fit equally? # TODO: Try a number of angles # TODO: The original refine-grid assembled the image before trying to improve the fit. Is this important or an artifact of the implementation? weighted_net_offset = np.copy(net_displacement) weighted_net_offset[:, 2] /= np.sum(net_displacement[:, 2]) weighted_net_offset[:, 0] *= weighted_net_offset[:, 2] weighted_net_offset[:, 1] *= weighted_net_offset[:, 2] net_offset = np.sum(weighted_net_offset[:, 0:2], axis=0) meaningful_weights = weighted_net_offset[weighted_net_offset[:, 2] > 0, 2] weight = 0 if meaningful_weights.shape[0] > 0: weight = np.median(meaningful_weights) net_offset = np.hstack((np.around(net_offset, 3), weight)) # net_offset = np.median(net_displacement,axis=0) return (point_pairs, net_offset)
def SliceToSliceBruteForce(FixedImageInput, WarpedImageInput, FixedImageMaskPath=None, WarpedImageMaskPath=None, LargestDimension=None, AngleSearchRange=None, MinOverlap=0.75, SingleThread=False, Cluster=False, TestFlip=True): '''Given two images this function returns the rotation angle which best aligns them Largest dimension determines how large the images used for alignment should be''' logger = logging.getLogger(__name__ + '.SliceToSliceBruteForce') imFixed = None if isinstance(FixedImageInput, str): imFixed = nornir_imageregistration.LoadImage(FixedImageInput, FixedImageMaskPath, dtype=np.float32) else: imFixed = FixedImageInput imWarped = None if isinstance(WarpedImageInput, str): imWarped = nornir_imageregistration.LoadImage(WarpedImageInput, WarpedImageMaskPath, dtype=np.float32) else: imWarped = WarpedImageInput scalar = 1.0 if not LargestDimension is None: scalar = nornir_imageregistration.ScalarForMaxDimension( LargestDimension, [imFixed.shape, imWarped.shape]) if scalar < 1.0: imFixed = nornir_imageregistration.ReduceImage(imFixed, scalar) imWarped = nornir_imageregistration.ReduceImage(imWarped, scalar) # Replace extrema with noise imFixed = nornir_imageregistration.ReplaceImageExtramaWithNoise( imFixed, ImageMedian=0.5, ImageStdDev=0.25) imWarped = nornir_imageregistration.ReplaceImageExtramaWithNoise( imWarped, ImageMedian=0.5, ImageStdDev=0.25) UserDefinedAngleSearchRange = not AngleSearchRange is None if not UserDefinedAngleSearchRange: AngleSearchRange = list(range(-180, 180, 2)) BestMatch = FindBestAngle(imFixed, imWarped, AngleSearchRange, MinOverlap=MinOverlap, SingleThread=SingleThread, Cluster=Cluster) IsFlipped = False if TestFlip: imWarpedFlipped = np.copy(imWarped) imWarpedFlipped = np.flipud(imWarpedFlipped) BestMatchFlipped = FindBestAngle(imFixed, imWarpedFlipped, AngleSearchRange, MinOverlap=MinOverlap, SingleThread=SingleThread, Cluster=Cluster) BestMatchFlipped.flippedud = True # Determine if the best match is flipped or not IsFlipped = BestMatchFlipped.weight > BestMatch.weight if IsFlipped: imWarped = imWarpedFlipped BestMatch = BestMatchFlipped if not UserDefinedAngleSearchRange: BestRefinedMatch = FindBestAngle(imFixed, imWarped, [(x * 0.1) + BestMatch.angle - 1 for x in range(0, 20)], MinOverlap=MinOverlap, SingleThread=SingleThread) BestRefinedMatch.flippedud = IsFlipped else: BestRefinedMatch = BestMatch if scalar > 1.0: AdjustedPeak = (BestRefinedMatch.peak[0] * scalar, BestRefinedMatch.peak[1] * scalar) BestRefinedMatch = nornir_imageregistration.AlignmentRecord( AdjustedPeak, BestRefinedMatch.weight, BestRefinedMatch.angle, IsFlipped) # BestRefinedMatch.CorrectPeakForOriginalImageSize(imFixed.shape, imWarped.shape) return BestRefinedMatch
def ScoreOneAngle(imFixed, imWarped, FixedImageShape, WarpedImageShape, angle, fixedStats=None, warpedStats=None, FixedImagePrePadded=True, MinOverlap=0.75): '''Returns an alignment score for a fixed image and an image rotated at a specified angle''' imFixed = nornir_imageregistration.ImageParamToImageArray(imFixed, dtype=np.float32) imWarped = nornir_imageregistration.ImageParamToImageArray( imWarped, dtype=np.float32) # gc.set_debug(gc.DEBUG_LEAK) if fixedStats is None: fixedStats = nornir_imageregistration.ImageStats.CalcStats(imFixed) if warpedStats is None: warpedStats = nornir_imageregistration.ImageStats.CalcStats(imWarped) OKToDelimWarped = False if angle != 0: imWarped = interpolation.rotate(imWarped, axes=(1, 0), angle=angle, cval=np.nan) imWarpedEmptyIndicies = np.isnan(imWarped) imWarped[imWarpedEmptyIndicies] = warpedStats.GenerateNoise( np.sum(imWarpedEmptyIndicies)) OKToDelimWarped = True RotatedWarped = nornir_imageregistration.PadImageForPhaseCorrelation( imWarped, ImageMedian=warpedStats.median, ImageStdDev=warpedStats.std, MinOverlap=MinOverlap) assert (RotatedWarped.shape[0] > 0) assert (RotatedWarped.shape[1] > 0) if not FixedImagePrePadded: PaddedFixed = nornir_imageregistration.PadImageForPhaseCorrelation( imFixed, ImageMedian=fixedStats.median, ImageStdDev=fixedStats.std, MinOverlap=MinOverlap) else: PaddedFixed = imFixed # print str(PaddedFixed.shape) + ' ' + str(RotatedPaddedWarped.shape) TargetHeight = max([PaddedFixed.shape[0], RotatedWarped.shape[0]]) TargetWidth = max([PaddedFixed.shape[1], RotatedWarped.shape[1]]) PaddedFixed = nornir_imageregistration.PadImageForPhaseCorrelation( imFixed, NewWidth=TargetWidth, NewHeight=TargetHeight, ImageMedian=fixedStats.median, ImageStdDev=fixedStats.std, MinOverlap=1.0) RotatedPaddedWarped = nornir_imageregistration.PadImageForPhaseCorrelation( RotatedWarped, NewWidth=TargetWidth, NewHeight=TargetHeight, ImageMedian=warpedStats.median, ImageStdDev=warpedStats.std, MinOverlap=1.0) #if OKToDelimWarped: del imWarped del imFixed del RotatedWarped assert (PaddedFixed.shape == RotatedPaddedWarped.shape) CorrelationImage = nornir_imageregistration.ImagePhaseCorrelation( PaddedFixed, RotatedPaddedWarped) del PaddedFixed del RotatedPaddedWarped CorrelationImage = fftshift(CorrelationImage) CorrelationImage -= CorrelationImage.min() CorrelationImage /= CorrelationImage.max() # Timer.Start('Find Peak') OverlapMask = nornir_imageregistration.overlapmasking.GetOverlapMask( FixedImageShape, WarpedImageShape, CorrelationImage.shape, MinOverlap, MaxOverlap=1.0) (peak, weight) = nornir_imageregistration.FindPeak(CorrelationImage, OverlapMask) del OverlapMask del CorrelationImage record = nornir_imageregistration.AlignmentRecord(peak, weight, angle) return record