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))
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)
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
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)
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))
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))