ahnlabb / BioformatsLoader.jl

A julia package to load images using bioformats
Other
17 stars 7 forks source link

Order of dimensions messed up. #13

Closed RainerHeintzmann closed 3 years ago

RainerHeintzmann commented 3 years ago

When importing multidimensional files, the order of the dimensions does not agree to what is expected:

julia> data = BioformatsLoader.bf_import_url("https://samples.fiji.sc/150707_WTstack.lsm"); size(data[1])
(1, 18, 620, 620, 3)

It should be 620x620x18x3. I fixed this in View5D.jl by permutedims(img[1].data, (3,4,2,5,1)) but this is a problem that many will encouter, if they just want to use the data.

ahnlabb commented 3 years ago

This is what the order parameter of bf_import (and bf_import_url) specifies. I don't know what a more "natural" order would be but it seems like this could depend on the format/person/field. I believe the default that I chose ("TZYXC") was the same as the order used by default in the python nd2reader library.

To get the 620x620x18x3 format you can use:

julia> data = BioformatsLoader.bf_import_url("https://samples.fiji.sc/150707_WTstack.lsm"; order="YXZC"); size(data[1])
(620, 620, 18, 3)

docstring for bf_import:

help?> bf_import
search: bf_import

  bf_import(filename::AbstractString; order::AbstractString="TZYXC", squeeze::Bool=false)

  Import all images and metadata in the file using Bio-Formats.

  The order keyword argument is the output order of dimensions, allowing you to
  reshuffle the dimensions of the images or skip singleton dimensions. Providing
  "CYX" for order gives you three-dimensional images in a "channels-first" data
  format but will fail with an error if any of the images in the file has a size
  greater than one in the T or Z dimension.

  By setting the squeeze keyword argument to true all singleton dimensions in the
  images will be dropped (even if they are in the order argument).
RainerHeintzmann commented 3 years ago

Thanks a lot for the hint with the "order" argument, which I did not know about. My choice would have been "XYZCT" as this is the standard order, I would think. Is this not also the way that ImageJ would load the data by default? At least this is the way that View5D.jl expects it normally. Do the axes have names or is there a way to find out in which order the data is present in the resulting data structure?

ahnlabb commented 3 years ago

Do the axes have names or is there a way to find out in which order the data is present in the resulting data structure?

Unfortunately not currently, but I'm considering depending on AxisArrays.jl and using it to name the axes, I agree that it would be convenient.

My choice would have been "XYZCT" as this is the standard order, I would think.

I'll look around a bit more at what other libraries are doing. However, "XY" doesn't really make sense for most purposes in Julia since that would mean that the "row" direction is the X-axis. The JuliaImages ecosystem renders an image the same way the corresponding matrix would be rendered, see the example below.

image

RainerHeintzmann commented 3 years ago

The good old neverending XY problem with images :-). My take on this is that the X-axis has to point to the right direction and axes are ordered XYZ. If some display packages handle this differently than this is the problem of the display package. I would not require that the image display has to agree with the way that Julia prints matrices.

But yes, if you encapsulate your loaded image into an ImageMeta this probably has to be of this order to agree with the default display of such a type. Given this I revise my statement to suggest YXZCT as the default order for ImageMeta. The quickest solution to retrospectively figure out how the data was loaded would be to save an entry ImportOrder with the string that was used into the properties dict. With this I should be able to choose the corresponding directions in View5D and I might also allow the user to select a preferred axis assignment. I wouldn't necessarily think that you need to support axes. They anyway vanish far to quickly when processing these images.

ahnlabb commented 3 years ago

I have now added the ImportOrder entry tor the metadata in addition to using AxisArrays.jl (since ImageMetadata anyways depends on AxisArrays). ImportOrder contains a Tuple of Symbols:

img = bf_import(path; order="XYZCT")
img.ImportOrder
(:X, :Y, :Z, :C, :T)

I have also changed the default to CYXZT. While I don't disagree with your points, this seems to be the order that currently is most compatible with the rest of the JuliaImages ecosystem. Functions like ImageCore.colorview assume an interleaved/channel-first structure.

RainerHeintzmann commented 3 years ago

Thanks! I tried updating to the 0.3.0 version but get an error attempting to load data. See below. Also loading an example file resulted in the same error:

julia> data = BioformatsLoader.bf_import("https://samples.fiji.sc/150707_WTstack.lsm")
ERROR: KeyError: key "C" not found
Stacktrace:
  [1] getindex
    @ .\dict.jl:482 [inlined]
  [2] bf_import(uri::String; kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ BioformatsLoader ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:122
  [3] bf_import
    @ ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:121 [inlined]
  [4] (::BioformatsLoader.var"#62#63"{Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, URIs.URI, String})(path::String)
    @ BioformatsLoader ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:169
  [5] mktempdir(fn::BioformatsLoader.var"#62#63"{Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, URIs.URI, String}, parent::String; prefix::String)
    @ Base.Filesystem .\file.jl:729
  [6] mktempdir (repeats 2 times)
    @ .\file.jl:727 [inlined]
  [7] #bf_import_http#61
    @ ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:166 [inlined]
  [8] bf_import_http
    @ ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:166 [inlined]
  [9] #bf_import_http#60
    @ ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:162 [inlined]
 [10] bf_import_http(url::URIs.URI)
    @ BioformatsLoader ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:160
 [11] bf_import(uri::String; kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ BioformatsLoader ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:122
 [12] bf_import(uri::String)
    @ BioformatsLoader ~\.julia\packages\BioformatsLoader\vtWPq\src\BioformatsLoader.jl:121
 [13] top-level scope
    @ REPL[5]:1
ahnlabb commented 3 years ago

I see, I didn't consider the Windows path format. The drive letter C: is interpreted as a URI scheme. I'll have to handle that (even if you're loading from a URL the issue manifests for the temporary file path).

RainerHeintzmann commented 3 years ago

I see. Sounds like a good explanation :-)

ahnlabb commented 3 years ago

The latest commit on master should fix the Windows issue and also be a bit more robust against strange paths. The CI tests are passing on Windows but unfortunately, I don't have a Windows machine handy so I can't do any manual verification. If you can confirm that it seems to work for both URLs and local files, @RainerHeintzmann, I'll release a new patch version :-)

RainerHeintzmann commented 3 years ago

Sure, I am happy to test it, yet I am for the moment stuck with the aforementioned problem of the missing XML file. With the usual installation route it somehow downloads it after a couple of tries, but activating the package does not seem to make it download the XML file.

julia> using BioformatsLoader
[ Info: Precompiling BioformatsLoader [d1f74bce-bb60-11ea-2eff-3d75f256b4d8]
I/O warning : failed to load external entity "file:/C:/Users/pi96doc/Documents/Programming/Julia/BioformatsLoader.jl/src/../deps/ome.xsd"
ERROR: LoadError: LoadError: LightXML.XMLParseError{String}("Failure in parsing an XML file.")

I also tried installing via ] add https://github.com/ahnlabb/BioformatsLoader.jl.git with a similar result. Not sure how to proceed.

RainerHeintzmann commented 3 years ago

I copied the xml file from a previous installation, but this seems to also cause problems:

julia> BioformatsLoader.init(); # Initializes JavaCall with opt and classpath
ERROR: JavaCall.JavaCallError("Class Not Found loci/common/DebugTools")
Stacktrace:
 [1] _metaclass(class::Symbol)
   @ JavaCall ~\.julia\packages\JavaCall\tjlYt\src\core.jl:383
 [2] metaclass(class::Symbol)
   @ JavaCall ~\.julia\packages\JavaCall\tjlYt\src\core.jl:389
 [3] jcall(typ::Type{JavaObject{Symbol("loci.common.DebugTools")}}, method::String, rettype::Type, argtypes::Tuple{DataType}, args::String)
   @ JavaCall ~\.julia\packages\JavaCall\tjlYt\src\core.jl:225
 [4] enableLogging(level::String)
   @ BioformatsLoader ~\.julia\packages\BioformatsLoader\mdbKZ\src\BioformatsLoader.jl:230
 [5] init(; memory::Int64, log_level::String)
   @ BioformatsLoader ~\.julia\packages\BioformatsLoader\mdbKZ\src\BioformatsLoader.jl:238
 [6] init()
   @ BioformatsLoader ~\.julia\packages\BioformatsLoader\mdbKZ\src\BioformatsLoader.jl:236
 [7] top-level scope
   @ REPL[10]:1
ahnlabb commented 3 years ago

I copied the xml file from a previous installation, but this seems to also cause problems:

This looks like the JAR file is missing. I really need to handle these dependencies better but for now could you try:

]build BioformatsLoader

or copying the bioformats_package.jar from the previous installation?

RainerHeintzmann commented 3 years ago

Brilliant. Works great! Please make a release. I adapted View5D.jl to read the :ImportOrder and permute accordingly if necessary.