def ScoreMosaicQuality(transforms, imagepaths, imageScale=None): ''' Walk each overlapping region between tiles. Subtract the ''' tiles = nornir_imageregistration.tile.CreateTiles(transforms, imagepaths) if imageScale is None: imageScale = tileset.MostCommonScalar(transforms, imagepaths) list_tiles = list(tiles.values()) total_score = 0 total_pixels = 0 pool = nornir_pools.GetGlobalMultithreadingPool() tasks = list() for tile_overlap in nornir_imageregistration.tile_overlap.IterateTileOverlaps( list_tiles, imageScale=imageScale): # (downsampled_overlapping_rect_A, downsampled_overlapping_rect_B, OffsetAdjustment) = nornir_imageregistration.tile.Tile.Calculate_Overlapping_Regions(tile_overlap.A, tile_overlap.B, imageScale) # __AlignmentScoreRemote(A.ImagePath, B.ImagePath, downsampled_overlapping_rect_A, downsampled_overlapping_rect_B) t = pool.add_task( "Score %d -> %d" % (tile_overlap.ID[0], tile_overlap.ID[1]), __AlignmentScoreRemote, tile_overlap.A.ImagePath, tile_overlap.B.ImagePath, tile_overlap.scaled_overlapping_source_rect_A, tile_overlap.scaled_overlapping_source_rect_B) tasks.append(t) # OverlappingRegionA = __get_overlapping_image(A.Image, downsampled_overlapping_rect_A, excess_scalar=1.0) # OverlappingRegionB = __get_overlapping_image(B.Image, downsampled_overlapping_rect_B, excess_scalar=1.0) # # OverlappingRegionA -= OverlappingRegionB # absoluteDiff = np.fabs(OverlappingRegionA) # score = np.sum(absoluteDiff.flat) pool.wait_completion() for t in tasks: # (score, num_pixels) = t.wait_return() score = t.wait_return() total_score += score # total_pixels += np.prod(num_pixels) # return total_score / total_pixels return total_score / len(tasks)
def TranslateTiles(transforms, imagepaths, excess_scalar, imageScale=None, max_relax_iterations=None, max_relax_tension_cutoff=None): ''' Finds the optimal translation of a set of tiles to construct a larger seemless mosaic. :param list transforms: list of transforms for tiles :param list imagepaths: list of paths to tile images, must be same length as transforms list :param float excess_scalar: How much additional area should we pad the overlapping regions with. :param float imageScale: The downsampling of the images in imagepaths. If None then this is calculated based on the difference in the transform and the image file dimensions :param int max_relax_iterations: Maximum number of iterations in the relax stage :param float max_relax_tension_cutoff: Stop relaxation stage if the maximum tension vector is below this value :return: (offsets_collection, tiles) tuple ''' if max_relax_iterations is None: max_relax_iterations = 150 if max_relax_tension_cutoff is None: max_relax_tension_cutoff = 1.0 if imageScale is None: imageScale = tileset.MostCommonScalar(transforms, imagepaths) tiles = nornir_imageregistration.tile.CreateTiles(transforms, imagepaths) tile_layout = _FindTileOffsets(tiles, excess_scalar, imageScale=imageScale) nornir_imageregistration.layout.ScaleOffsetWeightsByPopulationRank( tile_layout, min_allowed_weight=0.25, max_allowed_weight=1.0) nornir_imageregistration.layout.RelaxLayout( tile_layout, max_tension_cutoff=max_relax_tension_cutoff, max_iter=max_relax_iterations) # final_layout = nornir_imageregistration.layout.BuildLayoutWithHighestWeightsFirst(offsets_collection) # Create a mosaic file using the tile paths and transforms return (tile_layout, tiles)
def GenerateTileOverlaps(tiles, existing_overlaps=None, offset_epsilon=1.0, image_scale=None, min_overlap=None, inter_tile_distance_scale=None): ''' Create a list of TileOverlap objects for each overlapping region in the mosaic. Assign a feature score to the regions from each image that overlap. :param list tiles: A dictionary of Tile objects :param list existing_overlaps: A list of overlaps created previously. Scores for these offsets will be copied into the generated offsets if the difference in offset between the tiles is less than offset_epsilon. :param float offset_epsilon: The distance the expected offset between tiles has to change before we recalculate feature scores and registration :param float imageScale: The amount the images are downsampled compared to coordinates in the transforms :param float min_overlap: Tiles that overlap less than this amount percentage of area will not be included :return: Returns a four-component tuple composed of all found overlaps, the new overlaps, the overlaps that require updating, the deleted overlaps from the existing set, and the IDs of non-overlapping tiles. None of the returned overlaps are the same objects as those in the original set ''' assert (isinstance(tiles, dict)) if image_scale is None: image_scale = tileset.MostCommonScalar( [tile.Transform for tile in tiles.values()], [tile.ImagePath for tile in tiles.values()]) if inter_tile_distance_scale is None: inter_tile_distance_scale = 1.0 if inter_tile_distance_scale < 0 or inter_tile_distance_scale > 1.0: raise ValueError( "inter_tile_distance_scale must be in the range 0 to 1: value was {0}" .format(inter_tile_distance_scale)) generated_overlaps = list( nornir_imageregistration.tile_overlap.CreateTileOverlaps( list(tiles.values()), image_scale, min_overlap=min_overlap, inter_tile_distance_scale=inter_tile_distance_scale)) removed_offset_IDs = [] new_overlaps = [] updated_overlaps = [] nonoverlapping_tile_IDs = set(tiles.keys()) #Tiles with no overlaps for overlap in generated_overlaps: nonoverlapping_tile_IDs -= set(overlap.ID) # new_or_updated = [] # Iterate the current set of overlaps and determine if: # 1. The overlap is new and should be included # 2. The overlap is not different in a meaningful way # 3. The overlap is changed and should be recalculated num_new = 0 num_different = 0 num_similiar = 0 if existing_overlaps is None: new_overlaps.extend(generated_overlaps) num_new = len(generated_overlaps) else: existing_dict = {o.ID: o for o in existing_overlaps} updated_dict = {o.ID: o for o in generated_overlaps} # Remove overlaps that no longer exist so they aren't considered later for overlap in existing_overlaps: if overlap.ID not in updated_dict: # to_remove = existing_dict[overlap.ID] # del existing_dict[overlap.ID] # existing_overlaps.remove(to_remove) print("Removing overlap {0}".format(str(overlap))) removed_offset_IDs.append(overlap.ID) for updated in generated_overlaps: if not updated.ID in existing_dict: # new_or_updated.append(updated) new_overlaps.append(updated) num_new = num_new + 1 print("New overlap {0}".format(str(updated))) else: existing = existing_dict[updated.ID] # Compare the offsets delta = updated.scaled_offset - existing.scaled_offset distance = nornir_imageregistration.array_distance(delta) # Check whether it is significantly different if distance < offset_epsilon: # Substantially the same, recycle the feature scores updated.feature_scores = existing.feature_scores num_similiar += 1 else: updated_overlaps.append(updated) num_different += 1 print("\n") print("Updated Tile Overlaps:") print("{0} overlaps new".format(num_new)) print("{0} overlaps unchanged".format(num_similiar)) print("{0} overlaps changed".format(num_different)) print("{0} overlaps removed".format(len(removed_offset_IDs))) print("\n") return (generated_overlaps, new_overlaps, updated_overlaps, removed_offset_IDs, nonoverlapping_tile_IDs)
def TranslateTiles2(transforms, imagepaths, first_pass_excess_scalar=None, excess_scalar=None, feature_score_threshold=None, image_scale=None, min_translate_iterations=None, offset_acceptance_threshold=None, max_relax_iterations=None, max_relax_tension_cutoff=None, min_overlap=None, first_pass_inter_tile_distance_scale=None, inter_tile_distance_scale=None): ''' Finds the optimal translation of a set of tiles to construct a larger seemless mosaic. :param list transforms: list of transforms for tiles :param list imagepaths: list of paths to tile images, must be same length as transforms list :param float excess_scalar: How much additional area should we pad the overlapping regions with. Increase this value if you want larger offsets to be found. :param float feature_score_threshold: The minimum average power spectral density per pixel measurement required to believe there is enough texture in overlapping regions for registration algorithms :param float imageScale: The downsampling of the images in imagepaths. If None then this is calculated based on the difference in the transform and the image file dimensions :param int max_relax_iterations: Maximum number of iterations in the relax stage :param float max_relax_tension_cutoff: Stop relaxation stage if the maximum tension vector is below this value :param float min_overlap: The percentage of area that two tiles must overlap before being considered by the layout model :param float inter_tile_distance_scale: A scalar from 0 to 1. 1 indicates to trust the overlap reported by the transforms. 0 indicates to test the entire tile for overlaps. Use this value to increase the area searched for correlations if the stage input is not reliable. :return: (offsets_collection, tiles) tuple ''' if max_relax_iterations is None: max_relax_iterations = 150 if max_relax_tension_cutoff is None: max_relax_tension_cutoff = 1.0 if feature_score_threshold is None: feature_score_threshold = 0.035 if offset_acceptance_threshold is None: offset_acceptance_threshold = 1.0 if min_translate_iterations is None: min_translate_iterations = 5 if inter_tile_distance_scale is None: inter_tile_distance_scale = 1.0 if first_pass_inter_tile_distance_scale is None: first_pass_inter_tile_distance_scale = inter_tile_distance_scale / 2 if first_pass_excess_scalar is None: first_pass_excess_scalar = 3.0 if excess_scalar is None: excess_scalar = 3.0 if image_scale is None: image_scale = tileset.MostCommonScalar(transforms, imagepaths) tiles = nornir_imageregistration.tile.CreateTiles(transforms, imagepaths) if len(tiles) == 1: #If there is only one tile then just return it single_tile_layout = nornir_imageregistration.layout.Layout() single_tile_layout.CreateNode(tiles[0].ID, np.zeros((1, 2))) return (single_tile_layout, tiles) minOffsetWeight = 0 maxOffsetWeight = 1.0 last_pass_overlaps = None translated_layout = None iPass = min_translate_iterations max_passes = min_translate_iterations * 4 pass_count = 0 inter_tile_distance_scale_this_pass = first_pass_inter_tile_distance_scale inter_tile_distance_scale_last_pass = inter_tile_distance_scale_this_pass first_pass_overlaps = None #The set of offsets for each tile pair from the first-pass. Used to align layouts that are not connected. stage_reported_overlaps = None relaxed_layout = None while iPass >= 0: (distinct_overlaps, new_overlaps, updated_overlaps, removed_overlap_IDs, nonoverlapping_tile_IDs) = GenerateTileOverlaps( tiles=tiles, existing_overlaps=last_pass_overlaps, offset_epsilon=offset_acceptance_threshold, image_scale=image_scale, min_overlap=min_overlap, inter_tile_distance_scale=inter_tile_distance_scale_this_pass) if stage_reported_overlaps is None: stage_reported_overlaps = {to.ID: to.offset for to in new_overlaps} new_or_updated_overlaps = list(new_overlaps) new_or_updated_overlaps.extend(updated_overlaps) # If there is nothing to update we are done if len(new_or_updated_overlaps) == 0: break # If we added or remove tile overlaps then reset loop counter # if (len(new_overlaps) > 0 or len(removed_overlap_IDs) > 0) and pass_count < max_passes: # iPass = min_translate_iterations ScoreTileOverlaps(distinct_overlaps) # If this is the second pass remove any overlaps from the layout that no longer qualify if translated_layout is not None: for ID in removed_overlap_IDs: translated_layout.RemoveOverlap(ID) # Expand the area we search if we are adding and removing tiles inter_tile_distance_scale_this_pass = inter_tile_distance_scale # if pass_count < min_translate_iterations and iPass == min_translate_iterations: # inter_tile_distance_scale_this_pass = inter_tile_distance_scale # Recalculate all offsets if we changed the overlaps # if inter_tile_distance_scale_last_pass != inter_tile_distance_scale_this_pass: # new_or_updated_overlaps = distinct_overlaps inter_tile_distance_scale_last_pass = inter_tile_distance_scale_this_pass # 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) translated_layout = _FindTileOffsets(filtered_overlaps_needing_offsets, excess_scalar=excess_scalar, imageScale=image_scale, existing_layout=translated_layout) scaled_translated_layout = translated_layout.copy() nornir_imageregistration.layout.ScaleOffsetWeightsByPopulationRank( scaled_translated_layout, min_allowed_weight=minOffsetWeight, max_allowed_weight=maxOffsetWeight) # nornir_imageregistration.layout.NormalizeOffsetWeights(scaled_translated_layout) translated_final_layouts = nornir_imageregistration.layout.BuildLayoutWithHighestWeightsFirst( scaled_translated_layout) #TODO: Pass the dictionary to this function that indicates tile offsets for pairs of tiles #translated_final_layout = nornir_imageregistration.layout.MergeDisconnectedLayoutsWithOffsets(translated_final_layouts, stage_reported_overlaps) #Should we do a shorter pass on the first run? relax_iterations = max_relax_iterations if iPass == min_translate_iterations: relax_iterations = relax_iterations // 4 if relax_iterations < 10: relax_iterations = max_relax_iterations // 2 relaxed_layouts = [] for layout in translated_final_layouts: relaxed_layout = nornir_imageregistration.layout.RelaxLayout( layout, max_iter=relax_iterations, max_tension_cutoff=max_relax_tension_cutoff) relaxed_layouts.append(relaxed_layout) relaxed_layout = nornir_imageregistration.layout.MergeDisconnectedLayoutsWithOffsets( relaxed_layouts, stage_reported_overlaps) relaxed_layout.UpdateTileTransforms(tiles) 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 iPass = iPass - 1 pass_count = pass_count + 1 # final_layout = nornir_imageregistration.layout.BuildLayoutWithHighestWeightsFirst(offsets_collection) # Create a mosaic file using the tile paths and transforms return (relaxed_layout, tiles)
def TilesToImageParallel(transforms, imagepaths, TargetRegion=None, target_space_scale=None, source_space_scale=None, pool=None): '''Assembles a set of transforms and imagepaths to a single image using parallel techniques. :param tuple TargetRegion: (MinX, MinY, Width, Height) or Rectangle class. Specifies the SourceSpace to render from :param float target_space_scale: Scalar for the target space coordinates. Used to downsample or upsample the output image. Changes the coordinates of the target space control points of the transform. :param float target_space_scale: Scalar for the source space coordinates. Must match the change in scale of input images relative to the transform source space coordinates. So if downsampled by 4 images are used, this value should be 0.25. Calculated to be correct if None. Specifying is an optimization to reduce I/O of reading image files to calculate. ''' assert (len(transforms) == len(imagepaths)) logger = logging.getLogger('TilesToImageParallel') if pool is None: pool = nornir_pools.GetGlobalMultithreadingPool() # pool = nornir_pools.GetGlobalSerialPool() tasks = [] if source_space_scale is None: source_space_scale = tiles.MostCommonScalar(transforms, imagepaths) if target_space_scale is None: target_space_scale = source_space_scale original_fixed_rect_floats = None if not TargetRegion is None: if isinstance(TargetRegion, spatial.Rectangle): original_fixed_rect_floats = TargetRegion else: original_fixed_rect_floats = spatial.Rectangle.CreateFromPointAndArea( (TargetRegion[0], TargetRegion[1]), (TargetRegion[2] - TargetRegion[0], TargetRegion[3] - TargetRegion[1])) else: original_fixed_rect_floats = tutils.FixedBoundingBox(transforms) scaled_targetRect = nornir_imageregistration.Rectangle.scale_on_origin( original_fixed_rect_floats, target_space_scale) scaled_targetRect = nornir_imageregistration.Rectangle.SafeRound( scaled_targetRect) targetRect = nornir_imageregistration.Rectangle.scale_on_origin( scaled_targetRect, 1.0 / target_space_scale) (fullImage, fullImageZbuffer) = __CreateOutputBufferForArea(scaled_targetRect.Height, scaled_targetRect.Width, target_space_scale) CheckTaskInterval = 16 for i, transform in enumerate(transforms): regionToRender = None original_transform_target_rect = spatial.Rectangle( transform.FixedBoundingBox) transform_target_rect = nornir_imageregistration.Rectangle.SafeRound( original_transform_target_rect) regionToRender = nornir_imageregistration.Rectangle.Intersect( targetRect, transform_target_rect) if regionToRender is None: continue if regionToRender.Area == 0: continue scaled_region_rendered = nornir_imageregistration.Rectangle.scale_on_origin( regionToRender, target_space_scale) scaled_region_rendered = nornir_imageregistration.Rectangle.SafeRound( scaled_region_rendered) imagefullpath = imagepaths[i] task = pool.add_task("TransformTile" + imagefullpath, TransformTile, transform=transform, imagefullpath=imagefullpath, distanceImage=None, target_space_scale=target_space_scale, TargetRegion=regionToRender, SingleThreadedInvoke=False) task.transform = transform task.regionToRender = regionToRender task.scaled_region_rendered = scaled_region_rendered task.transform_fixed_rect = transform_target_rect tasks.append(task) if not i % CheckTaskInterval == 0: continue if len(tasks) > multiprocessing.cpu_count(): iTask = len(tasks) - 1 while iTask >= 0: t = tasks[iTask] if t.iscompleted: transformedImageData = t.wait_return() __AddTransformedTileTaskToComposite( t, transformedImageData, fullImage, fullImageZbuffer, scaled_targetRect) del transformedImageData del tasks[iTask] iTask -= 1 logger.info('All warps queued, integrating results into final image') while len(tasks) > 0: t = tasks.pop(0) transformedImageData = t.wait_return() __AddTransformedTileTaskToComposite(t, transformedImageData, fullImage, fullImageZbuffer, scaled_targetRect) del transformedImageData del t # Pass through the entire loop and eliminate completed tasks in case any finished out of order iTask = len(tasks) - 1 while iTask >= 0: t = tasks[iTask] if t.iscompleted: transformedImageData = t.wait_return() __AddTransformedTileTaskToComposite(t, transformedImageData, fullImage, fullImageZbuffer, scaled_targetRect) del transformedImageData del tasks[iTask] iTask -= 1 logger.info('Final image complete, building mask') mask = fullImageZbuffer < __MaxZBufferValue(fullImageZbuffer.dtype) del fullImageZbuffer fullImage[fullImage < 0] = 0 # Checking for > 1.0 makes sense for floating point images. During the DM4 migration # I was getting images which used 0-255 values, and the 1.0 check set them to entirely black # fullImage[fullImage > 1.0] = 1.0 logger.info('Assemble complete') if isinstance(fullImage, np.memmap): fullImage.flush() return (fullImage, mask)
def TilesToImage(transforms, imagepaths, TargetRegion=None, target_space_scale=None, source_space_scale=None): ''' Generate an image of the TargetRegion. :param tuple TargetRegion: (MinX, MinY, Width, Height) or Rectangle class. Specifies the SourceSpace to render from :param float target_space_scale: Scalar for the target space coordinates. Used to downsample or upsample the output image. Changes the coordinates of the target space control points of the transform. :param float target_space_scale: Scalar for the source space coordinates. Must match the change in scale of input images relative to the transform source space coordinates. So if downsampled by 4 images are used, this value should be 0.25. Calculated to be correct if None. Specifying is an optimization to reduce I/O of reading image files to calculate. ''' assert (len(transforms) == len(imagepaths)) # logger = logging.getLogger(__name__ + '.TilesToImage') if source_space_scale is None: source_space_scale = tiles.MostCommonScalar(transforms, imagepaths) if target_space_scale is None: target_space_scale = source_space_scale distanceImage = None original_fixed_rect_floats = None if not TargetRegion is None: if isinstance(TargetRegion, spatial.Rectangle): original_fixed_rect_floats = TargetRegion else: original_fixed_rect_floats = spatial.Rectangle.CreateFromPointAndArea( (TargetRegion[0], TargetRegion[1]), (TargetRegion[2] - TargetRegion[0], TargetRegion[3] - TargetRegion[1])) else: original_fixed_rect_floats = tutils.FixedBoundingBox(transforms) scaled_targetRect = nornir_imageregistration.Rectangle.scale_on_origin( original_fixed_rect_floats, target_space_scale) scaled_targetRect = nornir_imageregistration.Rectangle.SafeRound( scaled_targetRect) targetRect = nornir_imageregistration.Rectangle.scale_on_origin( scaled_targetRect, 1.0 / target_space_scale) (fullImage, fullImageZbuffer) = __CreateOutputBufferForArea(scaled_targetRect.Height, scaled_targetRect.Width, target_space_scale) for i, transform in enumerate(transforms): regionToRender = None original_transform_fixed_rect = spatial.Rectangle( transform.FixedBoundingBox) transform_target_rect = nornir_imageregistration.Rectangle.SafeRound( original_transform_fixed_rect) regionToRender = nornir_imageregistration.Rectangle.Intersect( targetRect, transform_target_rect) if regionToRender is None: continue if regionToRender.Area == 0: continue scaled_region_rendered = nornir_imageregistration.Rectangle.scale_on_origin( regionToRender, target_space_scale) scaled_region_rendered = nornir_imageregistration.Rectangle.SafeRound( scaled_region_rendered) imagefullpath = imagepaths[i] distanceImage = __GetOrCreateDistanceImage( distanceImage, nornir_imageregistration.GetImageSize(imagefullpath)) transformedImageData = TransformTile( transform, imagefullpath, distanceImage, target_space_scale=target_space_scale, TargetRegion=regionToRender, SingleThreadedInvoke=True) if transformedImageData.image is None: logger = logging.getLogger('TilesToImageParallel') logger.error('Convert task failed: ' + str(transformedImageData)) if not transformedImageData.errormsg is None: logger.error(transformedImageData.errormsg) continue CompositeOffset = scaled_region_rendered.BottomLeft - scaled_targetRect.BottomLeft CompositeOffset = CompositeOffset.astype(np.int64) CompositeImageWithZBuffer(fullImage, fullImageZbuffer, transformedImageData.image, transformedImageData.centerDistanceImage, CompositeOffset) del transformedImageData mask = fullImageZbuffer < __MaxZBufferValue(fullImageZbuffer.dtype) del fullImageZbuffer fullImage[fullImage < 0] = 0 # Checking for > 1.0 makes sense for floating point images. During the DM4 migration # I was getting images which used 0-255 values, and the 1.0 check set them to entirely black # fullImage[fullImage > 1.0] = 1.0 if isinstance(fullImage, np.memmap): fullImage.flush() return (fullImage, mask)
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 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)