Last active
August 21, 2018 16:00
-
-
Save thejohnhoffer/6539b0d39bf445f82b54cf0fcca72e96 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' Test to crop all tiles in a region | |
''' | |
import argparse | |
import pathlib | |
import urllib | |
import struct | |
import json | |
import sys | |
import ssl | |
import re | |
import os | |
import io | |
import skimage.io | |
import skimage.exposure | |
import numpy as np | |
from minerva_lib import crop | |
###### | |
# Omero API | |
### | |
HEADERS = { | |
'Cookie': os.environ['OME_COOKIE'], | |
} | |
ssl._create_default_https_context = ssl._create_unverified_context | |
''' Test to crop all tiles in a region | |
''' | |
import argparse | |
import pathlib | |
import urllib | |
import struct | |
import botocore | |
import boto3 | |
import json | |
import sys | |
import ssl | |
import re | |
import os | |
import skimage.io | |
import skimage.exposure | |
import numpy as np | |
from minerva_lib import crop | |
import metadata as metadata_xml | |
import xml.etree.ElementTree as ET | |
from aws_srp import AWSSRP | |
s3 = boto3.resource('s3') | |
###### | |
# Minerva API | |
### | |
ssl._create_default_https_context = ssl._create_unverified_context | |
class minerva(): | |
@staticmethod | |
def image(uuid, token, c, limit, **kwargs): | |
''' Load a single channel by pattern | |
Args: | |
uuid: Minerva image identifier | |
token: AWS Cognito Id Token | |
c: zero-based channel index | |
limit: max image pixel value | |
args: dict with following keys | |
{x, y, z, t, level} | |
Returns: | |
numpy array loaded from file | |
''' | |
def format_channel(c): | |
return f'{c},FFFFFF,0,1' | |
url = 'https://ba7xgutvbc.execute-api.' | |
url += 'us-east-1.amazonaws.com/dev/image/' | |
url += '{0}/render-tile/{x}/{y}/{z}/{t}/{l}/'.format(uuid, | |
**kwargs) | |
url += format_channel(c) | |
print(url) | |
req = urllib.request.Request(url, headers={ | |
'Authorization': token, | |
'Accept': 'image/png' | |
}) | |
try: | |
with urllib.request.urlopen(req) as f: | |
return skimage.io.imread(f)[:, :, 0] | |
except urllib.error.HTTPError as e: | |
print(e, file=sys.stderr) | |
return None | |
return None | |
@staticmethod | |
def index(uuid, token): | |
'''Find all the file paths in a range | |
Args: | |
image_id: the id of image in minerva | |
token: AWS Cognito Id Token | |
Returns: | |
indices: size in channels, times, LOD, Z, Y, X | |
tile: image tile size in pixels: y, x | |
limit: max image pixel value | |
''' | |
metadata_file = 'metadata.xml' | |
bucket = 'minerva-test-cf-common-tilebucket-yhuku9umej1s' | |
url = 'https://ba7xgutvbc.execute-api.' | |
url += f'us-east-1.amazonaws.com/dev/image/{uuid}' | |
print(url) | |
req = urllib.request.Request(url, headers={ | |
'Authorization': token | |
}) | |
try: | |
with urllib.request.urlopen(req) as f: | |
result = json.loads(f.read()) | |
prefix = result['data']['bfu_uuid'] | |
except urllib.error.HTTPError as e: | |
print(e, file=sys.stderr) | |
return | |
try: | |
print(bucket, prefix, metadata_file) | |
obj = s3.Object(bucket, f'{prefix}/{metadata_file}') | |
root_xml = obj.get()['Body'].read().decode('utf-8') | |
root = ET.fromstring(root_xml) | |
config = metadata_xml.parse_image(root, uuid) | |
except botocore.exceptions.ClientError as e: | |
if e.response['Error']['Code'] == "404": | |
print("The object does not exist.", file=sys.stderr) | |
return | |
dtype = config['meta']['pixelsType'] | |
tw, th = map(config['tile_size'].get, | |
('width', 'height')) | |
w, h, c, t, z = map(config['size'].get, | |
('width', 'height', 'c', 't', 'z')) | |
y = int(np.ceil(h / th)) | |
x = int(np.ceil(w / tw)) | |
return { | |
'limit': np.iinfo(getattr(np, dtype)).max, | |
'levels': config['levels'], | |
'tile_size': [th, tw], | |
'ctxy': [c, t, x, y], | |
} | |
class api(): | |
@staticmethod | |
def scaled_region(url, uuid, token): | |
""" Just parse the rendered_scaled_region API | |
Arguments: | |
url: "<matching OMERO.figure API>" | |
uuid: Minerva image identifier | |
token: AWS Cognito Id Token | |
Return Keywords: | |
iid: image id | |
t: integer timestep | |
z: integer z position in stack | |
max_size: maximum extent in x or y | |
origin: | |
integer [x, y] | |
shape: | |
[width, height] | |
chan: integer N channels by 1 index | |
r: float32 N channels by 2 min, max | |
c: float32 N channels by 3 red, green, blue | |
indices: size in channels, times, LOD, Z, Y, X | |
tile: image tile size in pixels: y, x | |
limit: max image pixel value | |
""" | |
url_match = re.search('render_scaled_region', url) | |
url = url[(lambda x: x.end() if x else 0)(url_match):] | |
if url[0] == '/': | |
url = url[1:] | |
def parse_channel(c): | |
cid, _min, _max, _hex = re.split('[:|$]', c) | |
hex_bytes = bytearray.fromhex(_hex) | |
return { | |
'min': int(_min), | |
'max': int(_max), | |
'shown': int(cid) > 0, | |
'cid': abs(int(cid)) - 1, | |
'color': struct.unpack('BBB', hex_bytes) | |
} | |
def parse_region(r): | |
return list(map(float, r.split(','))) | |
print(url) | |
iid, z, t = url.split('?')[0].split('/')[:3] | |
query = url.split('?')[1] | |
parameters = {} | |
# Make parameters dicitonary | |
for param in query.split('&'): | |
key, value = param.split('=') | |
parameters[key] = value | |
max_size = parameters.get('max_size', 2000) | |
channels = parameters['c'].split(',') | |
region = parameters['region'] | |
# Extract data from API | |
channels = list(map(parse_channel, channels)) | |
x, y, width, height = parse_region(region) | |
# Reformat data from API | |
chans = [c for c in channels if c['shown']] | |
shape = np.array([width, height]) | |
origin = np.array([x, y]) | |
# Make API request to interpret url | |
meta = minerva.index(uuid, token) | |
def get_range(chan): | |
r = np.array([chan['min'], chan['max']]) | |
return np.clip(r / meta['limit'], 0, 1) | |
def get_color(chan): | |
c = np.array(chan['color']) / 255 | |
return np.clip(c, 0, 1) | |
return { | |
'ctxy': meta['ctxy'], | |
'limit': meta['limit'], | |
'levels': meta['levels'], | |
'tile_size': meta['tile_size'], | |
'r': np.array([get_range(c) for c in chans]), | |
'c': np.array([get_color(c) for c in chans]), | |
'chan': np.int64([c['cid'] for c in chans]), | |
'max_size': int(max_size), | |
'origin': origin, | |
'shape': shape, | |
'iid': int(iid), | |
't': int(t), | |
'z': int(z) | |
} | |
###### | |
# Minerva API | |
### | |
def format_input(args): | |
''' Combine all parameters | |
''' | |
id_, color_, range_ = args | |
return { | |
'channel': id_, | |
'color': color_, | |
'min': range_[0], | |
'max': range_[1], | |
} | |
def do_crop(load_tile, channels, tile_size, full_origin, full_size, | |
levels=1, max_size=2000): | |
''' Interface with minerva_lib.crop | |
Args: | |
load_tile: Function to supply 2D numpy array | |
channels: List of dicts of channel rendering settings | |
tile_size: The width, height of a single tile | |
full_origin: Request's full-resolution x, y origin | |
full_size: Request's full-resolution width, height | |
levels: The number of pyramid levels | |
max_size: The maximum response width or height | |
Returns: | |
2D numpy float array of with width, height given by | |
`full_size` if `full_size <= max_size` or width, height given by | |
`full_size / 2 ** l <= max_size` for the lowest `l` meeting | |
`l < levels`. The array is a composite of all channels | |
for full or partial tiles within `full_size` from `full_origin`. | |
''' | |
level = crop.get_optimum_pyramid_level(full_size, levels, max_size) | |
crop_origin = crop.scale_by_pyramid_level(full_origin, level) | |
crop_size = crop.scale_by_pyramid_level(full_size, level) | |
print(f'Cropping 1/{level} scale') | |
image_tiles = [] | |
for channel in channels: | |
(red, green, blue) = channel['color'] | |
_id = channel['channel'] | |
_min = channel['min'] | |
_max = channel['max'] | |
for indices in crop.select_tiles(tile_size, crop_origin, crop_size): | |
(i, j) = indices | |
# Disallow negative tiles | |
if i < 0 or j < 0: | |
continue | |
# Load image from Minerva | |
image = load_tile(_id, level, i, j) | |
# Disallow empty images | |
if image is None: | |
continue | |
# Add to list of tiles | |
image_tiles.append({ | |
'min': _min, | |
'max': _max, | |
'image': image, | |
'indices': (i, j), | |
'color': (red, green, blue), | |
}) | |
return crop.stitch_tiles(image_tiles, tile_size, crop_origin, crop_size) | |
###### | |
# Entrypoint | |
### | |
def main(args): | |
""" Crop a region | |
""" | |
# Read from a configuration file at a default location | |
cmd = argparse.ArgumentParser( | |
description="Crop a region" | |
) | |
default_url = '/548111/0/0/?c=1|0:65535$FF0000' | |
default_url += '®ion=0,0,1024,1024' | |
cmd.add_argument( | |
'url', nargs='?', default=default_url, | |
help='OMERO.figure render_scaled_region url' | |
) | |
cmd.add_argument( | |
'-o', default=str(pathlib.Path.cwd()), | |
help='output directory' | |
) | |
parsed = cmd.parse_args(args) | |
out_file = str(pathlib.Path(parsed.o, 'out.png')) | |
# Set up AWS Authentication | |
try: | |
username = os.environ['MINERVA_USERNAME'] | |
except KeyError: | |
print('must have MINERVA_USERNAME in environ', file=sys.stderr) | |
return | |
try: | |
password = os.environ['MINERVA_PASSWORD'] | |
except KeyError: | |
print('must have MINERVA_PASSWORD in environ', file=sys.stderr) | |
return | |
minerva_pool = 'us-east-1_ecLW78ap5' | |
minerva_client = '7gv29ie4pak64c63frt93mv8lq' | |
uuid = '769cfb14-f583-4f22-9f48-94a24e09fd7f' | |
srp = AWSSRP(username, password, minerva_pool, minerva_client) | |
result = srp.authenticate_user() | |
token = result['AuthenticationResult']['IdToken'] | |
# Read parameters from URL and API | |
keys = api.scaled_region(parsed.url, uuid, token) | |
# Make array of channel parameters | |
inputs = zip(keys['chan'], keys['c'], keys['r']) | |
channels = map(format_input, inputs) | |
# OMERO loads the tiles | |
def ask_minerva(c, l, i, j): | |
keywords = { | |
't': 0, | |
'z': 0, | |
'l': l, | |
'x': i, | |
'y': j | |
} | |
limit = keys['limit'] | |
return minerva.image(uuid, token, c, limit, **keywords) | |
# Minerva does the cropping | |
out = do_crop(ask_minerva, channels, keys['tile_size'], | |
keys['origin'], keys['shape'], keys['levels'], | |
keys['max_size']) | |
# Write the image buffer to a file | |
try: | |
skimage.io.imsave(out_file, np.uint8(255 * out)) | |
except OSError as o_e: | |
print(o_e) | |
if __name__ == "__main__": | |
main(sys.argv[1:]) | |
class omero(): | |
@staticmethod | |
def image(c, limit, *args): | |
''' Load a single channel by pattern | |
Args: | |
c: zero-based channel index | |
limit: max image pixel value | |
args: tuple completing pattern | |
Returns: | |
numpy array loaded from file | |
''' | |
def format_channel(c): | |
api_c = c + 1 | |
selected = '{}|0:{}$000000'.format(api_c, limit) | |
filler = [str(-i) for i in range(1, api_c)] + [''] | |
return ','.join(filler) + selected | |
url = 'https://omero.hms.harvard.edu/webgateway/render_image_region/' | |
url += '{}/{}/{}/?m=g&format=tif&tile={},{},{}'.format(*args) | |
url += '&c=' + format_channel(c) | |
print(url) | |
req = urllib.request.Request(url, headers=HEADERS) | |
try: | |
with urllib.request.urlopen(req) as response: | |
f = io.BytesIO(response.read()) | |
return skimage.io.imread(f)[:, :, 0] | |
except urllib.error.HTTPError as e: | |
print(e) | |
return None | |
return None | |
@staticmethod | |
def index(image_id): | |
'''Find all the file paths in a range | |
Args: | |
image_id: the id of image in omero | |
Returns: | |
indices: size in channels, times, LOD, Z, Y, X | |
tile: image tile size in pixels: y, x | |
limit: max image pixel value | |
''' | |
config = {} | |
url = 'https://omero.hms.harvard.edu/webgateway/' | |
url += 'imgData/{}'.format(image_id) | |
print(url) | |
req = urllib.request.Request(url, headers=HEADERS) | |
with urllib.request.urlopen(req) as response: | |
config = json.loads(response.read()) | |
dtype = config['meta']['pixelsType'] | |
tw, th = map(config['tile_size'].get, | |
('width', 'height')) | |
w, h, c, t, z = map(config['size'].get, | |
('width', 'height', 'c', 't', 'z')) | |
y = int(np.ceil(h / th)) | |
x = int(np.ceil(w / tw)) | |
return { | |
'limit': np.iinfo(getattr(np, dtype)).max, | |
'levels': config['levels'], | |
'tile_size': [th, tw], | |
'ctxy': [c, t, x, y], | |
} | |
class api(): | |
@staticmethod | |
def scaled_region(url): | |
""" Just parse the rendered_scaled_region API | |
Arguments: | |
url: "<matching OMERO.figure API>" | |
Return Keywords: | |
iid: image id | |
t: integer timestep | |
z: integer z position in stack | |
max_size: maximum extent in x or y | |
origin: | |
integer [x, y] | |
shape: | |
[width, height] | |
chan: integer N channels by 1 index | |
r: float32 N channels by 2 min, max | |
c: float32 N channels by 3 red, green, blue | |
indices: size in channels, times, LOD, Z, Y, X | |
tile: image tile size in pixels: y, x | |
limit: max image pixel value | |
""" | |
url_match = re.search('render_scaled_region', url) | |
url = url[(lambda x: x.end() if x else 0)(url_match):] | |
if url[0] == '/': | |
url = url[1:] | |
def parse_channel(c): | |
cid, _min, _max, _hex = re.split('[:|$]', c) | |
hex_bytes = bytearray.fromhex(_hex) | |
return { | |
'min': int(_min), | |
'max': int(_max), | |
'shown': int(cid) > 0, | |
'cid': abs(int(cid)) - 1, | |
'color': struct.unpack('BBB', hex_bytes) | |
} | |
def parse_region(r): | |
return list(map(float, r.split(','))) | |
print(url) | |
iid, z, t = url.split('?')[0].split('/')[:3] | |
query = url.split('?')[1] | |
parameters = {} | |
# Make parameters dicitonary | |
for param in query.split('&'): | |
key, value = param.split('=') | |
parameters[key] = value | |
max_size = parameters.get('max_size', 2000) | |
channels = parameters['c'].split(',') | |
region = parameters['region'] | |
# Extract data from API | |
channels = list(map(parse_channel, channels)) | |
x, y, width, height = parse_region(region) | |
# Reformat data from API | |
chans = [c for c in channels if c['shown']] | |
shape = np.array([width, height]) | |
origin = np.array([x, y]) | |
# Make API request to interpret url | |
meta = omero.index(iid) | |
def get_range(chan): | |
r = np.array([chan['min'], chan['max']]) | |
return np.clip(r / meta['limit'], 0, 1) | |
def get_color(chan): | |
c = np.array(chan['color']) / 255 | |
return np.clip(c, 0, 1) | |
return { | |
'ctxy': meta['ctxy'], | |
'limit': meta['limit'], | |
'levels': meta['levels'], | |
'tile_size': meta['tile_size'], | |
'r': np.array([get_range(c) for c in chans]), | |
'c': np.array([get_color(c) for c in chans]), | |
'chan': np.int64([c['cid'] for c in chans]), | |
'max_size': int(max_size), | |
'origin': origin, | |
'shape': shape, | |
'iid': int(iid), | |
't': int(t), | |
'z': int(z) | |
} | |
###### | |
# Minerva API | |
### | |
def format_input(args): | |
''' Combine all parameters | |
''' | |
id_, color_, range_ = args | |
return { | |
'channel': id_, | |
'color': color_, | |
'min': range_[0], | |
'max': range_[1], | |
} | |
def do_crop(load_tile, channels, tile_size, full_origin, full_size, | |
levels=1, max_size=2000): | |
''' Interface with minerva_lib.crop | |
Args: | |
load_tile: Function to supply 2D numpy array | |
channels: List of dicts of channel rendering settings | |
tile_size: The width, height of a single tile | |
full_origin: Request's full-resolution x, y origin | |
full_size: Request's full-resolution width, height | |
levels: The number of pyramid levels | |
max_size: The maximum response width or height | |
Returns: | |
2D numpy float array of with width, height given by | |
`full_size` if `full_size <= max_size` or width, height given by | |
`full_size / 2 ** l <= max_size` for the lowest `l` meeting | |
`l < levels`. The array is a composite of all channels | |
for full or partial tiles within `full_size` from `full_origin`. | |
''' | |
level = crop.get_optimum_pyramid_level(full_size, levels, max_size) | |
crop_origin = crop.scale_by_pyramid_level(full_origin, level) | |
crop_size = crop.scale_by_pyramid_level(full_size, level) | |
print(f'Cropping 1/{level} scale') | |
image_tiles = [] | |
for channel in channels: | |
(red, green, blue) = channel['color'] | |
_id = channel['channel'] | |
_min = channel['min'] | |
_max = channel['max'] | |
for indices in crop.select_tiles(tile_size, crop_origin, crop_size): | |
(i, j) = indices | |
# Disallow negative tiles | |
if i < 0 or j < 0: | |
continue | |
# Load image from Omero | |
image = load_tile(_id, level, i, j) | |
# Disallow empty images | |
if image is None: | |
continue | |
# Add to list of tiles | |
image_tiles.append({ | |
'min': _min, | |
'max': _max, | |
'channel': _id, | |
'image': image, | |
'indices': (i, j), | |
'color': (red, green, blue), | |
}) | |
return crop.stitch_tiles(image_tiles, tile_size, crop_origin, crop_size) | |
###### | |
# Entrypoint | |
### | |
def main(args): | |
""" Crop a region | |
""" | |
# Read from a configuration file at a default location | |
cmd = argparse.ArgumentParser( | |
description="Crop a region" | |
) | |
default_url = '/548111/0/0/?c=1|0:65535$FF0000,3|0:65535$0000FF' | |
default_url += '®ion=-8160,-16416,48960,48960' | |
cmd.add_argument( | |
'url', nargs='?', default=default_url, | |
help='OMERO.figure render_scaled_region url' | |
) | |
cmd.add_argument( | |
'-o', default=str(pathlib.Path.cwd()), | |
help='output directory' | |
) | |
parsed = cmd.parse_args(args) | |
out_file = str(pathlib.Path(parsed.o, 'out.png')) | |
# Read parameters from URL and API | |
keys = api.scaled_region(parsed.url) | |
# Make array of channel parameters | |
inputs = zip(keys['chan'], keys['c'], keys['r']) | |
channels = map(format_input, inputs) | |
# OMERO loads the tiles | |
def ask_omero(c, l, i, j): | |
return omero.image(c, keys['limit'], keys['iid'], | |
keys['z'], keys['t'], l, i, j) | |
# Minerva does the cropping | |
out = do_crop(ask_omero, channels, keys['tile_size'], | |
keys['origin'], keys['shape'], keys['levels'], | |
keys['max_size']) | |
# Write the image buffer to a file | |
try: | |
skimage.io.imsave(out_file, np.uint8(255 * out)) | |
except OSError as o_e: | |
print(o_e) | |
if __name__ == "__main__": | |
main(sys.argv[1:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' Create imgData result from metadata.xml | |
''' | |
from functools import reduce | |
import xml.etree.ElementTree as ET | |
import json | |
XSD = { | |
'ome': 'http://www.openmicroscopy.org/Schemas/OME/2016-06' | |
} | |
def make_channel(channel, keys): | |
''' Create a channel dictionary for imgData request | |
''' | |
chan = channel.attrib | |
id_label = chan['ID'] | |
emission = float(chan['EmissionWavelength']) | |
return { | |
'label': id_label, | |
'window': { | |
'min': keys['min'], | |
'start': keys['min'], | |
'end': keys['max'], | |
'max': keys['max'], | |
}, | |
'emissionWave': emission, | |
'reverseIntensity': False, | |
'inverted': False, | |
'coefficient': 1, | |
'family': 'linear', | |
'color': 'FFFFFF', | |
'active': True, | |
} | |
def factor_pairs(count): | |
''' Yield factor pairs, sorted by smallest difference | |
''' | |
for i in range(int(count ** 0.5), 0, -1): | |
if count % i == 0: | |
yield i, count // i | |
yield 0, 0 | |
def make_grid(count, width, height): | |
''' Spatially arrange images of given shape | |
''' | |
border = 2 | |
gridx, gridy = next(factor_pairs(count)) | |
return { | |
'gridx': gridx, | |
'gridy': gridy, | |
'border': border, | |
'width': border + gridx * (border + width), | |
'height': border + gridy * (border + height), | |
} | |
def make_scale(scales, i): | |
''' Assign i to ith division by 2 | |
''' | |
scales[str(i)] = 1.0 / (2 ** i) | |
return scales | |
def get_image(uuid): | |
''' Simulate database result | |
''' | |
return { | |
'pyramid_levels': 1, | |
'uuid': uuid | |
} | |
def get_uuid(attributes): | |
''' Get the UUID from an XML attributes dictionary | |
''' | |
return attributes['ID'].split(':').pop() | |
def make_meta(props): | |
''' Make metadata from XML attributes | |
''' | |
img = props['Image'] | |
pix = props['Pixels'] | |
return { | |
'imageId': get_uuid(img), | |
'imageName': img['Name'], | |
'pixelsType': pix['Type'], | |
'imageAuthor': 'Minerva', | |
'projectDescription': '', | |
'datasetDescription': '', | |
'imageDescription': '', | |
'imageTimestamp': '', | |
'wellSampleId': '', | |
'datasetName': '', | |
'projectName': '', | |
'projectId': '', | |
'datasetId': '', | |
'wellId': '', | |
} | |
def make_image(image, props, channels, keys): | |
''' Make imgData dictionary from attriutes | |
''' | |
pix = props['Pixels'] | |
plane = props['Plane'] | |
size = { | |
'c': int(pix['SizeC']), | |
't': int(pix['SizeT']), | |
'z': int(pix['SizeZ']), | |
'width': int(pix['SizeX']), | |
'height': int(pix['SizeY']), | |
} | |
levels = int(image['pyramid_levels']) | |
scales = reduce(make_scale, range(levels), {}) | |
return { | |
'size': size, | |
'levels': levels, | |
'channels': channels, | |
'meta': make_meta(props), | |
'zoomLevelScaling': scales, | |
'pixel_range': [ | |
keys['min'], | |
keys['max'] | |
], | |
'pixel_size': { | |
'x': float(pix['PhysicalSizeX']), | |
'y': float(pix['PhysicalSizeY']), | |
'z': float(pix['PhysicalSizeZ']), | |
}, | |
'deltaT': [ | |
float(plane['DeltaT']) | |
], | |
'split_channel': { | |
'g': make_grid(size['c'], size['width'], size['height']), | |
'c': make_grid(size['c'] + 1, size['width'], size['height']) | |
}, | |
'id': image['uuid'], | |
'tile_size': { | |
'width': 1024, | |
'height': 1024, | |
}, | |
'init_zoom': 0, | |
'tiles': True, | |
'interpolate': True, | |
'perms': { | |
'canAnnotate': False, | |
'canDelete': False, | |
'canEdit': False, | |
'canLink': False, | |
}, | |
'rdefs': { | |
'defaultT': 0, | |
'defaultZ': 0, | |
'model': 'color', | |
'invertAxis': False, | |
'projection': 'normal', | |
}, | |
} | |
def parse_image(ome, image_uuid): | |
''' Make imgData dictionary from metadata / db | |
''' | |
image_id = f'@ID="Image:{image_uuid}"' | |
e_image = ome.find(f'ome:Image[{image_id}]', XSD) | |
if not e_image: | |
return {} | |
e_pixels = e_image.find('ome:Pixels', XSD) | |
e_plane = e_pixels.find('ome:Plane', XSD) | |
e_channels = e_pixels.findall('ome:Channel', XSD) | |
image = get_image(image_uuid) | |
props = { | |
'Pixels': e_pixels.attrib, | |
'Plane': e_plane.attrib, | |
'Image': e_image.attrib, | |
} | |
keys = { | |
'min': 0, | |
'max': 2 ** int(props['Pixels']['SignificantBits']) | |
} | |
channels = [make_channel(c, keys) for c in e_channels] | |
return make_image(image, props, channels, keys) | |
def main(): | |
''' write imgData json file | |
''' | |
image_uuid = 'b9e36f16-75a3-4a11-be88-ed838c3b9141' | |
metadata_file = 'metadata.xml' | |
imgdata_file = 'imgdata.json' | |
root = ET.parse(metadata_file).getroot() | |
imgdata = parse_image(root, image_uuid) | |
with open(imgdata_file, 'w') as idf: | |
json.dump(imgdata, idf) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
git+git://github.com/thejohnhoffer/minerva-lib-python@e7d8edffa86f0925c6bf164e373072aaaac239d5#egg=minerva-lib | |
scikit-image>=0.13.1 | |
numpy>=1.14.3 | |
boto>=2.48.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Installation
pip install -r requirements.txt
The most important function
Here is what it does:
crop.get_optimum_pyramid_level
to getlevel
level
, It callscrop.scale_by_pyramid_level
to scale the origin and sizecrop.select_tiles
image_tiles
crop.stitch_tiles
This function is
do_crop
on line 215.Usage
Default
<URL>
is:/render_scaled_region/548111/0/0/?c=1|0:65535$FF0000®ion=0,0,1024,1024
To combine channels 0 and 2, one would use a url like this:
/render_scaled_region/548111/0/0/?c=1|0:65535$FF0000,3|0:65535$0000FF®ion=0,0,1024,1024
Default
<OUTPUT/FOLDER>
is the current directory:The output is always called
out.png