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]])
示例#4
0
    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)
示例#6
0
    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")
示例#8
0
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
示例#9
0
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]
示例#10
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
示例#11
0
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
示例#12
0
    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]
示例#13
0
    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)