'gdouble' is invalid or out of range for property 'xres'
When working with medical slides, the resolution is often specified in mm or even nm scale. resunit only accepts cm and inches, so by creating a WSI using tiffsave the values becmoe very small:
slide.tiffsave('slide.tiff', compression='jpeg', Q=80, tile=True, tile_width=128, tile_height=128, pyramid=True, depth=3,
xres=0.00002333, yres=0.00002333, resunit='cm')
The values fall back to 28.3465 pixels/cm with a warning:
GLib-GObject-WARNING **: 17:22:50.793: value "0.000023" of type 'gdouble' is invalid or out of range for property 'xres' of type 'gdouble'
GLib-GObject-WARNING **: 17:22:50.793: value "0.000023" of type 'gdouble' is invalid or out of range for property 'yres' of type 'gdouble'
Hi @karray,
You're right, these values are sanity checked in the tiffsave API to be > 0.001. I'll change it, thanks for reporting this issue.
As a workaround, you can set the resolution on the image, rather than in the tiff saver. For example:
slide = slide.copy(xres=0.0002333, yres=0.0002333)
slide.tiffsave('x.tif', compression='jpeg', Q=80, tile=True, pyramid=True, depth=3, resunit='cm')
.copy() always works in pixels per millimetre.
https://www.libvips.org/API/current/libvips-conversion.html#vips-copy
Thank you for the fast reply. I tried your workaround but the resolution was set with different values:
$ exiftool slide.tiff
...
X Resolution : 0.009999999776
Y Resolution : 0.009999999776
Resolution Unit : cm
but should be 0.00002333
Also, if I understand correctly, tiffinfo prints the metadata for each of the levels and the resolution on each of them remains the same. Shouldn't it be recalculated accordingly?
Huh you're right, the tiff saver also clips the resolution:
/* Don't write mad resolutions (eg. zero), it confuses some programs.
*/
TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, wtiff->resunit );
TIFFSetField( tif, TIFFTAG_XRESOLUTION, VIPS_FCLIP( 0.01, wtiff->xres, 1000000 ) );
TIFFSetField( tif, TIFFTAG_YRESOLUTION, VIPS_FCLIP( 0.01, wtiff->yres, 1000000 ) );
That'll need changing too. It looks like there's no easy workaround, unfortunately. We should make this change as a bugfix for 8.13.
The tiffsaver writes the same metadata for all pyramid levels, so you'll see the same resolution. Imagine if you were printing the pyramid levels next to each other: would you want the levels to all be printed as the same size on the paper, or would you like the smaller levels to be smaller? It depends.
There's no single correct answer, so libvips avoids automatic resolution changes.
Thinking about this again after a bit more coffee, the xres and yres arguments to copy are in pixels/mm (like DPI). A WSI image might be 100,000 x 100,000 pixels and 2cm each way, so that's 5000 pixels/mm.
Are you perhaps thinking of mm/pixel? I know systems like DICOM use this inverse value.
I use OpenSlide and according to the docs,
mm_x, mm_y- width and height of a pixel in millimeters at the base resolution.
so I think, you are right.
I created a WSI and set the resolution to 0.01 mm:
slide = slide.copy(xres=0.01, yres=0.01)
slide.tiffsave('slide.tiff', compression='jpeg', Q=80, tile=True, tile_width=128, tile_height=128, pyramid=True)
I checked it using exiftool
$ exiftool slide.tiff
...
X Resolution : 0.009999999776
Y Resolution : 0.009999999776
Resolution Unit : cm
and 0.1000000015 cm was converted to 99999998.5098839 nm (~10 cm) in my app:
pixel_size_nm': {
'x': 99999998.5098839,
'y': 99999998.5098839,
}
I'm also wondering why 0.01 mm was converted to 0.1000000015 cm (floating-point rounding error?). If I change resolution using exiftool -Xresolution=0.1 -Yresolution=0.1 slide.tiff, exiftool -v1 slide.tiff prints:
| 7) XResolution = 0.1 (1/10)
| 8) YResolution = 0.1 (1/10)
If using slide.copy:
| 8) XResolution = 0.1000000015 (13421773/134217728)
| 9) YResolution = 0.1000000015 (13421773/134217728)
So it sounds like everything is correct, is that right?
I'm also wondering why 0.01 mm was converted to 0.1000000015 cm (floating-point rounding error?).
Yes, a rounding error, I think. TIFF only uses a float (32 bits) to store resolution.
Yes, I think I solved my problem. Your comment from another discussion helped me