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]])
示例#7
0
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