2121
2222from urllib .parse import urlsplit
2323import zarr
24+ from zarr .core .buffer import default_buffer_prototype
25+ from zarr .core .sync import sync
2426
2527import argparse
28+ import locale
29+ import platform
30+ import sys
31+ import tempfile
2632
2733from numpy import iinfo , finfo
2834
2935# from getpass import getpass
3036
3137from omero .cli import cli_login
3238from omero .gateway import BlitzGateway
33- from omero .gateway import OMERO_NUMPY_TYPES
3439
3540import omero
3641
4146
4247from omero .model import ExternalInfoI
4348from 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
4556AWS_DEFAULT_ENDPOINT = "s3.us-east-1.amazonaws.com"
4657
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+
61189def 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
116253def 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+
258416def 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