def test_composite_subtiles_real(real_tiles_green_mask, real_tiles_red_mask, color_red, color_green, real_stitched_with_gamma): '''Ensure 1024 x 1024 image matches image rendered without tiling.''' expected = real_stitched_with_gamma inputs = [] for y in range(0, 4): for x in range(0, 4): inputs += [{ 'min': 0.006, 'max': 0.024, 'grid': (y, x), 'image': real_tiles_green_mask[y][x], 'color': color_green }, { 'min': 0, 'max': 1, 'grid': (y, x), 'image': real_tiles_red_mask[y][x], 'color': color_red }] result = composite_subtiles(inputs, (256, 256), (0, 0), (1024, 1024)) np.testing.assert_allclose(expected, np.uint8(255 * result))
def test_composite_subtiles_nonsquare(hd_tiles_green_mask, color_green, hd_stitched): '''Ensure non-square image is stitched correctly with square tiles.''' expected = ski.adjust_gamma(hd_stitched, 1 / 2.2) inputs = [] for y in range(0, 2): for x in range(0, 2): inputs += [{ 'min': 0, 'max': 1, 'grid': (y, x), 'image': hd_tiles_green_mask[y][x], 'color': color_green }] result = composite_subtiles(inputs, (1024, 1024), (0, 0), (1080, 1920)) np.testing.assert_allclose(expected, result)
def render_region(self, event, context): from minerva_db.sql.api import Client as db_client self._open_session() client = db_client(self.session) '''Render the specified region with the given settings''' uuid = event_path_param(event, 'uuid') validate_uuid(uuid) self._has_image_permission(self.user_uuid, uuid, 'Read') x = int(event_path_param(event, 'x')) y = int(event_path_param(event, 'y')) width = int(event_path_param(event, 'width')) height = int(event_path_param(event, 'height')) z = int(event_path_param(event, 'z')) t = int(event_path_param(event, 't')) # Split the channels path parameters channel_path_params = event['pathParameters']['channels'].split('/') # Read the path parameter for the channels and convert channels = [ _parse_channel_params(param) for param in channel_path_params ] # Read the optional query parameters for output shape output_width = event['queryStringParameters'].get('output-width') output_height = event['queryStringParameters'].get('output-height') output_width = int(output_width) if output_width is not None else None output_height = (int(output_height) if output_height is not None else None) # Set prefer_higher_resolution from query parameter prefer_higher_resolution = ( event['queryStringParameters'].get('prefer-higher-resolution')) prefer_higher_resolution = (prefer_higher_resolution.lower() == 'true' if prefer_higher_resolution is not None else False) # Query the shape of the full image image = client.get_image(uuid) fileset_uuid = image['data']['fileset_uuid'] fileset = client.get_fileset(fileset_uuid) if fileset['data']['complete'] is not True: raise ValueError( f'Fileset has not had metadata extracted yet: {fileset_uuid}') obj = boto3.resource('s3').Object( bucket.split(':')[-1], f'{fileset_uuid}/metadata.xml') body = obj.get()['Body'] data = body.read() stream = BytesIO(data) import xml.etree.ElementTree as ET e_root = ET.fromstring(stream.getvalue().decode('UTF-8')) e_image = e_root.find('ome:Image[@ID="Image:{}"]'.format(uuid), {'ome': OME_NS}) e_pixels = e_image.find('ome:Pixels', {'ome': OME_NS}) image_shape = (int(e_pixels.attrib['SizeX']), int(e_pixels.attrib['SizeY'])) # Query the number of levels available level_count = image['data']['pyramid_levels'] # Create shape tuples tile_shape = (1024, 1024) target_shape = (height, width) # Get the optimum level of the pyramid from which to use tiles try: output_max = max( [d for d in (output_height, output_width) if d is not None]) level = render.get_optimum_pyramid_level(image_shape, level_count, output_max, prefer_higher_resolution) except ValueError: level = 0 # Transform origin and shape of target region into that required for # the pyramid level being used origin = render.transform_coordinates_to_level((x, y), level) shape = render.transform_coordinates_to_level(target_shape, level) # Calculate the scaling factor if output_width is not None: if output_height is not None: # Use both supplied scaling factors scaling_factor = (output_height / shape[0], output_width / shape[1]) else: # Calculate scaling factor from output_width only scaling_factor = output_width / shape[1] else: if output_height is not None: # Calcuate scaling factor from output_height only scaling_factor = output_height / shape[0] else: # No scaling scaling_factor = 1 args = [] tiles = [] for channel in channels: color = channel['color'] _id = channel['index'] _min = channel['min'] _max = channel['max'] for indices in render.select_grids(tile_shape, origin, shape): (i, j) = indices # Disallow negative tiles if i < 0 or j < 0: continue # Add to list of tiles to fetch args.append((uuid, j, i, z, t, _id, level)) # Add to list of tiles tiles.append({ 'grid': (i, j), 'color': color, 'min': _min, 'max': _max }) # Fetch raw tiles in parallel s3_tile_provider = S3TileProvider( bucket.split(':')[-1], missing_tile_callback=handle_missing_tile) try: pool = ThreadPool(len(args)) images = pool.starmap(s3_tile_provider.get_tile, args) finally: pool.close() # Update tiles dictionary with image data for image_tile, image in zip(tiles, images): image_tile['image'] = image # Blend the raw tiles composite = render.composite_subtiles(tiles, tile_shape, origin, shape) #Rescale for desired output size if scaling_factor != 1: scaled = render.scale_image_nearest_neighbor( composite, scaling_factor) else: scaled = composite # requires 0 - 255 values scaled *= 255 scaled = scaled.astype(np.uint8, copy=False) # Encode rendered image as JPG img = BytesIO() imagecodecs.imwrite(img, scaled, codec="jpg") img.seek(0) return img.read()
def test_composite_subtiles_level0( level0_tiles_green_mask, level0_tiles_red_mask, level0_tiles_magenta_mask, level0_tiles_blue_mask, level0_tiles_orange_mask, level0_tiles_cyan_mask, color_red, color_green, color_blue, color_magenta, color_cyan, color_orange, level0_stitched): '''Ensure expected rendering of multi-tile multi-channel image.''' expected = ski.adjust_gamma(level0_stitched, 1 / 2.2) result = composite_subtiles([{ 'min': 0, 'max': 1, 'grid': (0, 0), 'image': level0_tiles_green_mask[0][0], 'color': color_green }, { 'min': 0, 'max': 1, 'grid': (1, 0), 'image': level0_tiles_green_mask[1][0], 'color': color_green }, { 'min': 0, 'max': 1, 'grid': (2, 0), 'image': level0_tiles_green_mask[2][0], 'color': color_green }, { 'min': 0, 'max': 1, 'grid': (0, 0), 'image': level0_tiles_red_mask[0][0], 'color': color_red }, { 'min': 0, 'max': 1, 'grid': (1, 0), 'image': level0_tiles_red_mask[1][0], 'color': color_red }, { 'min': 0, 'max': 1, 'grid': (2, 0), 'image': level0_tiles_red_mask[2][0], 'color': color_red }, { 'min': 0, 'max': 1, 'grid': (0, 1), 'image': level0_tiles_magenta_mask[0][1], 'color': color_magenta }, { 'min': 0, 'max': 1, 'grid': (1, 1), 'image': level0_tiles_magenta_mask[1][1], 'color': color_magenta }, { 'min': 0, 'max': 1, 'grid': (2, 1), 'image': level0_tiles_magenta_mask[2][1], 'color': color_magenta }, { 'min': 0, 'max': 1, 'grid': (0, 1), 'image': level0_tiles_blue_mask[0][1], 'color': color_blue }, { 'min': 0, 'max': 1, 'grid': (1, 1), 'image': level0_tiles_blue_mask[1][1], 'color': color_blue }, { 'min': 0, 'max': 1, 'grid': (2, 1), 'image': level0_tiles_blue_mask[2][1], 'color': color_blue }, { 'min': 0, 'max': 1, 'grid': (0, 2), 'image': level0_tiles_orange_mask[0][2], 'color': color_orange }, { 'min': 0, 'max': 1, 'grid': (1, 2), 'image': level0_tiles_orange_mask[1][2], 'color': color_orange }, { 'min': 0, 'max': 1, 'grid': (2, 2), 'image': level0_tiles_orange_mask[2][2], 'color': color_orange }, { 'min': 0, 'max': 1, 'grid': (0, 2), 'image': level0_tiles_cyan_mask[0][2], 'color': color_cyan }, { 'min': 0, 'max': 1, 'grid': (1, 2), 'image': level0_tiles_cyan_mask[1][2], 'color': color_cyan }, { 'min': 0, 'max': 1, 'grid': (2, 2), 'image': level0_tiles_cyan_mask[2][2], 'color': color_cyan }], (2, 2), (0, 0), (6, 6)) np.testing.assert_allclose(expected, result)