Example #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))
Example #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)))
    def ShowTilesWithOffset(self, layout_obj, tiles_list, TileA_ID, TileB_ID,
                            filename, openwindow):

        if not (layout_obj.Contains(TileA_ID)
                and layout_obj.Contains(TileB_ID)):
            print(
                "Tile offset for {0},{1} cannot be shown, Tiles missing in layout"
                .format(TileA_ID, TileB_ID))
            return

        if not layout_obj.ContainsOffset((TileA_ID, TileB_ID)):
            print(
                "Tile offset for {0},{1} cannot be shown, offset not specified"
                .format(TileA_ID, TileB_ID))
            return

        NodeA = layout_obj.nodes[TileA_ID]
        NodeB_Offset = NodeA.GetOffset(TileB_ID)

        tileA = tiles_list[TileA_ID]
        tileB = tiles_list[TileB_ID]

        # transformA = self.RigidTransformForTile(tileA, AlignmentRecord((0, -624 * 2.0), 0, 0))
        transformA = self.RigidTransformForTile(tileA)
        transformB = self.RigidTransformForTile(tileB, NodeB_Offset)

        ImageToTransform = {}
        ImageToTransform[tileA.ImagePath] = transformA
        ImageToTransform[tileB.ImagePath] = transformB

        mosaic = Mosaic(ImageToTransform)
        mosaic.TranslateToZeroOrigin()

        info_str = "%d -> %d\noffset: (%gx, %gy)\nweight: %g" % (
            TileA_ID, TileB_ID, NodeB_Offset[1], NodeB_Offset[0],
            NodeA.GetWeight(TileB_ID))

        self._ShowMosaic(mosaic,
                         usecluster=False,
                         title=info_str,
                         mosaic_path=os.path.join(
                             self.TestOutputPath,
                             filename + "_%d-%d.png" % (TileA_ID, TileB_ID)),
                         openwindow=openwindow)
Example #5
0
    def ToMosaic(cls, VolumeObj, idocFileFullPath, ContrastCutoffs, OutputImageExt=None, TargetBpp=None, FlipList=None, ContrastMap=None, CameraBpp=None, debug=None):
        '''
        This function will convert an idoc file in the given path to a .mosaic file.
        It will also rename image files to the requested extension and subdirectory.
        TargetBpp is calculated based on the number of bits required to encode the values
        between the median min and max values
        :param list FlipList: List of section numbers which should have images flipped
        :param dict ContrastMap: Dictionary mapping section number to (Min, Max, Gamma) tuples 
        '''
        if(OutputImageExt is None):
            OutputImageExt = 'png'
  
        if TargetBpp is None:
            TargetBpp = 8

        if FlipList is None:
            FlipList = []

        if ContrastMap is None:
            ContrastMap = {}
   
        SaveChannel = False

        idocFilePath = serialem_utils.GetPathWithoutSpaces(idocFileFullPath)
 
        OutputPath = VolumeObj.FullPath

        os.makedirs(OutputPath, exist_ok=True)

        logger = logging.getLogger(__name__ + '.' + str(cls.__name__) + "ToMosaic")

        # Report the current stage to the user
        prettyoutput.CurseString('Stage', "SerialEM to Mosaic " + str(idocFileFullPath))

        SectionNumber = 0
        sectionDir = os.path.dirname(idocFileFullPath) #serialem_utils.GetDirectories(idocFileFullPath)

        BlockObj = BlockNode.Create('TEM')
        [saveBlock, BlockObj] = VolumeObj.UpdateOrAddChild(BlockObj)

        # If the parent directory doesn't have the section number in the name, change it
        ExistingSectionInfo = shared.GetSectionInfo(sectionDir)
        if(ExistingSectionInfo[0] < 0):
            i = 5
#            SectionNumber = SectionNumber + 1
#            newPathName = ('%' + nornir_buildmanager.templates.Current.SectionFormat) % SectionNumber + '_' + sectionDir
#            newPath = os.path.join(ParentDir, newPathName)
#            prettyoutput.Log('Moving: ' + InputPath + ' -> ' + newPath)
#            shutil.move(InputPath, newPath)
#
#            InputPath = newPath
#
#            #Run glob again because the dir changes
#            idocFiles = glob.glob(os.path.join(InputPath,'*.idoc'))
        else:
            SectionNumber = ExistingSectionInfo.number

        prettyoutput.CurseString('Section', str(SectionNumber))

        # Check for underscores.  If there is an underscore and the first part is the sectionNumber, then use everything after as the section name
        SectionName = ('%' + nornir_buildmanager.templates.Current.SectionFormat) % ExistingSectionInfo.number
        SectionPath = ('%' + nornir_buildmanager.templates.Current.SectionFormat) % ExistingSectionInfo.number
        try:
            parts = sectionDir.partition("_")
            if not parts is None:
                if len(parts[2]) > 0:
                    SectionName = parts[2]
        except:
            pass

        sectionObj = SectionNode.Create(SectionNumber,
                                        SectionName,
                                        SectionPath)

        [saveSection, sectionObj] = BlockObj.UpdateOrAddChildByAttrib(sectionObj, 'Number')
        sectionObj.Name = SectionName

        # Create a channel group 
        [saveChannel, channelObj] = sectionObj.UpdateOrAddChildByAttrib(ChannelNode.Create('TEM'), 'Name')
   
        # Create a channel group for the section

        # I started ignoring existing supertile.mosaic files so I could rebuild sections where
        # a handful of tiles were corrupt
        # if(os.path.exists(SupertilePath)):
        #    continue

        Flip = SectionNumber in FlipList;
        if(Flip):
            prettyoutput.Log("Found in FlipList.txt, flopping images")

        IDocData = IDoc.Load(idocFilePath, CameraBpp=CameraBpp)

        assert(hasattr(IDocData, 'PixelSpacing'))
        assert(hasattr(IDocData, 'DataMode'))
        assert(hasattr(IDocData, 'ImageSize'))

        # If there are no tiles... return
        if IDocData.NumTiles == 0:
            prettyoutput.Log("No tiles found in IDoc: " + idocFilePath)
            return

        # See if we can find a notes file...
        shared.TryAddNotes(channelObj, sectionDir, logger)
        serialem_utils.TryAddLogs(channelObj, sectionDir, logger)

        AddIdocNode(channelObj, idocFilePath, IDocData, logger)

        # Set the scale
        [added, ScaleObj] = cls.CreateScaleNode(IDocData, channelObj)
        
        # Parse the images
        ImageBpp = IDocData.GetImageBpp()            
        if ImageBpp is None:
            ImageBpp = cls.GetImageBpp(IDocData, sectionDir) 

        FilterName = 'Raw' + str(TargetBpp)
        if(TargetBpp is None):
            FilterName = 'Raw'

        histogramFullPath = os.path.join(sectionDir, 'Histogram.xml')
        
        IDocData.RemoveMissingTiles(sectionDir)
        source_tile_list = [os.path.join(sectionDir, t.Image) for t in IDocData.tiles ]
          
        (ActualMosaicMin, ActualMosaicMax, Gamma) = cls.GetSectionContrastSettings(SectionNumber, ContrastMap, ContrastCutoffs, source_tile_list, IDocData, histogramFullPath)
        ActualMosaicMax = numpy.around(ActualMosaicMax)
        ActualMosaicMin = numpy.around(ActualMosaicMin)
        
        contrast_mismatch = channelObj.RemoveFilterOnContrastMismatch(FilterName, ActualMosaicMin, ActualMosaicMax, Gamma)
    
        Pool = nornir_pools.GetGlobalThreadPool()
        #_PlotHistogram(histogramFullPath, SectionNumber, ActualMosaicMin, ActualMosaicMax)
        Pool.add_task(histogramFullPath, _PlotHistogram, histogramFullPath, SectionNumber, ActualMosaicMin, ActualMosaicMax, force_recreate=contrast_mismatch)
        
        
        ImageConversionRequired = contrast_mismatch

        # Create a channel for the Raw data 
        [added_filter, filterObj] = channelObj.UpdateOrAddChildByAttrib(FilterNode.Create(Name=FilterName), 'Name')
        if added_filter:
            ImageConversionRequired = True

        filterObj.SetContrastValues(ActualMosaicMin, ActualMosaicMax, Gamma)
        filterObj.BitsPerPixel = TargetBpp

        SupertileName = 'Stage'
        SupertileTransform = SupertileName + '.mosaic'
        SupertilePath = os.path.join(channelObj.FullPath, SupertileTransform)

        # Check to make sure our supertile mosaic file is valid
        RemoveOutdatedFile(idocFilePath, SupertilePath)

        [added_transform, transformObj] = channelObj.UpdateOrAddChildByAttrib(TransformNode.Create(Name=SupertileName,
                                                                         Path=SupertileTransform,
                                                                         Type='Stage'),
                                                                         'Path')

        [added_tilepyramid, PyramidNodeObj] = filterObj.UpdateOrAddChildByAttrib(TilePyramidNode.Create(Type='stage',
                                                                            NumberOfTiles=IDocData.NumTiles),
                                                                            'Path')

        [added_level, LevelObj] = PyramidNodeObj.GetOrCreateLevel(1, GenerateData=False)

        Tileset = NornirTileset.CreateTilesFromIDocTileData(IDocData.tiles, InputTileDir=sectionDir, OutputTileDir=LevelObj.FullPath, OutputImageExt=OutputImageExt)

        # Make sure the target LevelObj is verified        
        if not os.path.exists(LevelObj.FullPath):
            os.makedirs(LevelObj.FullPath, exist_ok=True)
        else:
            Tileset.RemoveStaleTilesFromOutputDir(SupertilePath=SupertilePath)
            VerifyTiles(filterObj.TilePyramid.GetLevel(1))

        SourceToMissingTargetMap = Tileset.GetSourceToMissingTargetMap()

        # Figure out if we have to move or convert images
        if len(SourceToMissingTargetMap) == 0:
            ImageConversionRequired = False
        else:
            ImageConversionRequired = (not ImageBpp == TargetBpp) or (ImageConversionRequired or Tileset.ImageConversionRequired)

        if(ImageConversionRequired):
            Invert = False 
            filterObj.SetContrastValues(ActualMosaicMin, ActualMosaicMax, Gamma)
            filterObj.TilePyramid.NumberOfTiles = IDocData.NumTiles
            # andValue = cls.GetBitmask(ActualMosaicMin, ActualMosaicMax, TargetBpp)
            #nornir_shared.images.ConvertImagesInDict(SourceToMissingTargetMap, Flip=Flip, Bpp=TargetBpp, Invert=Invert, bDeleteOriginal=False, MinMax=[ActualMosaicMin, ActualMosaicMax])
            nornir_imageregistration.ConvertImagesInDict(SourceToMissingTargetMap, Flip=Flip, InputBpp=ImageBpp, OutputBpp=TargetBpp, Invert=Invert, bDeleteOriginal=False, MinMax=[ActualMosaicMin, ActualMosaicMax], Gamma=Gamma)

        elif(Tileset.ImageMoveRequired):
            for f in SourceToMissingTargetMap:
                shutil.copy(f, SourceToMissingTargetMap[f])

        # If we wrote new images replace the .mosaic file
        if len(SourceToMissingTargetMap) > 0 or not os.path.exists(SupertilePath):
            # Writing this file indicates import succeeded and we don't need to repeat these steps, writing it will possibly invalidate a lot of downstream data
            # We need to flip the images.  This may be a Utah scope issue, our Y coordinates are inverted relative to the images.  To fix this
            # we flop instead of flip and reverse when writing the coordinates
            mosaicfile.MosaicFile.Write(SupertilePath, Entries=Tileset.GetPositionsForTargets(), Flip=not Flip, ImageSize=IDocData.ImageSize, Downsample=1);
            MFile = mosaicfile.MosaicFile.Load(SupertilePath)

            # Sometimes files fail to convert, when this occurs remove them from the .mosaic
            if MFile.RemoveInvalidMosaicImages(LevelObj.FullPath):
                MFile.Save(SupertilePath)
 
            Mosaic.TranslateMosaicFileToZeroOrigin(SupertilePath)
            transformObj.ResetChecksum()
            SaveChannel = True
            # transformObj.Checksum = MFile.Checksum

        if saveBlock:
            return VolumeObj
        elif saveSection:
            return BlockObj
        elif saveChannel:
            return sectionObj
        elif  added_transform or added_tilepyramid or added_level or ImageConversionRequired or SaveChannel or contrast_mismatch:
            return channelObj
        return None
Example #6
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)
Example #7
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))
Example #8
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))