Exemple #1
0
    def LoadMosaic(self, mosaicFullPath, tiles_dir=None):
        '''Return a list of image transform views for the mosaic file'''

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFullPath)
        if len(mosaic.ImageToTransform) == 0:
            return None

        mosaic.TranslateToZeroOrigin()

        tiles_dir = MosaicState.GetMosaicTilePath(
            list(mosaic.ImageToTransform.keys())[0], mosaicFullPath, tiles_dir)
        if tiles_dir is None:
            return None

        tilesPathList = mosaic.CreateTilesPathList(tiles_dir)
        transform_scale = nornir_imageregistration.tileset.MostCommonScalar(
            list(mosaic.ImageToTransform.values()), tilesPathList)

        ImageTransformViewList = []

        z_step = 1.0 / float(len(mosaic.ImageToTransform))
        z = z_step - (z_step / 2.0)
        output_len = 0

        pools = nornir_pools.GetGlobalThreadPool()

        tasks = []
        for image_filename, transform in list(mosaic.ImageToTransform.items()):
            tile_full_path = os.path.join(tiles_dir, image_filename)

            task = pools.add_task(str(z), self.AllocateMosaicTile, transform,
                                  tile_full_path, transform_scale)
            task.z = z
            tasks.append(task)

            # image_transform_view = self.AllocateMosaicTile(transform, tile_full_path, transform_scale)
            # image_transform_view.z = z
            z += z_step
            # ImageTransformViewList.append(image_transform_view)

        wx.Yield()

        for t in tasks:
            image_transform_view = t.wait_return()
            image_transform_view.z = t.z
            ImageTransformViewList.append(image_transform_view)

            output = '%g' % (z * 100.0)

            sys.stdout.write('\b' * output_len)
            sys.stdout.write(output)

            output_len = len(output)

        self.ImageTransformViewList = ImageTransformViewList
        return ImageTransformViewList
    def test_MosaicBoundsEachMosaicType(self):

        for m in self.GetMosaicFiles():

            mosaic = Mosaic.LoadFromMosaicFile(m)

            self.assertIsNotNone(mosaic.MappedBoundingBox, "No bounding box returned for mosiac")

            self.Logger.info(m + " mapped bounding box: " + str(mosaic.MappedBoundingBox))

            self.assertIsNotNone(mosaic.FixedBoundingBox, "No bounding box returned for mosiac")

            self.Logger.info(m + " fixed bounding box: " + str(mosaic.FixedBoundingBox))
Exemple #3
0
    def CreateAssembleOptimizedTile(self, mosaicFilePath, TilesDir):
        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaicBaseName = os.path.basename(mosaicFilePath)

        mosaicDir = os.path.dirname(mosaicFilePath)
        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        imageKey = list(mosaic.ImageToTransform.keys())[0]
        transform = mosaic.ImageToTransform[imageKey]

        (MinY, MinX, MaxY, MaxX) = transform.FixedBoundingBox

        expectedScale = 1.0 / self.DownsampleFromTilePath(TilesDir)

        result = at.TransformTile(transform,
                                  os.path.join(TilesDir, imageKey),
                                  distanceImage=None,
                                  target_space_scale=None,
                                  TargetRegion=(MinY, MinX, MaxY, MinX + 256))
        self.assertEqual(
            result.image.shape,
            (np.ceil(transform.FixedBoundingBox.Height * expectedScale),
             np.ceil(256 * expectedScale)))

        result = at.TransformTile(transform,
                                  os.path.join(TilesDir, imageKey),
                                  distanceImage=None,
                                  target_space_scale=None,
                                  TargetRegion=(MinY, MinX, MinY + 256, MaxX))
        self.assertEqual(
            result.image.shape,
            (np.ceil(256 * expectedScale),
             np.ceil(transform.FixedBoundingBox.Width * expectedScale)))

        result = at.TransformTile(transform,
                                  os.path.join(TilesDir, imageKey),
                                  distanceImage=None,
                                  target_space_scale=None,
                                  TargetRegion=(MinY + 2048, MinX + 2048,
                                                MinY + 2048 + 512,
                                                MinX + 2048 + 512))
        self.assertEqual(
            result.image.shape,
            (np.ceil(512 * expectedScale), np.ceil(512 * expectedScale)))
Exemple #4
0
    def AssembleMosaic(self,
                       mosaicFilePath,
                       tilesDir,
                       outputMosaicPath=None,
                       parallel=False,
                       downsamplePath=None):

        SaveFiles = not outputMosaicPath is None

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaicBaseName = os.path.basename(mosaicFilePath)
        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        mosaic.TranslateToZeroOrigin()

        assembleScale = tiles.MostCommonScalar(
            list(mosaic.ImageToTransform.values()),
            mosaic.TileFullPaths(tilesDir))

        expectedScale = 1.0 / self.DownsampleFromTilePath(tilesDir)

        self.assertEqual(
            assembleScale, expectedScale,
            "Scale for assemble does not match the expected scale")

        timer = TaskTimer()

        timer.Start("AssembleImage " + tilesDir)

        (mosaicImage, mask) = mosaic.AssembleImage(tilesDir,
                                                   usecluster=parallel)

        timer.End("AssembleImage " + tilesDir, True)

        self.assertEqual(
            mosaicImage.shape[0],
            np.ceil(mosaic.FixedBoundingBoxHeight * expectedScale),
            "Output mosaic height does not match .mosaic height %g vs %g" %
            (mosaicImage.shape[0],
             mosaic.FixedBoundingBoxHeight * expectedScale))
        self.assertEqual(
            mosaicImage.shape[1],
            np.ceil(mosaic.FixedBoundingBoxWidth * expectedScale),
            "Output mosaic width does not match .mosaic height %g vs %g" %
            (mosaicImage.shape[1],
             mosaic.FixedBoundingBoxWidth * expectedScale))
        # img = im.fromarray(mosaicImage, mode="I")
        # img.save(outputImagePath, mosaicImage, bits=8)

        if SaveFiles:
            OutputDir = os.path.join(self.TestOutputPath, outputMosaicPath)

            if not os.path.exists(OutputDir):
                os.makedirs(OutputDir)

            outputImagePath = os.path.join(OutputDir, mosaicBaseName + '.png')
            outputImageMaskPath = os.path.join(OutputDir,
                                               mosaicBaseName + '_mask.png')

            core.SaveImage(outputImagePath, mosaicImage)
            core.SaveImage(outputImageMaskPath, mask)
            self.assertTrue(os.path.exists(outputImagePath),
                            "OutputImage not found")

            outputMask = core.LoadImage(outputImageMaskPath)
            self.assertTrue(
                outputMask[int(outputMask.shape[0] / 2.0),
                           int(outputMask.shape[1] / 2.0)] > 0,
                "Center of assembled image mask should be non-zero")

            del mosaicImage
            del mask
        else:
            return (mosaicImage, mask)
Exemple #5
0
    def CreateAssembleOptimizedTileTwo(self,
                                       mosaicFilePath,
                                       TilesDir,
                                       tile_dims=None,
                                       numColumnsPerPass=None):

        if tile_dims is None:
            tile_dims = (512, 512)

        tile_dims = np.asarray(tile_dims, dtype=np.int64)

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaic.TranslateToZeroOrigin()
        mosaicBaseName = os.path.basename(mosaicFilePath)

        mosaicDir = os.path.dirname(mosaicFilePath)
        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        expectedScale = 1.0 / self.DownsampleFromTilePath(TilesDir)

        scaled_fixed_bounding_box_shape = np.ceil(
            mosaic.FixedBoundingBox.shape / (1 / expectedScale)).astype(
                np.int64)

        expected_grid_dims = nornir_imageregistration.TileGridShape(
            scaled_fixed_bounding_box_shape, tile_size=tile_dims)

        mosaic_expected_grid_dims = mosaic.CalculateGridDimensions(
            tile_dims, expectedScale)
        self.assertTrue(
            np.array_equal(expected_grid_dims, mosaic_expected_grid_dims),
            "Mosaic object grid dimensions should match manually calculated grid dimensions"
        )

        max_temp_image_dims = expected_grid_dims * tile_dims
        if numColumnsPerPass is not None:
            max_temp_image_dims[1] = tile_dims[1] * numColumnsPerPass

        max_temp_image_area = np.prod(max_temp_image_dims)

        tiles = [[None for iCol in range(expected_grid_dims[1])]
                 for iRow in range(expected_grid_dims[0])
                 ]  #[[None] * expected_grid_dims[1]] * expected_grid_dims[0]
        tile_returned = np.zeros(expected_grid_dims, dtype=np.bool)
        #out = list(mosaic.GenerateOptimizedTiles(tilesPath=TilesDir, tile_dims=tile_dims, usecluster=False, target_space_scale=expectedScale))
        for t in mosaic.GenerateOptimizedTiles(
                tilesPath=TilesDir,
                tile_dims=tile_dims,
                usecluster=False,
                max_temp_image_area=max_temp_image_area,
                target_space_scale=expectedScale):
            (iRow, iCol, tile_image) = t
            assert (iCol < expected_grid_dims[1])
            assert (iRow < expected_grid_dims[0])

            #            nornir_imageregistration.ShowGrayscale(tile_image)
            self.assertFalse(
                tile_returned[iRow, iCol],
                "Duplicate Row,Column value returned by enumerator")
            tile_returned[iRow, iCol] = True

            print("{0},{1} Tile completed".format(iRow, iCol))
            # origin = np.asarray((iCol, iRow), dtype=np.int64) * tile_dims
            # region = nornir_imageregistration.Rectangle.CreateFromPointAndArea(origin, tile_dims*(1/expectedScale))

            #             (controlImage, mask) = mosaic.AssembleImage(tilesPath=TilesDir, FixedRegion=region,
            #                                                 usecluster=True, target_space_scale=expectedScale)
            #             self.assertTrue(np.array_equal(tile_image, controlImage))

            tiles[iRow][iCol] = tile_image

        self.assertTrue(np.all(tile_returned.flat))
        title = ""
        if numColumnsPerPass is not None:
            title = "Generated {0} columns in a pass.\n".format(
                numColumnsPerPass)

        title = title + "Tiles should not overlap and look reasonable"
        self.assertTrue(
            nornir_imageregistration.ShowGrayscale(tiles,
                                                   title=title,
                                                   PassFail=True))
Exemple #6
0
    def CompareMosaicAsssembleAndTransformTile(self, mosaicFilePath, tilesDir):
        '''
        1) Assemble the entire mosaic
        2) Assemble subregion of the mosaic
        3) Assemble subregion directly using _TransformTile
        4) Check the output of all match
        '''

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        self.assertIsNotNone(mosaic, "Mosaic not loaded")

        mosaic.TranslateToZeroOrigin()

        (imageKey,
         transform) = sorted(list(mosaic.ImageToTransform.items()))[0]

        (MinY, MinX, MaxY, MaxZ) = transform.FixedBoundingBox
        print("Scaled fixed region %s" % str(transform.FixedBoundingBox))

        FixedRegion = np.array(
            [MinY + 512, MinX + 1024, MinY + 1024, MinX + 2048])
        ScaledFixedRegion = FixedRegion / self.DownsampleFromTilePath(tilesDir)

        (tileImage, tileMask) = mosaic.AssembleImage(tilesDir,
                                                     usecluster=False,
                                                     FixedRegion=FixedRegion)
        # self.assertEqual(tileImage.shape, (ScaledFixedRegion[3], ScaledFixedRegion[2]))

        (clustertileImage,
         clustertileMask) = mosaic.AssembleImage(tilesDir,
                                                 usecluster=True,
                                                 FixedRegion=FixedRegion)
        # self.assertEqual(tileImage.shape, (ScaledFixedRegion[3], ScaledFixedRegion[2]))

        self.assertTrue(
            np.sum(np.abs(clustertileImage - tileImage).flat) < 0.65,
            "Tiles generated with cluster should be identical to single threaded implementation"
        )
        self.assertTrue(
            np.all(clustertileMask == tileMask),
            "Tiles generated with cluster should be identical to single threaded implementation"
        )

        result = at.TransformTile(transform,
                                  os.path.join(tilesDir, imageKey),
                                  distanceImage=None,
                                  target_space_scale=None,
                                  TargetRegion=FixedRegion)
        self.assertEqual(result.image.shape,
                         (ScaledFixedRegion[2] - ScaledFixedRegion[0],
                          ScaledFixedRegion[3] - ScaledFixedRegion[1]))

        # core.ShowGrayscale([tileImage, result.image])
        (wholeimage, wholemask) = self.AssembleMosaic(mosaicFilePath,
                                                      tilesDir,
                                                      outputMosaicPath=None,
                                                      parallel=False)
        self.assertIsNotNone(wholeimage, "Assemble did not produce an image")
        self.assertIsNotNone(wholemask, "Assemble did not produce a mask")

        croppedWholeImage = core.CropImage(
            wholeimage, int(ScaledFixedRegion[1]), int(ScaledFixedRegion[0]),
            int(ScaledFixedRegion[3] - ScaledFixedRegion[1]),
            int(ScaledFixedRegion[2] - ScaledFixedRegion[0]))

        self.assertTrue(
            nornir_imageregistration.ShowGrayscale(
                [result.image, tileImage, croppedWholeImage, wholeimage],
                title="image: %s\n%s" %
                (imageKey, str(transform.FixedBoundingBox)),
                PassFail=True))
    def ArrangeMosaic(self,
                      mosaicFilePath,
                      TilePyramidDir=None,
                      downsample=None,
                      openwindow=False,
                      max_relax_iterations=None,
                      max_relax_tension_cutoff=None,
                      inter_tile_distance_scale=None,
                      min_translate_iterations=None):

        if downsample is None:
            downsample = 1

        if min_translate_iterations is None:
            min_translate_iterations = 5

        if inter_tile_distance_scale is None:
            inter_tile_distance_scale = 0.5

        downsamplePath = '%03d' % downsample

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaicBaseName = os.path.basename(mosaicFilePath)

        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        TilesDir = None
        if TilePyramidDir is None:
            TilesDir = os.path.join(self.ImportedDataPath, self.Dataset,
                                    'Leveled', 'TilePyramid', downsamplePath)
        else:
            TilesDir = os.path.join(TilePyramidDir, downsamplePath)

        mosaic.TranslateToZeroOrigin()

        original_score = mosaic.QualityScore(TilesDir)

        # self.__RemoveExtraImages(mosaic)

        assembleScale = tileset.MostCommonScalar(
            mosaic.ImageToTransform.values(), mosaic.TileFullPaths(TilesDir))

        expectedScale = 1.0 / float(downsamplePath)

        self.assertEqual(
            assembleScale, expectedScale,
            "Scale for assemble does not match the expected scale")

        timer = TaskTimer()

        timer.Start("ArrangeTiles " + TilesDir)

        translated_mosaic = mosaic.ArrangeTilesWithTranslate(
            TilesDir,
            excess_scalar=1.5,
            min_translate_iterations=min_translate_iterations,
            max_relax_iterations=max_relax_iterations,
            max_relax_tension_cutoff=max_relax_tension_cutoff,
            inter_tile_distance_scale=inter_tile_distance_scale,
            min_overlap=0.05)

        timer.End("ArrangeTiles " + TilesDir, True)

        translated_score = translated_mosaic.QualityScore(TilesDir)

        print("Original Quality Score: %g" % (original_score))
        print("Translate Quality Score: %g" % (translated_score))

        OutputDir = os.path.join(self.TestOutputPath,
                                 mosaicBaseName + '.mosaic')
        OutputMosaicDir = os.path.join(self.TestOutputPath,
                                       mosaicBaseName + '.png')

        if openwindow:
            self._ShowMosaic(translated_mosaic, OutputMosaicDir)
    def ArrangeMosaicDirect(self,
                            mosaicFilePath,
                            TilePyramidDir=None,
                            downsample=None,
                            openwindow=False,
                            max_relax_iterations=None,
                            max_relax_tension_cutoff=None,
                            inter_tile_distance_scale=None,
                            feature_score_threshold=0.5):

        if downsample is None:
            downsample = 1

        #pool = nornir_pools.GetGlobalThreadPool()

        downsamplePath = '%03d' % downsample

        minWeight = 0
        maxWeight = 1.0

        mosaicBaseName = os.path.basename(mosaicFilePath)
        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        scale = 1.0 / float(downsample)

        TilesDir = None
        if TilePyramidDir is None:
            TilesDir = os.path.join(self.ImportedDataPath, self.Dataset,
                                    'Leveled', 'TilePyramid', downsamplePath)
        else:
            TilesDir = os.path.join(TilePyramidDir, downsamplePath)

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaic.EnsureTransformsHaveMappedBoundingBoxes(image_scale=scale,
                                                       image_path=TilesDir)
        mosaic.TranslateToZeroOrigin()

        #        mosaic.TranslateToZeroOrigin()

        # self.__RemoveExtraImages(mosaic)

        # assembleScale = tiles.MostCommonScalar(mosaic.ImageToTransform.values(), mosaic.TileFullPaths(TilesDir))

        # expectedScale = 1.0 / float(downsamplePath)

        #  self.assertEqual(assembleScale, expectedScale, "Scale for assemble does not match the expected scale")

        timer = TaskTimer()
        timer.Start("ArrangeTiles " + TilesDir)

        tilesPathList = sorted(mosaic.CreateTilesPathList(TilesDir))
        transforms = list(mosaic._TransformsSortedByKey())

        imageScale = self.ReadOrCreateVariable(self.id() +
                                               "_imageScale_%03d" % downsample,
                                               tileset.MostCommonScalar,
                                               transforms=transforms,
                                               imagepaths=tilesPathList)
        first_pass_excess_scalar = 3  #This needs to be 3 to ensure we can detect any offset, otherwise quadrant of the peak is ambiguous
        excess_scalar = first_pass_excess_scalar

        initial_tiles = nornir_imageregistration.tile.CreateTiles(
            transforms=transforms, imagepaths=tilesPathList)

        min_overlap = 0.070

        if inter_tile_distance_scale is None:
            inter_tile_distance_scale = 1.0

        #tile_overlap_feature_scores = arrange.ScoreTileOverlaps(tiles=initial_tiles, imageScale=imageScale)
        last_pass_overlaps = None
        translated_layout = None

        stage_reported_overlaps = None

        for iPass in range(0, 5):
            (distinct_overlaps, new_overlaps, updated_overlaps,
             removed_overlap_IDs,
             non_overlapping_IDs) = arrange.GenerateTileOverlaps(
                 tiles=initial_tiles,
                 existing_overlaps=last_pass_overlaps,
                 offset_epsilon=1.0,
                 image_scale=imageScale,
                 min_overlap=min_overlap,
                 inter_tile_distance_scale=inter_tile_distance_scale)

            if stage_reported_overlaps is None:
                stage_reported_overlaps = {
                    to.ID: to.offset
                    for to in new_overlaps
                }

            #After the first pass trust the mosaic layout
            inter_tile_distance_scale = 1.0

            new_or_updated_overlaps = list(new_overlaps)
            new_or_updated_overlaps.extend(updated_overlaps)

            arrange.ScoreTileOverlaps(distinct_overlaps)

            overlap_colors = ['green'] * len(new_overlaps)
            overlap_colors.extend(['blue'] * len(updated_overlaps))

            if translated_layout is not None:
                for ID in removed_overlap_IDs:
                    translated_layout.RemoveOverlap(ID)
                for ID in non_overlapping_IDs:
                    self.assertTrue(
                        translated_layout.nodes[ID].ConnectedIDs.shape[0] == 0,
                        "Non-overlapping node should not have overlaps")
#                    translated_layout.RemoveNode(ID)

#         tile_overlap_feature_scores = self.ReadOrCreateVariable(self.id() + "tile_overlap_feature_scores_%03d_%03g" % (downsample, first_pass_excess_scalar),
#                                                       arrange.GenerateScoredTileOverlaps,
#                                                       tiles=initial_tiles,
#                                                       imageScale=imageScale)

            Scores = []
            for overlap in distinct_overlaps:
                Scores.extend(overlap.feature_scores)

            Scores.sort()
            h = nornir_shared.histogram.Histogram.Init(
                minVal=np.min(Scores),
                maxVal=np.max(Scores),
                numBins=int(np.sqrt(len(Scores)) * 10))
            h.Add(Scores)

            #nornir_shared.plot.Histogram(h)
            nornir_shared.plot.Histogram(
                h,
                ImageFilename=os.path.join(
                    self.TestOutputPath,
                    "{0:d}pass_FeatureScoreHistogram.png".format(iPass)),
                Title="Tile overlap feature scores")
            #            nornir_shared.plot.Histogram(h, ImageFilename=os.path.join(mosaicBaseName + "_{0d}pass_FeatureScoreHistogram.png", Title="Tile overlap feature scores")

            #pool.add_task('Plot prune histogram', nornir_shared.plot.Histogram,h, ImageFilename=mosaicBaseName + "_PrunePlotHistogram.png", Title="Tile overlap feature scores")
            #pool.add_task('Plot prune histogram', nornir_shared.plot.Histogram,h, ImageFilename=mosaicBaseName + "_PrunePlotHistogram.png", Title="Tile overlap feature scores")

            #self.assertAlmostEqual(imageScale, 1.0 / downsample, "Calculated image scale should match downsample value passed to test")

            #Create a list of offsets requiring updates
            filtered_overlaps_needing_offsets = []
            for overlap in new_or_updated_overlaps:
                if overlap.feature_scores[
                        0] >= feature_score_threshold and overlap.feature_scores[
                            1] >= feature_score_threshold:
                    filtered_overlaps_needing_offsets.append(overlap)
                else:
                    if translated_layout is not None:
                        translated_layout.RemoveOverlap(overlap)

            #Create a list of every offset that should be found in the layout for debugging
            filtered_distinct_offsets = []
            for overlap in distinct_overlaps:
                if overlap.feature_scores[
                        0] >= feature_score_threshold and overlap.feature_scores[
                            1] >= feature_score_threshold:
                    filtered_distinct_offsets.append(overlap)

            #Find the overlaps that are locked
            new_or_updated_dict = {o.ID for o in new_or_updated_overlaps}
            locked_overlaps = []
            for d in distinct_overlaps:
                if not d.ID in new_or_updated_dict:
                    locked_overlaps.append(d)
                    if translated_layout.ContainsOffset(d.ID):
                        overlap_colors.extend(['gold'])
                    else:
                        overlap_colors.extend(['red'])

            self.SaveVariable(
                (new_overlaps, updated_overlaps, locked_overlaps,
                 overlap_colors),
                os.path.join(self.TestOutputPath,
                             "pass_{0}_tile_overlaps.pickle".format(iPass)))

            nornir_imageregistration.views.plot_tile_overlaps(
                new_overlaps + updated_overlaps + locked_overlaps,
                colors=overlap_colors,
                OutputFilename=os.path.join(
                    self.TestOutputPath,
                    "pass_{0}_tile_overlaps.svg".format(iPass)))

            translated_layout = self.ReadOrCreateVariable(
                self.id() + "_{0:d}pass_tile_layout_{1:03d}_{2:03g}".format(
                    iPass, downsample, first_pass_excess_scalar),
                self.CalculateOffsetsForTiles,
                tile_offsets_to_update=filtered_overlaps_needing_offsets,
                all_tile_offsets=filtered_distinct_offsets,
                excess_scalar=excess_scalar,
                imageScale=imageScale,
                existing_layout=translated_layout)
            excess_scalar = 3.0
            # Each tile should contain a dictionary with the known offsets.  Show the overlapping images using the calculated offsets

            (tileA_ID, tileB_ID) = _GetWorstOffsetPair(translated_layout)
            self.ShowTilesWithOffset(translated_layout,
                                     initial_tiles,
                                     tileA_ID,
                                     tileB_ID,
                                     "_{0:d}pass_Worst1stPass".format(iPass),
                                     openwindow=openwindow)

            (tileA_ID, tileB_ID) = _GetBestOffsetPair(translated_layout)
            self.ShowTilesWithOffset(translated_layout,
                                     initial_tiles,
                                     tileA_ID,
                                     tileB_ID,
                                     "_{0:d}pass_Best1stPass".format(iPass),
                                     openwindow=openwindow)

            #self.ShowTilesWithOffset(translated_layout, initial_tiles, 0, 1, "{0:d}_pass".format(iPass), openwindow=openwindow)
            #self.ShowTilesWithOffset(translated_layout, initial_tiles, 0, 60, "{0:d}_pass".format(iPass), openwindow=openwindow)
            #self.ShowTilesWithOffset(translated_layout, initial_tiles, 1, 61, "{0:d}_pass".format(iPass), openwindow=openwindow)
            #self.ShowTilesWithOffset(translated_layout, initial_tiles, 60, 61, "{0:d}_pass".format(iPass), openwindow=openwindow)
            #self.ShowTilesWithOffset(translated_layout, initial_tiles, 53, 56, "{0:d}_pass".format(iPass), openwindow=openwindow)

            # mosaic.ArrangeTilesWithTranslate(TilesDir, usecluster=parallel)
            scaled_translated_layout = translated_layout.copy()

            nornir_imageregistration.layout.ScaleOffsetWeightsByPopulationRank(
                scaled_translated_layout,
                min_allowed_weight=minWeight,
                max_allowed_weight=maxWeight)

            #            nornir_imageregistration.layout.NormalizeOffsetWeights(scaled_translated_layout)

            self.PlotLayoutWeightHistogram(
                scaled_translated_layout,
                mosaicBaseName + "_{0:d}pass_weight_histogram".format(iPass),
                openwindow=False)
            translated_final_layouts = nornir_imageregistration.layout.BuildLayoutWithHighestWeightsFirst(
                scaled_translated_layout)
            translated_final_layout = nornir_imageregistration.layout.MergeDisconnectedLayoutsWithOffsets(
                translated_final_layouts, stage_reported_overlaps)

            translated_mosaic = self.CreateSaveShowMosaic(
                mosaicBaseName + "_{0:d}pass_Weighted".format(iPass),
                translated_final_layout,
                initial_tiles,
                openwindow=openwindow,
                target_space_scale=1 / 4.0,
                source_space_scale=None)

            #             relaxed_layout = self._Relax_Layout(scaled_translated_layout,
            #                                                 max_iter=max_relax_iterations,
            #                                                 max_tension_cutoff=max_relax_tension_cutoff,
            #                                                 dirname_postfix="_pass{0:d}".format(iPass))

            relaxed_layouts = []
            for layout in translated_final_layouts:
                relaxed_layout = nornir_imageregistration.layout.RelaxLayout(
                    layout,
                    max_iter=max_relax_iterations,
                    max_tension_cutoff=max_relax_tension_cutoff,
                    plotting_output_path=os.path.join(
                        self.TestOutputPath, "relax_pass_{0:d}".format(iPass)),
                    plotting_interval=10)
                relaxed_layouts.append(relaxed_layout)

            relaxed_layout = nornir_imageregistration.layout.MergeDisconnectedLayoutsWithOffsets(
                relaxed_layouts, stage_reported_overlaps)

            relaxed_layout.UpdateTileTransforms(initial_tiles)

            relaxed_mosaic = self.CreateSaveShowMosaic(
                mosaicBaseName + "_{0:d}pass_relaxed".format(iPass),
                relaxed_layout,
                initial_tiles,
                openwindow=openwindow,
                target_space_scale=1 / 4.0,
                source_space_scale=None)

            last_pass_overlaps = distinct_overlaps

            #Copy the relaxed layout positions back into the translated layout
            for ID, node in relaxed_layout.nodes.items():
                tnode = translated_layout.nodes[ID]
                tnode.Position = node.Position

            nornir_pools.WaitOnAllPools()

        #self.CreateSaveShowMosaic(mosaicBaseName + "_{0:d}pass_relaxed".format(iPass), relaxed_layout, initial_tiles, openwindow)

        #
        original_score = mosaic.QualityScore(TilesDir)
        translated_score = translated_mosaic.QualityScore(TilesDir)
        relaxed_score = relaxed_mosaic.QualityScore(TilesDir)
        #translated_refined_score = translated_refined_mosaic.QualityScore(TilesDir)
        #translated_refined_relaxed_score = translated_refined_relaxed_mosaic.QualityScore(TilesDir)

        print("Original Quality Score: %g" % (original_score))