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)
Пример #7
0
    def AssembleMosaic(self,
                       mosaicFilePath,
                       tilesDir,
                       outputMosaicPath=None,
                       parallel=False,
                       downsamplePath=None):

        SaveFiles = not outputMosaicPath is None

        mosaic = Mosaic.LoadFromMosaicFile(mosaicFilePath)
        mosaicBaseName = os.path.basename(mosaicFilePath)
        (mosaicBaseName, ext) = os.path.splitext(mosaicBaseName)

        mosaic.TranslateToZeroOrigin()

        assembleScale = tiles.MostCommonScalar(
            list(mosaic.ImageToTransform.values()),
            mosaic.TileFullPaths(tilesDir))

        expectedScale = 1.0 / self.DownsampleFromTilePath(tilesDir)

        self.assertEqual(
            assembleScale, expectedScale,
            "Scale for assemble does not match the expected scale")

        timer = TaskTimer()

        timer.Start("AssembleImage " + tilesDir)

        (mosaicImage, mask) = mosaic.AssembleImage(tilesDir,
                                                   usecluster=parallel)

        timer.End("AssembleImage " + tilesDir, True)

        self.assertEqual(
            mosaicImage.shape[0],
            np.ceil(mosaic.FixedBoundingBoxHeight * expectedScale),
            "Output mosaic height does not match .mosaic height %g vs %g" %
            (mosaicImage.shape[0],
             mosaic.FixedBoundingBoxHeight * expectedScale))
        self.assertEqual(
            mosaicImage.shape[1],
            np.ceil(mosaic.FixedBoundingBoxWidth * expectedScale),
            "Output mosaic width does not match .mosaic height %g vs %g" %
            (mosaicImage.shape[1],
             mosaic.FixedBoundingBoxWidth * expectedScale))
        # img = im.fromarray(mosaicImage, mode="I")
        # img.save(outputImagePath, mosaicImage, bits=8)

        if SaveFiles:
            OutputDir = os.path.join(self.TestOutputPath, outputMosaicPath)

            if not os.path.exists(OutputDir):
                os.makedirs(OutputDir)

            outputImagePath = os.path.join(OutputDir, mosaicBaseName + '.png')
            outputImageMaskPath = os.path.join(OutputDir,
                                               mosaicBaseName + '_mask.png')

            core.SaveImage(outputImagePath, mosaicImage)
            core.SaveImage(outputImageMaskPath, mask)
            self.assertTrue(os.path.exists(outputImagePath),
                            "OutputImage not found")

            outputMask = core.LoadImage(outputImageMaskPath)
            self.assertTrue(
                outputMask[int(outputMask.shape[0] / 2.0),
                           int(outputMask.shape[1] / 2.0)] > 0,
                "Center of assembled image mask should be non-zero")

            del mosaicImage
            del mask
        else:
            return (mosaicImage, mask)
    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)