Skip to content

Commit b2187ea

Browse files
authored
Merge pull request #10 from NL-BioImaging/dev-register2
Updated register script, added pixel size for Omero import
2 parents 96960d7 + 94298c0 commit b2187ea

2 files changed

Lines changed: 257 additions & 33 deletions

File tree

biomero_importer/utils/importer.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -489,13 +489,15 @@ def import_to_omero(self, conn, file_path, target_id, target_type, uuid, transfe
489489
@connection
490490
def import_zarr(self, conn, uri, target, target_by_name=None, endpoint=None, nosignrequest=False):
491491
# Using https://github.com/BioNGFF/omero-import-utils/blob/main/metadata/register.py
492-
from .register import load_attrs, register_image, register_plate, link_to_target, validate_endpoint
492+
from .register import (load_attrs, register_image, register_plate, link_to_target, validate_endpoint,
493+
get_omexml_bytes, full_import, parse_image_metadata, set_rendering_settings,
494+
set_external_info)
493495
import zarr
494496
from types import SimpleNamespace
495497

496498
file_title = os.path.splitext(os.path.basename(uri))[0].rstrip('.ome')
497499
args = SimpleNamespace(uri=uri, endpoint=endpoint, name=file_title,
498-
nosignrequest=nosignrequest, target=target, target_by_name=target_by_name)
500+
nosignrequest=nosignrequest, target=str(target), target_by_name=target_by_name)
499501

500502
# --- start copy from register.main() ---
501503

@@ -524,16 +526,43 @@ def import_zarr(self, conn, uri, target, target_by_name=None, endpoint=None, nos
524526
else:
525527
if "bioformats2raw.layout" in zattrs and zattrs["bioformats2raw.layout"] == 3:
526528
print("Registering: bioformats2raw.layout")
527-
series = 0
528-
series_exists = True
529-
while series_exists:
530-
try:
531-
print("Checking for series:", series)
532-
obj = register_image(conn, store, args, None, image_path=str(series))
533-
objs.append(obj)
534-
except FileNotFoundError:
535-
series_exists = False
536-
series += 1
529+
zarr_name = args.uri.rstrip("/").split("/")[-1]
530+
if args.name:
531+
zarr_name = args.name
532+
# try to load OME/METADATA.ome.xml
533+
omexml_bytes = get_omexml_bytes(store)
534+
if omexml_bytes is not None:
535+
print("Importing OME/METADATA.ome.xml")
536+
rsp = full_import(conn.c, omexml_bytes, args.wait)
537+
for series, p in enumerate(rsp.pixels):
538+
# set external info. NB: order of pixels MUST match the series 0, 1, 2...
539+
image = conn.getObject("Image", p.image.id.val)
540+
image_path = str(series)
541+
image_attrs = load_attrs(store, image_path)
542+
# pixels_type is only used if we have *incomplete* `omero` metadata
543+
sizes, pixel_size, pixels_type = parse_image_metadata(store, image_attrs, image_path)
544+
rnd_def = set_rendering_settings(conn, image, image_attrs, pixels_type)
545+
if rnd_def is not None:
546+
conn.getUpdateService().saveAndReturnObject(rnd_def)
547+
set_external_info(image._obj, args, image_path=image_path)
548+
# default name is METADATA.ome.xml [series], based on clientPath?
549+
new_name = image.name.replace("METADATA.ome.xml", zarr_name)
550+
print("Imported Image:", image.id, new_name)
551+
image.setName(new_name)
552+
image.save() # save Name and ExternalInfo
553+
objs.append(image)
554+
else:
555+
print("OME/METADATA.ome.xml Not Found")
556+
series = 0
557+
series_exists = True
558+
while series_exists:
559+
try:
560+
print("Checking for series:", series)
561+
obj = register_image(conn, store, args, None, image_path=str(series))
562+
objs.append(obj)
563+
except FileNotFoundError:
564+
series_exists = False
565+
series += 1
537566
else:
538567
print("Registering: Image")
539568
objs = [register_image(conn, store, args, zattrs)]

biomero_importer/utils/register.py

Lines changed: 216 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@
2121

2222
from urllib.parse import urlsplit
2323
import zarr
24+
from zarr.core.buffer import default_buffer_prototype
25+
from zarr.core.sync import sync
2426

2527
import argparse
28+
import locale
29+
import platform
30+
import sys
31+
import tempfile
2632

2733
from numpy import iinfo, finfo
2834

2935
# from getpass import getpass
3036

3137
from omero.cli import cli_login
3238
from omero.gateway import BlitzGateway
33-
from omero.gateway import OMERO_NUMPY_TYPES
3439

3540
import omero
3641

@@ -41,6 +46,12 @@
4146

4247
from omero.model import ExternalInfoI
4348
from omero.rtypes import rbool, rdouble, rint, rlong, rstring, rtime
49+
from omero.model import ChecksumAlgorithmI
50+
from omero.model import LengthI
51+
from omero.model import NamedValue
52+
from omero.model.enums import ChecksumAlgorithmSHA1160
53+
from omero_version import omero_version
54+
from omero.callbacks import CmdCallbackI
4455

4556
AWS_DEFAULT_ENDPOINT = "s3.us-east-1.amazonaws.com"
4657

@@ -58,6 +69,123 @@
5869
'complex_': PixelsTypecomplex,
5970
'complex64': PixelsTypecomplex}
6071

72+
73+
def get_omexml_bytes(store):
74+
# get() is async. Need to sync to get the bytes
75+
rsp = store.get("OME/METADATA.ome.xml", prototype=default_buffer_prototype())
76+
result = sync(rsp)
77+
if result is None:
78+
return None
79+
return result.to_bytes()
80+
81+
82+
def create_fileset():
83+
"""Create a new Fileset with single OME XML file."""
84+
fileset = omero.model.FilesetI()
85+
entry = omero.model.FilesetEntryI()
86+
# NB: If the clientPath includes .zarr, Bio-Formats tries to import zarr group
87+
entry.setClientPath(rstring("OME/METADATA.ome.xml"))
88+
fileset.addFilesetEntry(entry)
89+
90+
# Fill version info
91+
system, node, release, version, machine, processor = platform.uname()
92+
93+
client_version_info = [
94+
NamedValue('omero.version', omero_version),
95+
NamedValue('os.name', system),
96+
NamedValue('os.version', release),
97+
NamedValue('os.architecture', machine)
98+
]
99+
try:
100+
client_version_info.append(
101+
NamedValue('locale', locale.getlocale()[0]))
102+
except:
103+
pass
104+
105+
upload = omero.model.UploadJobI()
106+
upload.setVersionInfo(client_version_info)
107+
fileset.linkJob(upload)
108+
return fileset
109+
110+
111+
def create_settings():
112+
"""Create ImportSettings and set some values."""
113+
settings = omero.grid.ImportSettings()
114+
# can't create thumbnails on import since ExternalInfo is not set yet
115+
settings.doThumbnails = rbool(False)
116+
settings.noStatsInfo = rbool(False)
117+
settings.userSpecifiedTarget = None
118+
settings.userSpecifiedName = None
119+
settings.userSpecifiedDescription = None
120+
settings.userSpecifiedAnnotationList = None
121+
settings.userSpecifiedPixels = None
122+
settings.checksumAlgorithm = ChecksumAlgorithmI()
123+
s = rstring(ChecksumAlgorithmSHA1160)
124+
settings.checksumAlgorithm.value = s
125+
return settings
126+
127+
128+
def upload_file(proc, omexml_bytes, client):
129+
"""Upload files to OMERO from local filesystem."""
130+
ret_val = []
131+
i = 0
132+
rfs = proc.getUploader(i)
133+
try:
134+
offset = 0
135+
# rfs.write([], offset, 0) # Touch
136+
# Write the OME XML file
137+
rfs.write(omexml_bytes, offset, len(omexml_bytes))
138+
139+
# create temp file for sha1
140+
with tempfile.NamedTemporaryFile(delete_on_close=False) as fp:
141+
fp.write(omexml_bytes)
142+
fp.close()
143+
ret_val.append(client.sha1(fp.name))
144+
finally:
145+
rfs.close()
146+
return ret_val
147+
148+
149+
def assert_import(client, proc, omexml_bytes, wait):
150+
"""Wait and check that we imported an image."""
151+
hashes = upload_file(proc, omexml_bytes, client)
152+
# print ('Hashes:\n %s' % '\n '.join(hashes))
153+
handle = proc.verifyUpload(hashes)
154+
cb = CmdCallbackI(client, handle)
155+
156+
# https://github.com/openmicroscopy/openmicroscopy/blob/v5.4.9/components/blitz/src/ome/formats/importer/ImportLibrary.java#L631
157+
if wait == 0:
158+
cb.close(False)
159+
return None
160+
if wait < 0:
161+
while not cb.block(2000):
162+
sys.stdout.write('.')
163+
sys.stdout.flush()
164+
sys.stdout.write('\n')
165+
else:
166+
cb.loop(wait, 1000)
167+
rsp = cb.getResponse()
168+
if isinstance(rsp, omero.cmd.ERR):
169+
raise Exception(rsp)
170+
assert len(rsp.pixels) > 0
171+
return rsp
172+
173+
174+
def full_import(client, omexml_bytes, wait=-1):
175+
"""Re-usable method for a basic import."""
176+
mrepo = client.getManagedRepository()
177+
178+
fileset = create_fileset()
179+
settings = create_settings()
180+
181+
proc = mrepo.importFileset(fileset, settings)
182+
try:
183+
# do the upload and trigger the import
184+
return assert_import(client, proc, omexml_bytes, wait)
185+
finally:
186+
proc.close()
187+
188+
61189
def format_s3_uri(uri, endpoint):
62190
'''
63191
Combine endpoint and uri
@@ -109,8 +237,17 @@ def parse_image_metadata(store, img_attrs, image_path=None):
109237
else:
110238
sizes[axis["name"]] = size
111239

240+
pixel_size = {}
241+
transforms = multiscale_attrs["datasets"][0]["coordinateTransformations"]
242+
for transform in transforms:
243+
if transform["type"] == "scale":
244+
scale = transform["scale"]
245+
pixel_size = {axis["name"]: (pixel_size, axis.get("unit", "")) for axis, pixel_size
246+
in zip(axes, scale) if axis["name"] in "xyz"}
247+
break
248+
112249
pixels_type = array_data.dtype.name
113-
return sizes, pixels_type
250+
return sizes, pixel_size, pixels_type
114251

115252

116253
def create_image(conn, store, image_attrs, object_name, families, models, args, image_path=None):
@@ -119,7 +256,7 @@ def create_image(conn, store, image_attrs, object_name, families, models, args,
119256
'''
120257
query_service = conn.getQueryService()
121258
pixels_service = conn.getPixelsService()
122-
sizes, pixels_type = parse_image_metadata(store, image_attrs, image_path)
259+
sizes, pixel_size, pixels_type = parse_image_metadata(store, image_attrs, image_path)
123260
size_t = sizes.get("t", 1)
124261
size_z = sizes.get("z", 1)
125262
size_x = sizes.get("x", 1)
@@ -133,11 +270,10 @@ def create_image(conn, store, image_attrs, object_name, families, models, args,
133270

134271
rnd_def = None
135272
image = conn.getObject("Image", iid)
136-
omero_attrs = image_attrs.get('omero', None)
137-
if omero_attrs is not None:
138-
set_channel_names(conn, iid, omero_attrs)
139-
# Check rendering settings
140-
rnd_def = set_rendering_settings(omero_attrs, pixels_type, image.getPixelsId(), families, models)
273+
# Set rendering settings and channel names if omero_attrs is provided
274+
rnd_def = set_rendering_settings(conn, image, image_attrs, pixels_type, families, models)
275+
276+
set_pixel_size(image, pixel_size)
141277

142278
img_obj = image._obj
143279
set_external_info(img_obj, args, image_path)
@@ -174,10 +310,22 @@ def set_channel_names(conn, iid, omero_attrs):
174310
conn.setChannelNames("Image", [iid], nameDict)
175311

176312

177-
def set_rendering_settings(omero_info, pixels_type, pixels_id, families, models):
313+
def set_rendering_settings(conn, image, image_attrs, pixels_type, families=None, models=None):
178314
'''
179315
Extract the rendering settings and the channels information
180316
'''
317+
omero_info = image_attrs.get('omero', None)
318+
if omero_info is None:
319+
return None
320+
set_channel_names(conn, image.id, omero_info)
321+
322+
if families is None:
323+
families = load_families(conn.getQueryService())
324+
if models is None:
325+
models = load_models(conn.getQueryService())
326+
327+
pixels_id = image.getPrimaryPixels().getId()
328+
181329
if omero_info is None:
182330
return
183331
rdefs = omero_info.get('rdefs', None)
@@ -255,6 +403,16 @@ def set_rendering_settings(omero_info, pixels_type, pixels_id, families, models)
255403
return rnd_def
256404

257405

406+
def set_pixel_size(image, pixel_size):
407+
pixels = image.getPrimaryPixels()._obj
408+
if "x" in pixel_size:
409+
pixels.setPhysicalSizeX(LengthI(pixel_size["x"][0], pixel_size["x"][1].upper()))
410+
if "y" in pixel_size:
411+
pixels.setPhysicalSizeY(LengthI(pixel_size["y"][0], pixel_size["y"][1].upper()))
412+
if "z" in pixel_size:
413+
pixels.setPhysicalSizeZ(LengthI(pixel_size["z"][0], pixel_size["z"][1].upper()))
414+
415+
258416
def load_families(query_service):
259417
ctx = {'omero.group': '-1'}
260418
return query_service.findAllByQuery('select f from Family as f', None, ctx)
@@ -498,9 +656,15 @@ def link_to_target(args, conn, obj):
498656

499657
if args.target:
500658
if is_plate:
501-
target = conn.getObject("Screen", attributes={'id': int(args.target)})
659+
screen_id = args.target
660+
if screen_id.startswith("Screen:"):
661+
screen_id = screen_id.split(":")[1]
662+
target = conn.getObject("Screen", attributes={'id': int(screen_id)})
502663
else:
503-
target = conn.getObject("Dataset", attributes={'id': int(args.target)})
664+
dataset_id = args.target
665+
if dataset_id.startswith("Dataset:"):
666+
dataset_id = dataset_id.split(":")[1]
667+
target = conn.getObject("Dataset", attributes={'id': int(dataset_id)})
504668
else:
505669
if is_plate:
506670
target = conn.getObject("Screen", attributes={'name': args.target_by_name})
@@ -532,6 +696,10 @@ def main():
532696
parser.add_argument("--nosignrequest", required=False, action='store_true', help="Indicate to sign anonymously")
533697
parser.add_argument("--target", required=False, type=str, help="The id of the target (dataset/screen)")
534698
parser.add_argument("--target-by-name", required=False, type=str, help="The name of the target (dataset/screen)")
699+
parser.add_argument('--wait', type=int, default=-1, help=(
700+
'Wait for this number of seconds for each import to complete. '
701+
'0: return immediately, -1: wait indefinitely (default). '
702+
'Only applies when importing OME/METADATA.ome.xml.'))
535703

536704
args = parser.parse_args()
537705

@@ -565,16 +733,43 @@ def main():
565733
else:
566734
if "bioformats2raw.layout" in zattrs and zattrs["bioformats2raw.layout"] == 3:
567735
print("Registering: bioformats2raw.layout")
568-
series = 0
569-
series_exists = True
570-
while series_exists:
571-
try:
572-
print("Checking for series:", series)
573-
obj = register_image(conn, store, args, None, image_path=str(series))
574-
objs.append(obj)
575-
except FileNotFoundError:
576-
series_exists = False
577-
series += 1
736+
zarr_name = args.uri.rstrip("/").split("/")[-1]
737+
if args.name:
738+
zarr_name = args.name
739+
# try to load OME/METADATA.ome.xml
740+
omexml_bytes = get_omexml_bytes(store)
741+
if omexml_bytes is not None:
742+
print("Importing OME/METADATA.ome.xml")
743+
rsp = full_import(conn.c, omexml_bytes, args.wait)
744+
for series, p in enumerate(rsp.pixels):
745+
# set external info. NB: order of pixels MUST match the series 0, 1, 2...
746+
image = conn.getObject("Image", p.image.id.val)
747+
image_path = str(series)
748+
image_attrs = load_attrs(store, image_path)
749+
# pixels_type is only used if we have *incomplete* `omero` metadata
750+
sizes, pixel_size, pixels_type = parse_image_metadata(store, image_attrs, image_path)
751+
rnd_def = set_rendering_settings(conn, image, image_attrs, pixels_type)
752+
if rnd_def is not None:
753+
conn.getUpdateService().saveAndReturnObject(rnd_def)
754+
set_external_info(image._obj, args, image_path=image_path)
755+
# default name is METADATA.ome.xml [series], based on clientPath?
756+
new_name = image.name.replace("METADATA.ome.xml", zarr_name)
757+
print("Imported Image:", image.id, new_name)
758+
image.setName(new_name)
759+
image.save() # save Name and ExternalInfo
760+
objs.append(image)
761+
else:
762+
print("OME/METADATA.ome.xml Not Found")
763+
series = 0
764+
series_exists = True
765+
while series_exists:
766+
try:
767+
print("Checking for series:", series)
768+
obj = register_image(conn, store, args, None, image_path=str(series))
769+
objs.append(obj)
770+
except FileNotFoundError:
771+
series_exists = False
772+
series += 1
578773
else:
579774
print("Registering: Image")
580775
objs = [register_image(conn, store, args, zattrs)]

0 commit comments

Comments
 (0)