Arcadia-Science / readlif

Leica Image Format (LIF) file reader for Python
GNU General Public License v3.0
32 stars 13 forks source link

Failed to load tiled image - Unknown bit depth #7

Closed PezHayes closed 3 years ago

PezHayes commented 3 years ago

Hello Nick

I'm trying to use readlif to import a lif file that contains a series of 82 images. That's 9x9 tiles (each 1024x1024) plus the stitched version. Each image is 12-bit with 2C, 6Z & 1T.

Below is my code:

from readlif.reader import LifFile

new = LifFile(image_path)
img_0 = new.get_image(0)
img_list = [i for i in new.get_iter_image()]

print(img_0)
print(img_list)
print(img_list[0].get_frame(z=0, t=0, c=0))

And the outputs and error message:

<__main__.LifImage object at 0x0000023433866D08>
[<__main__.LifImage object at 0x0000023433866588>]
Traceback (most recent call last):
  File "C:/Users/dhayes/Dropbox/PyCharmProjects/Erik_Sam_NeuriteLength/NeuriteLengthAnalysis.py", line 539, in <module>
    print(img_list[0].get_frame(z=5, t=0, c=1))
  File "C:/Users/dhayes/Dropbox/PyCharmProjects/Erik_Sam_NeuriteLength/NeuriteLengthAnalysis.py", line 163, in get_frame
    return self._get_item(item_requested)
  File "C:/Users/dhayes/Dropbox/PyCharmProjects/Erik_Sam_NeuriteLength/NeuriteLengthAnalysis.py", line 126, in _get_item
    raise ValueError("Unknown bit-depth, please submit a bug report"
ValueError: Unknown bit-depth, please submit a bug report on Github

Although the .lif file contains a series of many images, get_iter_image() only returns one.

Looking a bit into the code, I found that within the _get_item() function where this traceback is generated, len(data) has a value of 169869312 which equals 1024x1024x81x2 (with self.dims = (1024, 1024, 6, 1) and self.channels = 2). I'm guessing data combines all 81 tiles across both channels for a given z? I can therefore reshape to get my images, but does readlif have any function to directly extract series of images from a single .lif file?

Thanks for any help you have!

nimne commented 3 years ago

Hello,

Thanks for opening a bug report! Right now, this package hasn't been built for / tested with tiled images - for an immediate solution you can use python-bioformats which is essentially an interface to the Java Bio-Formads software. It's a little difficult to use, and quite slow, which inspired me to write this.

There is also a fork that returns numpy arrays (not PIL objects) that looks like it supports images of varying bit-depth. The documentation isn't updated with the new functions, but the code is pretty clear: https://github.com/RiosGroup/readlif

I'm a little surprised that the image was able to be loaded, and that the error happened when trying to detect the bit-depth (a new-ish bit of code). Has this worked in the past with 8-bit tiled images? Perhaps it's just an artifact of how the images are initialized. get_iter_image() doesn't do much but read the metadata until you try to load up an image.

I can certainly add support for tiled images, but it'll take me a week or two! Fortunately, I think I can use some of the same conventions as the RiosGroup fork.

I will also need an example file to build the code against. If you are willing to share the image file (or a subset of the images) I could give it a shot. If you don't want to post it publicly, you can shoot me a twitter DM (listed on my profile).

I really haven't used many tiled images, and I no longer have a Leica scope nearby to test out. If I were to add support for tiled images I have a bunch of questions. What functions do you think makes sense to access them? Is m the convention for defining a tile?

In my quick imagination of how tiled images are usually used, get_iter_image would return a bunch of LifImage objects. get_frame(z=0, t=0, c=0) would be expanded to have something like get_frame(z=0, t=0, c=0, m=0). I would need to add a new function like get_iter_m(c=0, t=0, z=0) and let you reconstruct the tiled image on your own? Is there any metadata that needs to come along with the images, like X or Y position that is needed for the reconstruction? It would also make sense to add metadata to the LifImage describing how many tiles are present.

-Nick

evamaxfield commented 3 years ago

Hey @nimne just wanted to chime in here! We wrap this wonderful library into aicsimageio for our LifReader. We just this morning also received an issue about reading of "mosaic" / "tiled" LIF images.

While the user couldn't provide the full file, they did provide the XML metadata which could be useful in developing this functionality can be found as a download here.

I don't know the LIF Spec 100% but would be happy to help in any way to add this functionality to the library so let me know.

And thanks again for making and releasing this to the public!

nimne commented 3 years ago

I'm glad that this side-project is getting some use! That XML snippet is really helpful. I'll see if I can implement something over the next week or so, but without a test file, it will be hard to make sure I have the byte order right - unfortunately LIF isn't an open specification.

Based on a guess (and that XML snippet), the image block probably goes ((((XY) * C) * Z) * T) * M where M is the mosaic / tile. I'm sure there's a better way to represent the order, but it's hard for me to separate the 'visual' aspect of images with the big data matrix that they're drawn from!

In the immediate future, I'll probably make a quick update to raise an error if a tiled image is loaded - right now the error isn't very specific or informative. I will probably also need to find an example LIF file with tiled images before I have any level of confidence in whatever code I write to process them - this will probably be a roadblock / holdup, as I don't have easy access to a Leica scope.

nimne commented 3 years ago

It looks like there is an example on the OME forums that I can use to build this: https://forum.image.sc/t/tilling-issue-with-lif-file/26843

I think the images will need to bring along more metadata including FlipX, FlipY, and SwapXY, as well as some tiling metadata (FieldX, FieldY, PosX, and PosY) to enable stitching after the fact (if needed). I haven't used many tiled images, so if anyone has suggestions on function names / conventions - please let me know!

evamaxfield commented 3 years ago

Totally understand how writing this library without the spec would be a nightmare... @heeler sorry to poke while on break but do you know of a hidden LIF spec document somewhere?

@nimne I think your image block assumptions make decent sense, hard to say for sure haha. Adding a warning or error about failure to read tiles makes sense to me!

Regarding the mosaic reading API: we are currently dealing with this as well. So far, we have found that when an image is tiled users prefer to have the fully reconstructed image back out as default. @heeler may also have some thoughts on this as well.

PezHayes commented 3 years ago

Hi Nick, Hi Jackson. Thanks very much for your efforts and quick responses, and sorry for the delay in mine.

I'm a little surprised that the image was able to be loaded, and that the error happened when trying to detect the bit-depth (a new-ish bit of code). Has this worked in the past with 8-bit tiled images? Perhaps it's just an artifact of how the images are initialized. get_iter_image() doesn't do much but read the metadata until you try to load up an image.

I tried with an 8-bit mosaic and got the same error.

I can certainly add support for tiled images, but it'll take me a week or two! Fortunately, I think I can use some of the same conventions as the RiosGroup fork.

I will also need an example file to build the code against. If you are willing to share the image file (or a subset of the images) I could give it a shot. If you don't want to post it publicly, you can shoot me a twitter DM (listed on my profile).

That would be awesome. The .lif files are from some collaborators, but I checked with them and they are all happy for me to share them with you. I'll DM you on twitter like you suggested.

I really haven't used many tiled images, and I no longer have a Leica scope nearby to test out. If I were to add support for tiled images I have a bunch of questions. What functions do you think makes sense to access them? Is m the convention for defining a tile?

In my quick imagination of how tiled images are usually used, get_iter_image would return a bunch of LifImage objects. get_frame(z=0, t=0, c=0) would be expanded to have something like get_frame(z=0, t=0, c=0, m=0). I would need to add a new function like get_iter_m(c=0, t=0, z=0) and let you reconstruct the tiled image on your own? Is there any metadata that needs to come along with the images, like X or Y position that is needed for the reconstruction? It would also make sense to add metadata to the LifImage describing how many tiles are present.

I don't know if I can help much on these details, but I think the issue is not specific to mosaics, but to any series of images saved in a single .lif file. When I open the file in ImageJ, it is considered a series with the 81 tiles, plus the mega stitched file. I think there S is used rather than M to determine the image in the series. From my perspective, your suggestion seems like exactly what I would want: a get_frame(z=0, t=0, c=0, m/s=0) function, along with the XY position, so I can reconstruct the mosaic myself, and metadata within the LifImage object so that I know how many S/M, C, T & Z I am dealing with.

evamaxfield commented 3 years ago

I would personally prefer M as the dimension to index with rather than S. S to me is Scene 🤷

nimne commented 3 years ago

Hi folks, I have a working bit of code in the development branch (0.4.0dev) to access tiled images. Code documentation is here: https://readlif.readthedocs.io/en/dev/.

There is a new function get_iter_m() that works a lot like the other functions (i.e. get_iter_t()). I also added a new m parameter to get_frame() (i.e. img_0.get_frame(z=0, t=0, c=0, m=1)). I still want to do more testing (and thinking) before merging this in to the main branch. There is also more information about the mosaic images (where available) in the LifImage.mosaic_position attribute. This is a list of tuples with information on the position of the mosaic images.

LifImage.mosaic_position currently returns a list of tuples with information about the set of tiles. For example, the position of tile 0 is LifImage.mosaic_position[0]. This returns a tuple of (FieldX, FieldY, PosX, PosY).

For the purposes of this library (simple data access) I don't think I want to return a reconstructed image image by default, although I'm considering whether to add a 'merging' function to utilities.

This api could change before I merge this in to master! I also need to double check the documentation and add a few tests before this is done. Hoping for feedback!

evamaxfield commented 3 years ago

I will try to give this a test sometime this week! Thanks for the updates!

evamaxfield commented 3 years ago

Hey @nimne sorry I haven't gotten to this yet. It's all "what can I do in my free time" -- but since I am in the process of upgrading the aicsimageio lib to 4.x I am planning on testing this out when I upgrade the LifReader itself (just makes less work for me). I should have the upgraded LifReader done and this tested in about 3 ish weeks is my timeline right now.

nimne commented 3 years ago

Great, thanks for the update! As I find time, I'll try to add some unit tests and release a 0.4.0-beta / something on the main branch. Hopefully getting an update on pypi makes testing easier for others!

nimne commented 3 years ago

Hi All, 0.4.0 is up, and it should work with mosaic images! I'll leave this issue open for a while in case there is feedback, or something doesn't work as expected.

nimne commented 3 years ago

Closed with 0.4.0 - if there are any problems with mosaic images, let me know by opening a new issue!