@@ -289,3 +289,243 @@ def write_bf_memoryfile(path_to_file):
289289 reader = Memoizer (ImageReader ())
290290 reader .setId (path_to_file )
291291 reader .close ()
292+
293+
294+ def get_metadata_from_file (path_to_image ):
295+ """Extract metadata from an image file using Bio-Formats.
296+
297+ This function reads an image file using the Bio-Formats library and extracts
298+ various metadata properties including physical dimensions, pixel dimensions,
299+ and other image characteristics.
300+
301+ Parameters
302+ ----------
303+ path_to_image : str or pathlib.Path
304+ Path to the image file from which metadata should be extracted.
305+
306+ Returns
307+ -------
308+ dict
309+ A dictionary containing the following metadata:
310+
311+ {
312+ unit_width : float, # physical width of a pixel
313+ unit_height : float, # physical height of a pixel
314+ unit_depth : float, # physical depth of a voxel
315+ pixel_width : int, # width of the image in pixels
316+ pixel_height : int, # height of the image in pixels
317+ slice_count : int, # number of Z-slices
318+ channel_count : int, # number of channels
319+ timepoints_count : int, # number of timepoints
320+ dimension_order : str, # order of dimensions, e.g. "XYZCT"
321+ pixel_type : str, # data type of the pixel values
322+ }
323+ """
324+ reader = ImageReader ()
325+ ome_meta = MetadataTools .createOMEXMLMetadata ()
326+ reader .setMetadataStore (ome_meta )
327+ reader .setId (str (path_to_image ))
328+
329+ phys_size_x = ome_meta .getPixelsPhysicalSizeX (0 )
330+ phys_size_y = ome_meta .getPixelsPhysicalSizeY (0 )
331+ phys_size_z = ome_meta .getPixelsPhysicalSizeZ (0 )
332+ pixel_size_x = ome_meta .getPixelsSizeX (0 )
333+ pixel_size_y = ome_meta .getPixelsSizeY (0 )
334+ pixel_size_z = ome_meta .getPixelsSizeZ (0 )
335+ channel_count = ome_meta .getPixelsSizeC (0 )
336+ timepoints_count = ome_meta .getPixelsSizeT (0 )
337+ dimension_order = ome_meta .getPixelsDimensionOrder (0 )
338+ pixel_type = ome_meta .getPixelsType (0 )
339+
340+ image_calibration = {
341+ "unit_width" : phys_size_x .value (),
342+ "unit_height" : phys_size_y .value (),
343+ "unit_depth" : phys_size_z .value (),
344+ "pixel_width" : pixel_size_x .getNumberValue (),
345+ "pixel_height" : pixel_size_y .getNumberValue (),
346+ "slice_count" : pixel_size_z .getNumberValue (),
347+ "channel_count" : channel_count .getNumberValue (),
348+ "timepoints_count" : timepoints_count .getNumberValue (),
349+ "dimension_order" : dimension_order ,
350+ "pixel_type" : pixel_type ,
351+ }
352+
353+ reader .close ()
354+
355+ return image_calibration
356+
357+
358+ def get_stage_coords (source , filenames ):
359+ """Get stage coordinates and calibration for a given list of images.
360+
361+ Parameters
362+ ----------
363+ source : str
364+ Path to the images.
365+ filenames : list of str
366+ List of images filenames.
367+
368+ Returns
369+ -------
370+ dict
371+
372+ {
373+ dimensions : int, # number of dimensions (2D or 3D)
374+ stage_coordinates_x : list, # absolute stage x-coordinated
375+ stage_coordinates_y : list, # absolute stage y-coordinated
376+ stage_coordinates_z : list, # absolute stage z-coordinated
377+ relative_coordinates_x : list, # relative stage x-coordinates in px
378+ relative_coordinates_y : list, # relative stage y-coordinates in px
379+ relative_coordinates_z : list, # relative stage z-coordinates in px
380+ image_calibration : list, # x,y,z image calibration in unit/px
381+ calibration_unit : str, # image calibration unit
382+ image_dimensions_czt : list, # number of images in dimensions c,z,t
383+ series_names : list of str, # names of all series in the files
384+ max_size : list of int, # max size (x/y/z) across all files
385+ }
386+ """
387+
388+ # open an array to store the abosolute stage coordinates from metadata
389+ stage_coordinates_x = []
390+ stage_coordinates_y = []
391+ stage_coordinates_z = []
392+ series_names = []
393+
394+ for counter , image in enumerate (filenames ):
395+ # parse metadata
396+ reader = ImageReader ()
397+ reader .setFlattenedResolutions (False )
398+ omeMeta = MetadataTools .createOMEXMLMetadata ()
399+ reader .setMetadataStore (omeMeta )
400+ reader .setId (source + str (image ))
401+ series_count = reader .getSeriesCount ()
402+
403+ # get hyperstack dimensions from the first image
404+ if counter == 0 :
405+ frame_size_x = reader .getSizeX ()
406+ frame_size_y = reader .getSizeY ()
407+ frame_size_z = reader .getSizeZ ()
408+ frame_size_c = reader .getSizeC ()
409+ frame_size_t = reader .getSizeT ()
410+
411+ # note the dimensions
412+ if frame_size_z == 1 :
413+ dimensions = 2
414+ if frame_size_z > 1 :
415+ dimensions = 3
416+
417+ # get the physical calibration for the first image series
418+ physSizeX = omeMeta .getPixelsPhysicalSizeX (0 )
419+ physSizeY = omeMeta .getPixelsPhysicalSizeY (0 )
420+ physSizeZ = omeMeta .getPixelsPhysicalSizeZ (0 )
421+
422+ # workaround to get the z-interval if physSizeZ.value() returns None.
423+ z_interval = 1
424+ if physSizeZ is not None :
425+ z_interval = physSizeZ .value ()
426+
427+ if frame_size_z > 1 and physSizeZ is None :
428+ log .debug ("no z calibration found, trying to recover" )
429+ first_plane = omeMeta .getPlanePositionZ (0 , 0 )
430+ next_plane_imagenumber = frame_size_c + frame_size_t - 1
431+ second_plane = omeMeta .getPlanePositionZ (0 , next_plane_imagenumber )
432+ z_interval = abs (abs (first_plane .value ()) - abs (second_plane .value ()))
433+ log .debug ("z-interval seems to be: " + str (z_interval ))
434+
435+ # create an image calibration
436+ image_calibration = [
437+ physSizeX .value (),
438+ physSizeY .value (),
439+ z_interval ,
440+ ]
441+ calibration_unit = physSizeX .unit ().getSymbol ()
442+ image_dimensions_czt = [
443+ frame_size_c ,
444+ frame_size_z ,
445+ frame_size_t ,
446+ ]
447+
448+ reader .close ()
449+
450+ for series in range (series_count ):
451+ if omeMeta .getImageName (series ) == "macro image" :
452+ continue
453+
454+ if series_count > 1 and not str (image ).endswith (".vsi" ):
455+ series_names .append (omeMeta .getImageName (series ))
456+ else :
457+ series_names .append (str (image ))
458+ # get the plane position in calibrated units
459+ current_position_x = omeMeta .getPlanePositionX (series , 0 )
460+ current_position_y = omeMeta .getPlanePositionY (series , 0 )
461+ current_position_z = omeMeta .getPlanePositionZ (series , 0 )
462+
463+ physSizeX_max = (
464+ physSizeX .value ()
465+ if physSizeX .value () >= omeMeta .getPixelsPhysicalSizeX (series ).value ()
466+ else omeMeta .getPixelsPhysicalSizeX (series ).value ()
467+ )
468+ physSizeY_max = (
469+ physSizeY .value ()
470+ if physSizeY .value () >= omeMeta .getPixelsPhysicalSizeY (series ).value ()
471+ else omeMeta .getPixelsPhysicalSizeY (series ).value ()
472+ )
473+ if omeMeta .getPixelsPhysicalSizeZ (series ):
474+ physSizeZ_max = (
475+ physSizeZ .value ()
476+ if physSizeZ .value ()
477+ >= omeMeta .getPixelsPhysicalSizeZ (series ).value ()
478+ else omeMeta .getPixelsPhysicalSizeZ (series ).value ()
479+ )
480+
481+ else :
482+ physSizeZ_max = 1.0
483+
484+ # get the absolute stage positions and store them
485+ pos_x = current_position_x .value ()
486+ pos_y = current_position_y .value ()
487+
488+ if current_position_z is None :
489+ log .debug ("the z-position is missing in the ome-xml metadata." )
490+ pos_z = 1.0
491+ else :
492+ pos_z = current_position_z .value ()
493+
494+ stage_coordinates_x .append (pos_x )
495+ stage_coordinates_y .append (pos_y )
496+ stage_coordinates_z .append (pos_z )
497+
498+ max_size = [physSizeX_max , physSizeY_max , physSizeZ_max ]
499+
500+ # calculate the store the relative stage movements in px (for the grid/collection stitcher)
501+ relative_coordinates_x_px = []
502+ relative_coordinates_y_px = []
503+ relative_coordinates_z_px = []
504+
505+ for i in range (len (stage_coordinates_x )):
506+ rel_pos_x = (
507+ stage_coordinates_x [i ] - stage_coordinates_x [0 ]
508+ ) / physSizeX .value ()
509+ rel_pos_y = (
510+ stage_coordinates_y [i ] - stage_coordinates_y [0 ]
511+ ) / physSizeY .value ()
512+ rel_pos_z = (stage_coordinates_z [i ] - stage_coordinates_z [0 ]) / z_interval
513+
514+ relative_coordinates_x_px .append (rel_pos_x )
515+ relative_coordinates_y_px .append (rel_pos_y )
516+ relative_coordinates_z_px .append (rel_pos_z )
517+
518+ return {
519+ "dimensions" : dimensions ,
520+ "stage_coordinates_x" : stage_coordinates_x ,
521+ "stage_coordinates_y" : stage_coordinates_y ,
522+ "stage_coordinates_z" : stage_coordinates_z ,
523+ "relative_coordinates_x" : relative_coordinates_x_px ,
524+ "relative_coordinates_y" : relative_coordinates_y_px ,
525+ "relative_coordinates_z" : relative_coordinates_z_px ,
526+ "image_calibration" : image_calibration ,
527+ "calibration_unit" : calibration_unit ,
528+ "image_dimensions_czt" : image_dimensions_czt ,
529+ "series_names" : series_names ,
530+ "max_size" : max_size ,
531+ }
0 commit comments