libvips / pyvips

python binding for libvips using cffi
MIT License
647 stars 49 forks source link

Question: Any way to get pyvips to distinguish between time and depth for tiff images? #151

Open petoor opened 4 years ago

petoor commented 4 years ago

Hello. This question is inspired by : https://github.com/libvips/ruby-vips/issues/91 Is there a way to make pyvips.Image.new_from_file() return the dimension of bands and the dimensions of z?

Using your multipage tiff file as example : http://downloads.openmicroscopy.org/images/OME-TIFF/2016-06/bioformats-artificial/multi-channel-z-series.ome.tif

pyvips.Image.new_from_file("multi-channel-z-series.ome.tif", access="sequential") yields : <pyvips.Image 439x167 char, 1 bands, b-w>

And we can then loop over the bands with the pages argument. However, it seems like the colorchannel and the z-channel are multiplied together... That is, pyvips.Image.new_from_file("multi-channel-z-series.ome.tif", access="sequential", pages=13) yields the 3rd channel at the 4th time.

Compare this to bio formats, the metadata is. Axes: 4 Axis 'x' size: 439 Axis 'y' size: 167 Axis 'c' size: 3 Axis 'z' size: 5 Pixel Datatype: i1

So the question is, is there a way to make pyvips return the metadata of (t,x,y,z,c)? (Time, Height, Width, Depth, Color)

This would make it easier to structure data into a database.

Best regards.

jcupitt commented 4 years ago

Hello @petoor,

Yes, you can get the OME XML from image-description, eg.:

$ vipsheader -f image-description multi-channel-z-series.ome.tif

Or in Python:

xml = image.get("image-description")

You can set new xml as well, though it's up to you to ensure conformance.

petoor commented 4 years ago

Cheers, this gives exactly what i'm looking for!

When i try to do the same with a .ndpi / .bmp / .png file i get a vips_image_get: field "image-description" not found i guess due to the lack of the files not having a xlm header.

Is there any way to get a standardized output from pyvips containing the metadata of the images?

Also, is there a way to see which key words image.get accepts? I can't find much about it here : https://libvips.github.io/pyvips/vimage.html#pyvips.Image.get

jcupitt commented 4 years ago

That's right, image-description is part of libtiff:

https://www.awaresystems.be/imaging/tiff/tifftags/imagedescription.html

And OME uses it to carry the extra XML metadata it needs around.

You can check for the existence of a field with get_typeof(), eg.:

>>> x = pyvips.Image.new_from_file("k2.jpg")
>>> x.get("banana")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/john/.local/lib/python3.7/site-packages/pyvips/vimage.py", line 771, in get
    raise Error('unable to get {0}'.format(name))
pyvips.error.Error: unable to get banana
  vips_image_get: field "banana" not found

>>> x.get_typeof("banana")
0
>>> x.get_typeof("width")
24

The int returned by get_typeof is a GType, 0 means unknown. Have a look at:

https://github.com/libvips/pyvips/blob/master/pyvips/gvalue.py#L18

vipsheader -a somefile is a handy way to see what metadata an image has.

jcupitt commented 4 years ago

libvips is really a colour 2D image processing library, so width / height / bands / format is the standard metadata.

It has some support for things like volumetric, time-series and multi-res images, but it's a bit ad-hoc and it depends on the format to a degree. DICOM volumetric images are loaded as multi-page "toilet roll" images, for example, like PDFs and GIFs. OpenSlide images are opened a layer at a time and use the standard openslide metadata to select subimages at open time, just like pyramidal TIFFs.

jcupitt commented 4 years ago

... I meant to say, you'll need to check that image-description contains OME XML. Most TIFF files will have image-description, but it'll usually be something dull like "created with photoshop 12".

petoor commented 4 years ago

Thank you for the elaboration John. running vipsheader -a "image" from the terminal gives me all the information i'm looking for. Is there a way to call vipsheader -a inside a pyton file and save the metadata as a dict or list?

jcupitt commented 4 years ago

.get() returns the same information. .get_fields() gives you the names of all fields.

metadata = [image.get(field) for field in image.get_fields()]
petoor commented 4 years ago

metadata = [image.get(field) for field in image.get_fields()] returns [] for both the .ndpi file and the ome.tif file vipsheader -a works for both.

petoor commented 4 years ago

using your multi-channel-z-series.ome.tif file as test

jcupitt commented 4 years ago

Oh, strange, it works for me:

>>> x = pyvips.Image.new_from_file("multi-channel-z-series.ome.tif")
>>> x.get_fields()
['width', 'height', 'bands', 'format', 'coding', 'interpretation', 'xoffset', 'yoffset', 'xres', 'yres', 'filename', 'vips-loader', 'n-pages', 'image-description', 'resolution-unit', 'orientation']
>>> [x.get(field) for field in x.get_fields()]
[439, 167, 1, 'char', 'none', 'b-w', 0, 0, 0.0, 0.0, ...

Could something else be wrong?

A hash might be more useful, of course:

>>> {field: x.get(field) for field in x.get_fields()}
{'width': 439, 'height': 167, 'bands': 1, 'format': 'char', ...
petoor commented 4 years ago

Actually, the vipsheader -a multi-channel-z-series.ome.tif returns : width: 439 height: 167 bands: 1 format: 1 - char coding: 0 - none interpretation: 1 - b-w

Shouldn't this be 3 bands? Since the image contains 3 color channels. Or is the interpretation bands /= color channels?

petoor commented 4 years ago

Output from terminal : Python 3.6.9 (default, Nov 7 2019, 10:44:02) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. import pyvips img_path = '/home/peter/Downloads/multi-channel-z-series.ome.tif' image = pyvips.Image.new_from_file(img_path) image.get_fields() [] {field: image.get(field) for field in image.get_fields()} {}

Version : Name: pyvips Version: 2.1.11 Summary: binding for the libvips image processing library, API mode Home-page: https://github.com/libvips/pyvips Author: John Cupitt Author-email: jcupitt@gmail.com License: MIT Location: /home/peter/.local/lib/python3.6/site-packages Requires: cffi, pkgconfig Required-by:

jcupitt commented 4 years ago

Do you have an old libvips? I see:

>>> pyvips.version(0)
8
>>> pyvips.version(1)
9
>>> pyvips.version(2)
1

ie. my pyvips is using libvips version 8.9.1.

jcupitt commented 4 years ago

OME puts the colour into separate planes, so you need to extract and recombine layers to get a recognisable colour image (I think).

petoor commented 4 years ago

pyvips.version(0) 8 pyvips.version(1) 4 pyvips.version(2) 5

With the sudo apt install libvips libvips-dev this should be the newest version. I can try updating tomorrow. It should be noted that image.get('width') for instance works fine. It is just image.get_fields() that returns an empty list.

Regarding the tif files, I think putting the number of color channels to be the number of bands in the vipsheader is the correct thing to do. However, i could be wrong here.

jcupitt commented 4 years ago

Ah that's very old, you'll probably need to build libvips from source. It's not hard -- it's a standard autotools package.

The libvips header fields are describing the exact layout of the tiff file, so bands really is 1. OME TIFF stores RGB images like this:

RRR
RRR
RRR
GGG
GGG
GGG
BBB
BBB
BBB

But everyone else stores like this:

RGBRGBRGB
RGBRGBRGB
RGBRGBRGB

So you'll need to do a bit of data reorganisation if you want to make a JPG (for example).

petoor commented 4 years ago

I can confirm that your code works after updating to 8.9.1.

Updating was easy following https://github.com/libvips/libvips/wiki/Build-for-Ubuntu

It should be noted that for python 3.6 the line 'export PYTHONPATH=$VIPSHOME/lib/python2.7/site-packages' should be 'export PYTHONPATH=$VIPSHOME/lib/python3.6/site-packages' and the user should remember to do source .bashrc after updating it.

I also used ./autogen.sh --prefix=/home/peter/vips but this can be excluded out from the guide.

petoor commented 4 years ago

actually, if you add the /home/user_name/vips folder to the prefix argument i guess it shouldn't be sudo make install but make install. I guess for simplification the --prefix should only be mentioned as an option.

jcupitt commented 4 years ago

Glad it worked!

Anyone can edit that wiki page, if you'd like to add your notes.

petoor commented 4 years ago

Hi again.

If i want to convert the ome.tiff with 15 pages into 5 images with RGB color. I can use something like

# c = 3, z = 5.
# band_nr = img_metadata.get('n-pages') // c
for i in range(1, c):
    band = pyvips.Image.tiffload(photo.file_original.path, page=z+i*band_nr)
    image = image.bandjoin(band)

Which results : <pyvips.Image 439x167 char, 3 bands, b-w> (times 5 in this case)

Is there any way to convert b-w to rgb? image.colourspace("rgb") yields vips_colourspace: no known route from 'b-w' to 'rgb'

jcupitt commented 4 years ago

Hi again,

It's called srgb, so:

y = x.colourspace("srgb")

Though that won't work for you -- you have a three band image tagged as b-w, so libvips thinks it is a mono image with two alpha channels. You need to simply change the tag so your three bands are interpreted as RGB.

You can change the tag on an image with copy, for example:

y = x.copy(interpretation="srgb")

Now you should have a three band image tagged as srgb.