We're Hiring!

Issues with OME and original metadata

Historical discussions about the Bio-Formats library. Please look for and ask new questions at https://forum.image.sc/tags/bio-formats
Please note:
Historical discussions about the Bio-Formats library. Please look for and ask new questions at https://forum.image.sc/tags/bio-formats

If you are having trouble with image files, there is information about reporting bugs in the Bio-Formats documentation. Please send us the data and let us know what version of Bio-Formats you are using. For issues with your code, please provide a link to a public repository, ideally GitHub.

Issues with OME and original metadata

Postby fgarcia » Mon Nov 26, 2018 5:21 pm

Dear all,

I've been having a number of issues with reading and storing both OME as well as original metadata from a Leica *.lif file. Before posting this entry, I've gone through various threads in this forum, which point out reasons why I'm encountering difficulties... But yet I can't get a clear solution. Such threads I read are:


In brief, my goal is: to import a Leica *.lif file, to pick one of the multiple series that it contains, and to store the hyperstack in separate OME-TIFF files in disk - one per time-point, one per channel. For future operations, I'd strongly prefer to have all relevant metadata saved onto each file, too.

Well, I've been programming in Jython, with the aim to stay in Fiji as much as possible. But I've also given a try to Matlab Bio-formats tool. Given the known issues, there were suggestions by contributors of this forum to try 4.4.x versions. Hence, I've experimented with 4.4.8, 4.4.12, 5.9.2 (by the way, the *.jar as automatically downloaded by Fiji differs in size with respect to the one we can download from OME's repository?!). I've observed as follows with a 1024x1024x38x3x1 hyperstack:

    * Fiji, LOCI 4.4.8
      · 1609 original metadata entries (fine);
      · Wrong LUTs (should be green-gray-red, but I get purple-gray-blue);
      · Physical size X = 1.513672 microns (which I suspect is wrong by a factor of 1023/1024)
      · No indication of original metadata in the OME-XML window shown by Fiji
    * Matlab, LOCI 4.4.8 ---> 4449 metadata entries (i.e. from all 4 series, undesirable); wrong PhysX

    * Fiji, LOCI 4.4.12
      · 1609 original metadata entries;
      · Wrong LUTs;
      · Wrong physical size X = 1.513672 microns
      · No indication of original metadata in the OME-XML
    * Matlab, LOCI 4.4.12 ---> 1609 metadata entries; wrong PhysX

    * Fiji, Bio-Formats 5.9.2
      · 376 original metadata entries (wrong, info from channels lost);
      · Right LUTs;
      · Right physical size X = 1.515152 microns (coincides with what Leica's proprietary software states)
      · There is an indication of the presence of original metadata in the OME-XML, with a tag <MetadataOnly/>... but I don't know how to access to it, there seems not to be a query method in the API like getBlaBlaBla()
    * Matlab, Bio-Formats 5.9.2 ---> 1609 metadata entries; wrong PhysX

Furthermore, when I export to *.ome.tif indicating that I want to split by C and by T, then the OME-XML is generated with UUID identification of the other files associated to the hyperstack... But there no longer appear any mention to the tag <MetadataOnly/>, and the original metadata are chopped to the core ones only.

Basically, I am quite confused on how to progress from this point onwards... perhaps with the kind-of-inelegant tweak of the TiffComment functionality?

Sorry for the extremely long post, but I wanted to be precise and couldn't find a more abbreviated way. If useful, I can provide a test example with 3 channels and 1 time-point.

Thank you very much in advance for your assistance!!

Best regards,
Fernando
fgarcia
 
Posts: 5
Joined: Fri Nov 23, 2018 6:11 pm

Re: Issues with OME and original metadata

Postby dgault » Tue Nov 27, 2018 12:45 pm

Hi Fernando,

Unfortunately it looks as though the missing metadata entries you are seeing with the latest Bio-Formats 5.9.2 release is related to the bug https://trac.openmicroscopy.org/ome/ticket/12237 and https://trac.openmicroscopy.org/ome/ticket/12237

Exporting to ome.tiff should show the original metadata stored as Annotations such as the one below:
Code: Select all
      <XMLAnnotation ID="Annotation:1703" Namespace="openmicroscopy.org/OriginalMetadata">
         <Value>
            <OriginalMetadata>
               <Key>Image003 ATLConfocalSettingDefinition|Memory|MemoryBlockID</Key>
               <Value>MemBlock_65</Value>
            </OriginalMetadata>
         </Value>
      </XMLAnnotation>


Note that when viewing the XML in FIJI these annotations are trimmed for performance reasons. You can view the values via Image -> Show Info or if you want to access them programatically you can retrieve them using
Code: Select all
getSeriesMetadataValue(field, value)
. Doing it this way through the macro extensions though does require you to know the key name for each value you are retrieving, which is not ideal.

When exporting if the values are not being retained it might be worth ensuring that
Code: Select all
setOriginalMetadataPopulated(populate)
is set

An easier approach may be to convert using the command line tools at https://www.openmicroscopy.org/bio-formats/downloads/

You can convert the files splitting the T and C using:
bfconvert /path/to/originalFile.lif /path/to/convertedFile_series_C%c_T%t.ome.tiff

You can then verify the metadata stored in the OME-XML using:
showinf -nopix -omexml /path/to/convertedFile.ome.tiff

David Gault
User avatar
dgault
Team Member
 
Posts: 208
Joined: Fri Aug 14, 2015 2:56 pm

Re: Issues with OME and original metadata

Postby fgarcia » Tue Nov 27, 2018 3:16 pm

Thank you so much, David! I really appreciate your help.

I will go and implement your guides and suggestions a.s.a.p. (I have a few busy days ahead), and let you know.
Just a couple of extra question for now: These two commands (bfconvert & showinf) are to be run in a command shell, right? Would there be a way to integrate them to a Jython script inside Fiji?

Best regards,
Fernando
fgarcia
 
Posts: 5
Joined: Fri Nov 23, 2018 6:11 pm

Re: Issues with OME and original metadata

Postby dgault » Wed Nov 28, 2018 3:37 pm

Yeah those tools mentioned are command line tools. You should be able to achieve the same result in a jython script though. If you have a sample of the script you are using Id be happy to help modify it to provide the same behaviour and ensure the metadata is all exported.
User avatar
dgault
Team Member
 
Posts: 208
Joined: Fri Aug 14, 2015 2:56 pm

Re: Issues with OME and original metadata

Postby fgarcia » Thu Nov 29, 2018 2:31 pm

Dear David,

First of all, I cannot stress enough my gratitude for your help!

The process of reading OME and original metadata is working now:
Code: Select all
from ij import IJ
#from ij.process import ImageProcessor
from loci.formats import ImageReader, MetadataTools
from loci.formats.services import OMEXMLServiceImpl
#from loci.formats.tools import ImageConverter
from loci.plugins import BF
from loci.plugins import LociExporter
from loci.plugins.in import ImporterOptions

selFilePathSource = "/home/fer/Documents/Data/fileInLeica.lif";
selFilePathDestin = "/home/fer/Documents/Data/fileOutOME.ome.tif";
selFilePathOMEXML = "/home/fer/Documents/Data/ome.xml";
selIdxSeries = 1; # 2nd series
   
# display selected series alone
importOpts = ImporterOptions();
importOpts.setId(selFilePathSource);
importOpts.setAutoscale(True);
importOpts.setCrop(False);
importOpts.setSplitFocalPlanes(False);
importOpts.setSplitChannels(False);
importOpts.setSplitTimepoints(False);
importOpts.setColorMode(ImporterOptions.COLOR_MODE_COLORIZED);
importOpts.setStackFormat(ImporterOptions.VIEW_HYPERSTACK);
importOpts.setStackOrder(ImporterOptions.ORDER_XYCZT);
importOpts.setUngroupFiles(True);
   
importOpts.setSeriesOn(selIdxSeries,True);

imagesSelSeries = BF.openImagePlus(importOpts);
for img in imagesSelSeries:
   img.show(); # in fact I get only one
   
# read series' original and OME-XML metadata
selSeriesReader = ImageReader();
seriesOMEMetaData = MetadataTools.createOMEXMLMetadata();
selSeriesReader.setMetadataStore(seriesOMEMetaData);
selSeriesReader.setOriginalMetadataPopulated(True);
selSeriesReader.setId(selFilePathSource);
   
selSeriesReader.setSeries(selIdxSeries);
seriesOrigMetaData = selSeriesReader.getSeriesMetadata();
selSeriesReader.close();


So when I query, results are as expected:
Code: Select all
physSizeX = seriesOMEMetaData.getPixelsPhysicalSizeX(0)
print("PhysX = " + str(physSizeX.value()) + " " + physSizeX.unit().getSymbol());
   
metaKey = "Image|ATLConfocalSettingDefinition|NumericalAperture";
metaVal = selSeriesReader.getSeriesMetadataValue(metaKey);
print("NA = " + str(metaVal));

There is the known limitation of the reported bug you pointed out (Leica lif and metadata from multiple channels squashed in >4.4.x)... Not ideal, but I can survive (fairly well!) with a workaround based on <Channel Color> OME and manual input of a few fields: excitation & emission wavelengths, basically.

However, I am intrigued with the process of exporting/saving: as I found no way to explicitly feed the conversion/writing with the OME object (called seriesOMEMetaData in my code above). I've tried the following programmatical strategies without success:

a) Inspired by Bio-Formats Macros:
Code: Select all
IJ.run("Bio-Formats Exporter", "save=[" + selFilePathDestin + "] write_each_timepoint write_each_channel use compression=Uncompressed");


b) As above, but with the "-option ometiff.companion converted.companion.ome" I read in the extra help for bfconvert:
Code: Select all
IJ.run("Bio-Formats Exporter", "save=[" + selFilePathDestin + "] -option ometiff.companion converted.companion.ome write_each_timepoint write_each_channel use compression=Uncompressed");


c) ImageConverter class, up to now just to see how the syntax is:
Code: Select all
imgConvert = ImageConverter();
imgConvert.testConvert(None, None); # print info on how to use

However, it doesn't even build in my machine, because loci.formats.tools.ImageConverter isn't recognized.

d) LociExporter class:
Code: Select all
imgLociExport = LociExporter();
strLociExportSetup = ""; # no setup, opens GUI dialog
imgLociExport.setup(strLociExportSetup, imagesSelSeries[0]);
imgLociExport.run(None); # empty ImageProcessor

This latest code is working, but I don't seem to find the appropriate configuration string. I've also tried -in failure- other alternatives, such as:
strLociExportSetup = "/home/fer/Documents/Data/fileOutOME.ome.tiff";
strLociExportSetup = "/home/fer/Documents/Data/fileOutOME_C%c_T%t.ome.tiff";
strLociExportSetup = "splitC splitT";
strLociExportSetup = selFilePathDestin + "splitC splitT";

(The latter two inspired by source code for Exporter.java)

Yet the original metadata doesn't get stored, but -as I mentioned before- I suspect that is because I can't find a method to feed the corresponding MetadataStore explicitly.

With sincere gratitude,
Fernando
fgarcia
 
Posts: 5
Joined: Fri Nov 23, 2018 6:11 pm

Re: Issues with OME and original metadata

Postby dgault » Fri Nov 30, 2018 4:24 pm

Ive put together the below sample jython which will read in a lif and export it containing all of the original metadata. It also changes the output filename to split the T and C channels. I hope that will be useful and accomplish what you are looking for. It also shows an example of retrieving seriesMetadata which might be useful if you need to parse any of the original metadata values:

Code: Select all
from ij import IJ
from loci.plugins import BF
from loci.formats import ImageReader
from loci.formats import ImageWriter
from loci.formats import MetadataTools
from loci.formats import FormatTools

file = "/path/to/inputFile.lif"
outId = "/path/to/outputFile_C%c_T%t.ome.tiff"

reader = ImageReader()
writer = ImageWriter()
omeMeta = MetadataTools.createOMEXMLMetadata()

reader.setOriginalMetadataPopulated(True);
reader.setMetadataStore(omeMeta)
reader.setId(file)
seriesCount = reader.getSeriesCount()

seriesMetadata = reader.getSeriesMetadata()
IJ.log("SeriesMetadata: " + str(seriesMetadata))

writer.setMetadataRetrieve(omeMeta)
outputName = FormatTools.getFilename(0, 0, reader, outId);
writer.setId(outputName);

for s in range(seriesCount):
  reader.setSeries(s)
  writer.setSeries(s)
  planeCount = reader.getImageCount()
  for p in range(planeCount):
    outputName = FormatTools.getFilename(s, p, reader, outId);
    writer.changeOutputFile(outputName);
   
    plane = reader.openBytes(p)
    writer.saveBytes(p, plane)

writer.close()
reader.close()
User avatar
dgault
Team Member
 
Posts: 208
Joined: Fri Aug 14, 2015 2:56 pm

Re: Issues with OME and original metadata

Postby fgarcia » Fri Nov 30, 2018 6:45 pm

Dear David,

Thank you so much. Your code is running smooth in my device, and that's good news!
However, there is a point that I didn't manage to clarify in my previous posts... and that seems to complicate things beyond expected: In my data I have 4 images series per file, but in the *.ome.tiff I want to retain only 1 (and its corresponding metadata of that specific series, not the whole, to avoid reading info from other series in the future).

Let's say, for instance, that I always want just the 3rd series to be stored. I've tried:
Code: Select all
idxS = 2;

... along with removing the outer for loop:
Code: Select all
s = idxS
reader.setSeries(s)
writer.setSeries(s)
planeCount = reader.getImageCount()
for p in range(planeCount):
   outputName = FormatTools.getFilename(s, p, reader, outId);
   writer.changeOutputFile(outputName);

   plane = reader.openBytes(p)
   writer.saveBytes(p, plane)

Data seems to be written, and the files reach reasonable sizes in disk. However, when I try to open them via the Bio-Formats plugin GUI, an exception is launched (java.lang.IndexOutOfBoundsException)... The same happens if I put 0 instead of s in the writer:
Code: Select all
s = idxS
reader.setSeries(s)
writer.setSeries(0)
...


Similarly, I have tried adding an if inside the inner for loop, just before the saveBytes operation:
Code: Select all
...
      plane = reader.openBytes(p)
      if (s == idxS):
         writer.saveBytes(p, plane)

No success, the same import exception arises.

As an added complication, I would like to select only a sub-range of Z stack planes to save as output... But I'm afraid this would mess up things with the two nested for loops you suggest, since reader and writer would not have the same size. Actually, I suspect this is also the reason why my attempts of dropping series fail. Am I right?

Thanks a lot, enjoy the weekend!
Fernando
fgarcia
 
Posts: 5
Joined: Fri Nov 23, 2018 6:11 pm

Re: Issues with OME and original metadata

Postby dgault » Mon Dec 03, 2018 1:47 pm

You are correct that the reason it is failing is most likely because the metadata contains image info which is no longer relevant. As such you will have to remove these particular fields for the images you are not saving. The rest of your code for selecting the appropriate series is correct though and should be saving the correct pixel data. I have modified the below code so that it only converts the series as selected by idxS.

Code: Select all
from ij import IJ
from loci.plugins import BF
from loci.formats import ImageReader
from loci.formats import ImageWriter
from loci.formats import MetadataTools
from loci.formats import FormatTools

file = "/Users/dgault/Documents/Sample Images/leica-lif/21707/HNSCC_N6a(1).lif"
outId = "/Users/dgault/Documents/Sample Images/leica-lif/21707/HNSCC_N6a(1)_T%t_C%c.ome.tif"

reader = ImageReader()
writer = ImageWriter()
omeMeta = MetadataTools.createOMEXMLMetadata()

reader.setOriginalMetadataPopulated(True);
reader.setMetadataStore(omeMeta)
reader.setId(file)
seriesCount = reader.getSeriesCount()
idxS = 2
reader.setSeries(idxS)

seriesMetadata = reader.getSeriesMetadata()
IJ.log("SeriesMetadata: " + str(seriesMetadata))

# Find the series metadata we wish to keep
root = omeMeta.getRoot()
exportImage = root.getImage(idxS)
exportPixels = root.getImage(idxS).getPixels()
exportImage.setPixels(exportPixels)

# Remove all the series meatadata
while root.sizeOfImageList() > 0:
  root.removeImage(root.getImage(0))

while root.sizeOfPlateList() > 0:
  root.removePlate(root.getPlate(0))

# Add back the metadata for the selected series
root.addImage(exportImage)
omeMeta.setRoot(root)
writer.setMetadataRetrieve(omeMeta)


outputName = FormatTools.getFilename(0, 0, reader, outId);
writer.setId(outputName);
writer.setSeries(0)

planeCount = reader.getImageCount()
for p in range(planeCount):
  outputName = FormatTools.getFilename(0, p, reader, outId);
  writer.changeOutputFile(outputName);
   
  plane = reader.openBytes(p)
  writer.saveBytes(p, plane)

writer.close()
reader.close()



Additionally if you wish to only save a sub section of the Z slices, then you can do so but will need to overwrite the z count using :

Code: Select all
from ome.xml.model.primitives import PositiveInteger
omeMeta.setPixelsSizeZ(PositiveInteger(newSize), 0);
User avatar
dgault
Team Member
 
Posts: 208
Joined: Fri Aug 14, 2015 2:56 pm

Re: Issues with OME and original metadata

Postby fgarcia » Tue Dec 04, 2018 12:25 pm

Dear David,

Thank you very much for your help.
Your code was very useful to me, and I managed to achieve my goal. I had to do a few minor changes, so below I'm posting these code modifications, just in case they might be useful for someone else.

Regarding the original metadata, I discarded all of the metadata from other series by specifying not to populate them at first. Instead, I picked those from my only series of interest and added them at last, by using a OMEXMLServiceImpl object:
Code: Select all
from loci.formats.services import OMEXMLServiceImpl
...
reader.setOriginalMetadataPopulated(False);
...
reader.setSeries(idxS)
seriesMetadata = reader.getSeriesMetadata()
...
omeMetaImpl = OMEXMLServiceImpl();
omeMetaImpl.populateOriginalMetadata(omeMeta, seriesMetadata);


Also, the part about removing plates didn't apply in my case. Instead, I had to take care of instruments:
Code: Select all
exportInstrument = root.getInstrument(idxS);
...
# Remove all the series metadata
...
while root.sizeOfInstrumentList() > 0:
  root.removeInstrument(root.getInstrument(0))

# Add back the metadata for the selected series
root.addImage(exportImage);
root.addInstrument(exportInstrument);


Finally, I took care of the cropping of Z slices as you indicated. The resulting code is not straightforward, as I had to compute which Z slice corresponded to the current plane, and deploy two separate plane counters: one for the reader (pR), and another for the writer (pW):
Code: Select all
idxZMin = 3; idxZMax = 27; # for example
numZCrop = idxZMax-idxZMin+1;
...
omeMeta.setPixelsSizeZ(PositiveInteger(numZCrop), 0);
writer.setMetadataRetrieve(omeMeta)
...
planeCount = reader.getImageCount()
pW = 0;
for pR in range(planeCount):
  idxZCT = FormatTools.getZCTCoords(reader, pR);
  idxZ = idxZCT[0];
  if idxZ not in range(idxZMin, idxZMax+1):
     continue;

  outputName = FormatTools.getFilename(0, pW, reader, fileOut, True);
  writer.changeOutputFile(outputName);
   
  plane = reader.openBytes(pR);
  writer.saveBytes(pW, plane);

  pW += 1;


Now I only have some details to polish, like adding certain info from the original metadata to the OME. But I think that should be pretty easy with the set*() methods from the OMEXMLMetadataImpl class.

So, let me insist: Thank you so much, I really appreciate your willingness to help, your knowledge and your effort!
fgarcia
 
Posts: 5
Joined: Fri Nov 23, 2018 6:11 pm

Re: Issues with OME and original metadata

Postby radhikas » Thu Jan 17, 2019 12:06 pm

Collectively we refer to both series and global metadata as original metadata. Both of these types are stored in the OME-XML as XMLAnnotations, specifically as an OriginalMetadataAnnotation.

An example of how these annotations are written is as below:

ServiceFactory factory = new ServiceFactory();
OMEXMLService service = factory.getInstance(OMEXMLService.class);
OMEXMLMetadata metadata = service.createOMEXMLMetadata();
IFormatWriter writer = new ImageWriter();
Hashtable<String, Object> originalMetadata = null;
//set metadata
service.populateOriginalMetadata(metadata, originalMetadata);
writer.setMetadataRetrieve(metadata);
If you are writing both series and global metadata then the way Bio-Formats writes these values is that series metadata is prefixed with the series name and both sets are merged together before being written. An example of how this might look is as below:

Hashtable<String, Object> globalMetadata = new Hashtable<String, Object>();
allMetadata.putAll(globalMetadata);

for (int series=0; series<getSeriesCount(); series++) {
String name = "Series " + series;
String realName = ((IMetadata) store).getImageName(series);
if (realName != null && realName.trim().length() != 0) {
name = realName;
}
setSeries(series);
MetadataTools.merge(getSeriesMetadata(), allMetadata, name + " ");
setSeries(0);
}
setSeries(0);
service.populateOriginalMetadata((OMEXMLMetadata) store, allMetadata);
radhikas
 
Posts: 3
Joined: Wed Jan 16, 2019 7:01 am


Return to User Discussion [Legacy]

Who is online

Users browsing this forum: No registered users and 1 guest