def testRigidTransformAssemblyFactory(self): angle = -132.0 X = -4 Y = 22 __transform_tolerance = 1e-5 angle = (angle / 180) * np.pi WarpedImagePath = os.path.join( self.ImportedDataPath, "0017_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") FixedImagePath = os.path.join( self.ImportedDataPath, "mini_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") warped_size = nornir_imageregistration.GetImageSize(WarpedImagePath) half_warped_size = np.asarray(warped_size) / 2.0 fixed_size = nornir_imageregistration.GetImageSize(FixedImagePath) half_fixed_size = np.asarray(fixed_size) / 2.0 reference_transform = nornir_imageregistration.transforms.Rigid( [Y, X], half_fixed_size, angle) reference_ImageData = nornir_imageregistration.assemble.WarpedImageToFixedSpace( reference_transform, None, WarpedImagePath) r_transform = nornir_imageregistration.transforms.factory.CreateRigidTransform( target_image_shape=fixed_size, source_image_shape=warped_size, rangle=angle, warped_offset=[Y, X], flip_ud=False) m_transform = nornir_imageregistration.transforms.factory.CreateRigidMeshTransform( target_image_shape=fixed_size, source_image_shape=warped_size, rangle=angle, warped_offset=[Y, X], flip_ud=False) np.testing.assert_allclose(r_transform.MappedBoundingBox.Corners, m_transform.MappedBoundingBox.Corners, atol=__transform_tolerance) np.testing.assert_allclose(r_transform.FixedBoundingBox.Corners, m_transform.FixedBoundingBox.Corners, atol=__transform_tolerance) r_transformedImageData = nornir_imageregistration.assemble.WarpedImageToFixedSpace( r_transform, None, WarpedImagePath) m_transformedImageData = nornir_imageregistration.assemble.WarpedImageToFixedSpace( m_transform, None, WarpedImagePath) self.assertTrue( nornir_imageregistration.ShowGrayscale( [[FixedImagePath, WarpedImagePath], [reference_ImageData], [r_transformedImageData, m_transformedImageData]], title= "Middle row image should be perfectly aligned with the top left\nBottom row should match middle row", PassFail=True))
def testRigidTransformAssemblyDirect(self): angle = -132.0 X = -4 Y = 22 angle = (angle / 180) * np.pi WarpedImagePath = os.path.join( self.ImportedDataPath, "0017_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") FixedImagePath = os.path.join( self.ImportedDataPath, "mini_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") warped_size = nornir_imageregistration.GetImageSize(WarpedImagePath) half_warped_size = np.asarray(warped_size) / 2.0 fixed_size = nornir_imageregistration.GetImageSize(FixedImagePath) half_fixed_size = np.asarray(fixed_size) / 2.0 transform = nornir_imageregistration.transforms.Rigid([Y, X], half_fixed_size, angle) transformedImageData = nornir_imageregistration.assemble.WarpedImageToFixedSpace( transform, None, WarpedImagePath) self.assertTrue( nornir_imageregistration.ShowGrayscale( [FixedImagePath, transformedImageData, WarpedImagePath], title="Second image should be perfectly aligned with the first", PassFail=True))
def TestReadWriteTransform(self): '''A simple test of a transform which maps points from a 10,10 image to a 100,100 without translation or rotation''' WarpedImagePath = os.path.join(self.ImportedDataPath, "10x10.png") self.assertTrue(os.path.exists(WarpedImagePath), "Missing test input") FixedImagePath = os.path.join(self.ImportedDataPath, "1000x100.png") self.assertTrue(os.path.exists(FixedImagePath), "Missing test input") FixedSize = (100, 1000) WarpedSize = (10, 10) arecord = nornir_imageregistration.AlignmentRecord(peak=(0, 0), weight=100, angle=0) alignmentTransform = arecord.ToTransform(FixedSize, WarpedSize) self.assertEqual( FixedSize[0], nornir_imageregistration.GetImageSize(FixedImagePath)[0]) self.assertEqual( FixedSize[1], nornir_imageregistration.GetImageSize(FixedImagePath)[1]) self.assertEqual( WarpedSize[0], nornir_imageregistration.GetImageSize(WarpedImagePath)[0]) self.assertEqual( WarpedSize[1], nornir_imageregistration.GetImageSize(WarpedImagePath)[1]) TransformCheck(self, alignmentTransform, [[0, 0]], [[45, 495]]) TransformCheck(self, alignmentTransform, [[9, 9]], [[54, 504]]) # OK, try to save the stos file and reload it. Make sure the transforms match savedstosObj = arecord.ToStos(FixedImagePath, WarpedImagePath, PixelSpacing=1) self.assertIsNotNone(savedstosObj) stosfilepath = os.path.join(self.VolumeDir, 'TestRWScaleOnly.stos') savedstosObj.Save(stosfilepath) loadedStosObj = StosFile.Load(stosfilepath) self.assertIsNotNone(loadedStosObj) loadedTransform = nornir_imageregistration.transforms.LoadTransform( loadedStosObj.Transform) self.assertIsNotNone(loadedTransform) self.assertTrue( (alignmentTransform.points == loadedTransform.points).all(), "Transform different after save/load") TransformCheck(self, loadedTransform, [[0, 0]], [[45, 495]]) TransformCheck(self, alignmentTransform, [[9, 9]], [[54, 504]])
def AddOrUpdateFilterImageSet(self, channel_obj, filter_obj, imageset_obj, ZLevel): transform_name = imageset_obj.InputTransform db_coord_space = GetCoordSpace(channel_obj, transform_name) (db_channel, created) = models.Channel.objects.get_or_create(name=channel_obj.Name) if created: db_channel.save() (db_filter, created) = models.Filter.objects.get_or_create(name=filter_obj.Name, channel=db_channel) if created: db_filter.save() for (level_number, image) in imageset_obj.GetImages(): img_name = os.path.basename(image.fullpath) (height, width) = nornir_imageregistration.GetImageSize(image.fullpath) db_data = models.Data2D(name=img_name, image=os.path.abspath(image.fullpath), filter=db_filter, level=level_number, relative_path=image.fullpath, coord_space=db_coord_space, width=width, height=height)
def EnsureTransformsHaveMappedBoundingBoxes(self, image_scale, image_path, all_same_dims=True): ''' If a transform does not have a mapped bounding box, define it using the image dimensions :param float image_scale: Downsample factor of image files :parma str image_path: Directory containing image files :param bool all_same_dims: If true, cache image dimensions and re-use for all transforms. ''' cached_tile_shape = None for (file, transform) in self.ImageToTransform.items(): if transform.MappedBoundingBox is None: if all_same_dims == True: mapped_bbox_shape = cached_tile_shape if mapped_bbox_shape is None: image_full_path = os.path.join(image_path, file) mapped_bbox_shape = nornir_imageregistration.GetImageSize( image_full_path) mapped_bbox_shape = np.array( mapped_bbox_shape, dtype=np.int32) * (1.0 / image_scale) cached_tile_shape = mapped_bbox_shape transform.MappedBoundingBox = nornir_imageregistration.Rectangle.CreateFromPointAndArea( (0, 0), mapped_bbox_shape)
def RunStosAssemble(self, stosFullPath): OutputPath = os.path.join(self.VolumeDir, "test_StosAssemble.png") warpedImage = assemble.TransformStos(stosFullPath, OutputPath, self.FixedImagePath, self.WarpedImagePath) self.assertIsNotNone(warpedImage) self.assertTrue(os.path.exists(OutputPath), "RegisteredImage does not exist") self.assertEquals( nornir_imageregistration.GetImageSize(self.FixedImagePath)[0], nornir_imageregistration.GetImageSize(OutputPath)[0]) self.assertEquals( nornir_imageregistration.GetImageSize(self.FixedImagePath)[1], nornir_imageregistration.GetImageSize(OutputPath)[1])
def TestTranslateReadWriteAlignment(self): WarpedImagePath = os.path.join( self.ImportedDataPath, "0017_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") self.assertTrue(os.path.exists(WarpedImagePath), "Missing test input") FixedImagePath = os.path.join( self.ImportedDataPath, "mini_TEM_Leveled_image__feabinary_Cel64_Mes8_sp4_Mes8.png") self.assertTrue(os.path.exists(FixedImagePath), "Missing test input") peak = (20, 5) arecord = nornir_imageregistration.AlignmentRecord(peak, weight=100, angle=0) FixedSize = nornir_imageregistration.GetImageSize(FixedImagePath) WarpedSize = nornir_imageregistration.GetImageSize(WarpedImagePath) alignmentTransform = arecord.ToTransform(FixedSize, WarpedSize) TransformCheck(self, alignmentTransform, [[(WarpedSize[0] / 2.0), (WarpedSize[1] / 2.0)]], [[(FixedSize[0] / 2.0) + peak[0], (FixedSize[1] / 2.0) + peak[1]]]) # OK, try to save the stos file and reload it. Make sure the transforms match savedstosObj = arecord.ToStos(FixedImagePath, WarpedImagePath, PixelSpacing=1) self.assertIsNotNone(savedstosObj) stosfilepath = os.path.join(self.VolumeDir, '17-18_brute.stos') savedstosObj.Save(stosfilepath) loadedStosObj = StosFile.Load(stosfilepath) self.assertIsNotNone(loadedStosObj) loadedTransform = nornir_imageregistration.transforms.LoadTransform( loadedStosObj.Transform) self.assertIsNotNone(loadedTransform) self.assertTrue( (alignmentTransform.points == loadedTransform.points).all(), "Transform different after save/load")
def TransformStos(transformData, OutputFilename=None, fixedImage=None, warpedImage=None, scalar=1.0, CropUndefined=False): '''Assembles an image based on the passed transform. :param str fixedImage: Image describing the size we want the warped image to fill, either a string or ndarray :param str warpedImage: Image we will warp into fixed space, either a string or ndarray :param float scalar: Amount to scale the transform before passing the image through :param bool CropUndefined: If true exclude areas outside the convex hull of the transform, if it exists :param bool Dicreet: True causes points outside the defined transform region to be clipped instead of interpolated :return: transformed image ''' stos = None stostransform = ParameterToStosTransform(transformData) if fixedImage is None: if stos is None: return None fixedImage = stos.ControlImageFullPath if warpedImage is None: if stos is None: return None warpedImage = stos.MappedImageFullPath fixedImageSize = nornir_imageregistration.GetImageSize(fixedImage) fixedImageShape = np.array(fixedImageSize) * scalar warpedImage = nornir_imageregistration.ImageParamToImageArray(warpedImage) stostransform.points = stostransform.points * scalar warpedImage = TransformImage(stostransform, fixedImageShape, warpedImage, CropUndefined) if not OutputFilename is None: imsave(OutputFilename, warpedImage, cmap='gray') return warpedImage
def DimensionsMatch(imageFullPath, area): '''Return true if the area matches the area of the image. :param str imageFullPath: Path to image file on disk :param tuple area: (Width, Height) ''' if area is None: raise TypeError( "RemoveOnDimensionMismatch area parameter should be a (Width,Height) tuple instead of None" ) try: size = nornir_imageregistration.GetImageSize(imageFullPath) except IOError as e: # Unable to read the size, assume the dimensions do not Match logging.error("IOError reading dimensions of file: %s\n%s" % (imageFullPath, str(e))) return False if size is None: return False else: return size[1] == area[1] and size[0] == area[0]
def EnsureTileHasMappedBoundingBox(self, image_scale, cached_tile_shape=None): ''' If our tile does not have a mapped bounding box, define it using the tile's image dimensions :param float image_scale: Downsample factor of image files :param tuple cached_tile_shape: If not None, use these dimensions for the mapped bounding box instead of loading the image to obtain the shape ''' if self._transform.MappedBoundingBox is None: if cached_tile_shape is not None: mapped_bbox_shape = cached_tile_shape else: mapped_bbox_shape = nornir_imageregistration.GetImageSize( self._imagepath) mapped_bbox_shape = np.array( mapped_bbox_shape, dtype=np.int32) * (1.0 / image_scale) self._transform.MappedBoundingBox = nornir_imageregistration.Rectangle.CreateFromPointAndArea( (0, 0), mapped_bbox_shape) return mapped_bbox_shape return None
def ParsePMG(filename, TileOverlapPercent=None): if TileOverlapPercent is None: TileOverlapPercent = 0.1 # Create a dictionary to store tile position Tiles = dict() # OutFilepath = os.path.join(SectionPath, OutFilename) PMGDir = os.path.dirname(filename) if (DEBUG): prettyoutput.Log("Filename: " + filename) # prettyoutput.Log("PMG to: " + OutFilepath) Data = '' with open(filename, 'r') as SourceFile: Data = SourceFile.read() SourceFile.close() Data = Data.replace('\r\n', '\n') Tuple = Data.partition('VISPIECES') Tuple = Tuple[2].partition('\n') NumPieces = int(Tuple[0]) Tuple = Data.partition('VISSCALE') Tuple = Tuple[2].partition('\n') ScaleFactor = 1 / float(Tuple[0]) Tuple = Data.partition('IMAGEREDUCTION') Tuple = Tuple[2].partition('\n') ReductionFactor = 1 / float(Tuple[0]) Data = Tuple[2] if (DEBUG): prettyoutput.Log(("Num Tiles: " + str(NumPieces))) prettyoutput.Log(("Scale : " + str(ScaleFactor))) prettyoutput.Log(("Reduction: " + str(ReductionFactor))) Tuple = Data.partition('PIECE') Data = Tuple[2] # What type of files did Syncroscan write? We have to assume BMP and then switch to tif # if the BMP's do not exist. FileTypeExt = "BMP" TileWidth = 0 TileHeight = 0 while Data: Tuple = Data.partition('ENDPIECE') # Remaining unprocessed file goes into data Data = Tuple[2] Entry = Tuple[0] # Find filename Tuple = Entry.partition('PATH') # Probably means we skipped the last tile in a PMG if (Tuple[1] != "PATH"): continue Tuple = Tuple[2].partition('<') Tuple = Tuple[2].partition('>') TileFilename = Tuple[0] TileFullPath = os.path.join(PMGDir, TileFilename) # PMG files for some reason always claim the tile is a .bmp. So we trust them first and if it doesn't # exist we try to find a .tif if not os.path.exists(TileFullPath): [base, ext] = os.path.splitext(TileFilename) TileFilename = base + '.tif' TileFullPath = os.path.join(PMGDir, TileFilename) FileTypeExt = 'tif' if not os.path.exists(TileFullPath): prettyoutput.Log('Skipping missing tile in PMG: ' + TileFilename) continue if (TileWidth == 0): try: [TileHeight, TileWidth ] = nornir_imageregistration.GetImageSize(TileFullPath) if (DEBUG): prettyoutput.Log( str(TileWidth) + ',' + str(TileHeight) + " " + TileFilename) except: prettyoutput.Log('Could not determine size of tile: ' + TileFilename) continue # prettyoutput.Log("Adding tile: " + TileFilename) # Prevent rounding errors later when we divide these numbers TileWidth = float(TileWidth) TileHeight = float(TileHeight) TileWidthMinusOverlap = TileWidth - (TileWidth * TileOverlapPercent) TileHeightMinusOverlap = TileHeight - (TileHeight * TileOverlapPercent) # Find Position Tuple = Entry.partition('CORNER') Tuple = Tuple[2].partition('\n') Position = Tuple[0].split() # prettyoutput.Log( Position X = float(Position[0]) Y = float(Position[1]) # Convert Position into pixel units using Reduction and Scale factors X = X * ScaleFactor * ReductionFactor Y = Y * ScaleFactor * ReductionFactor # Syncroscan lays out tiles in grids, so find the nearest grid coordinates of this tile # This lets us use the last captured tile per grid cell, so users can manually correct # focus iX = round(X / TileWidthMinusOverlap) iY = round(Y / TileHeightMinusOverlap) if (DEBUG): prettyoutput.Log(("Name,iX,iY: " + TileFilename + " " + str( (iX, iY)))) # Add tile to dictionary # Using the indicies will replace any existing tiles in that location. # Syncroscan adds the tiles to the file in order of capture, so the older, blurry # tiles will be replaced. Tiles[TileFilename] = X, Y # Prime for next iteration Tuple = Data.partition('PIECE') Data = Tuple[2] return Tiles
def ToMosaic(cls, VolumeObj, PMGFullPath, scaleValueInNm, OutputPath=None, Extension=None, OutputImageExt=None, TileOverlap=None, TargetBpp=None, debug=None, *args, **kwargs): '''#Converts a PMG #PMG files are created by Objective Imaging's Surveyor. #This function expects a directory to contain a single PMG file with the tile images in the same directory #Returns the SectionNumber and ChannelName of PMG processed. Otherwise [None,None]''' ParentDir = os.path.dirname(PMGFullPath) sectionDir = os.path.basename(PMGFullPath) # Default to the directory above ours if an output path is not specified if OutputPath is None: OutputPath = os.path.join(PMGFullPath, "..") # If the user did not supply a value, use a default if (TileOverlap is None): TileOverlap = 0.10 if (TargetBpp is None): TargetBpp = 8 # Report the current stage to the user # prettyoutput.CurseString('Stage', "PMGToMosaic " + InputPath) BlockName = 'TEM' BlockObj = BlockNode.Create('TEM') [addedBlock, BlockObj] = VolumeObj.UpdateOrAddChild(BlockObj) ChannelName = None # TODO wrap in try except and print nice error on badly named files? PMG = ParseFilename(PMGFullPath, pmgMappings) PMGDir = os.path.dirname(PMGFullPath) if (PMG is None): raise Exception("Could not parse section from PMG filename: %s" + PMGFullPath) if PMG.Section is None: PMG.Section = PMG.Spot sectionObj = SectionNode.Create(PMG.Section) [addedSection, sectionObj] = BlockObj.UpdateOrAddChildByAttrib(sectionObj, 'Number') # Calculate our output directory. The scripts expect directories to have section numbers, so use that. ChannelName = PMG.Probe ChannelName = ChannelName.replace(' ', '_') channelObj = ChannelNode.Create(ChannelName) channelObj.SetScale(scaleValueInNm) [channelAdded, channelObj] = sectionObj.UpdateOrAddChildByAttrib(channelObj, 'Name') channelObj.Initials = PMG.Initials channelObj.Mag = PMG.Mag channelObj.Spot = PMG.Spot channelObj.Slide = PMG.Slide channelObj.Block = PMG.Block FlipList = GetFlipList(ParentDir) Flip = PMG.Section in FlipList if (Flip): prettyoutput.Log("Flipping") # TODO: Add scale element # OutFilename = ChannelName + "_supertile.mosaic" # OutFilepath = os.path.join(SectionPath, OutFilename) # Preserve the PMG file # PMGBasename = os.path.basename(filename) # PMGOutputFile = os.path.join(OutputPath, PMGBasename) # ir.RemoveOutdatedFile(filename, PMGOutputFile) # if not os.path.exists(PMGOutputFile): # shutil.copy(filename, PMGOutputFile) # # #See if we need to remove the old supertile mosaic # ir.RemoveOutdatedFile(filename, OutFilepath) # if(os.path.exists(OutFilepath)): # continue # Tiles = ParsePMG(PMGFullPath) if len(Tiles) == 0: raise Exception("No tiles found within PMG file") NumImages = len(Tiles) # Create a filter and mosaic FilterName = 'Raw' + str(TargetBpp) if (TargetBpp is None): FilterName = 'Raw' [added_filter, filterObj] = channelObj.GetOrCreateFilter(FilterName) filterObj.BitsPerPixel = TargetBpp SupertileName = 'Stage' SupertileTransform = SupertileName + '.mosaic' [addedTransform, transformObj] = channelObj.UpdateOrAddChildByAttrib( TransformNode.Create(Name=SupertileName, Path=SupertileTransform, Type='Stage'), 'Path') [added, PyramidNodeObj] = filterObj.UpdateOrAddChildByAttrib( TilePyramidNode.Create(Type='stage', NumberOfTiles=NumImages), 'Path') [added, LevelObj ] = PyramidNodeObj.UpdateOrAddChildByAttrib(LevelNode.Create(Level=1), 'Downsample') # Make sure the target LevelObj is verified VerifyTiles(LevelNode=LevelObj) OutputImagePath = os.path.join(channelObj.FullPath, filterObj.Path, PyramidNodeObj.Path, LevelObj.Path) os.makedirs(OutputImagePath, exist_ok=True) InputTileToOutputTile = {} PngTiles = {} TileKeys = list(Tiles.keys()) imageSize = [] for inputTile in TileKeys: [base, ext] = os.path.splitext(inputTile) pngMosaicTile = base + '.png' OutputTileFullPath = os.path.join(LevelObj.FullPath, pngMosaicTile) InputTileFullPath = os.path.join(PMGDir, inputTile) if not os.path.exists(OutputTileFullPath): InputTileToOutputTile[InputTileFullPath] = OutputTileFullPath PngTiles[pngMosaicTile] = Tiles[inputTile] (Height, Width) = nornir_imageregistration.GetImageSize(InputTileFullPath) imageSize.append((Width, Height)) nornir_imageregistration.ConvertImagesInDict(InputTileToOutputTile, Flip=False, OutputBpp=TargetBpp) if not os.path.exists(transformObj.FullPath): mosaicfile.MosaicFile.Write(transformObj.FullPath, PngTiles, Flip=Flip, ImageSize=imageSize) return [PMG.Section, ChannelName]
def BulkAddData2D(self, channel, full_path, rel_path, extension, db_channel, db_filter, ZLevel, level_number): image_paths = glob.glob(os.path.join(full_path, '*' + extension)) if len(image_paths) == 0: return (height, width) = nornir_imageregistration.GetImageSize(image_paths[0]) db_bounds = CreateBoundingBox((ZLevel, 0, 0, ZLevel, height, width)) db_data_list = [] img_rel_path_table = {} for image_path in image_paths: # if os.path.exists(image_path): img_name = os.path.basename(image_path) (img_number, ext) = os.path.splitext(img_name) # (height, width) = nornir_imageregistration.GetImageSize(image_path) # db_bounds = CreateBoundingRect(Rectangle.CreateFromPointAndArea((0, 0), (height, width)), minZ=ZLevel) (db_tile_coordspace, created_tile_coordspace) = self.GetOrCreateTileCoordSpace( channel, 'Tile%d' % int(img_number), bounds=db_bounds) # db_tile_mapping = GetTileMapping(tile_number=img_number, Z=ZLevel, ) img_rel_path = os.path.join(rel_path, img_name) if img_rel_path in img_rel_path_table: print("Trying to create this tile twice: %s" % (img_rel_path)) continue img_rel_path_table[img_rel_path] = True existing_db_data = models.Data2D.objects.filter( relative_path=img_rel_path) if existing_db_data.exists(): #Update path db_data = existing_db_data.first() db_data.image = os.path.abspath(image_path) db_data.filter = db_filter db_data.level = level_number db_data.coord_space = db_tile_coordspace db_data.width = width db_data.height = height db_data.save() else: #Create new, bulk update path db_data = models.Data2D(name=img_name, image=os.path.abspath(image_path), filter=db_filter, level=level_number, relative_path=img_rel_path, coord_space=db_tile_coordspace, width=width, height=height) db_data_list.append(db_data) if len(db_data_list) > 0: models.Data2D.objects.bulk_create(db_data_list)
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)