From 28391dbfc7b96f0f9ba80b4b89e2bc51b7a03147 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 27 Jan 2018 16:43:58 +0000 Subject: [PATCH] option to attach associated images as metadata you now see: ``` $ vipsheader -a CMU-1.svs[attach_associated] | grep ass openslide.associated.label: 387x463 uchar, 4 bands, rgb openslide.associated.macro: 1280x431 uchar, 4 bands, rgb openslide.associated.thumbnail: 1024x732 uchar, 4 bands, rgb slide-associated-images: label, macro, thumbnail ``` --- ChangeLog | 1 + libvips/foreign/openslide2vips.c | 165 ++++++++++++++++++++++--------- libvips/foreign/openslideload.c | 33 +++++-- libvips/foreign/pforeign.h | 5 +- 4 files changed, 149 insertions(+), 55 deletions(-) diff --git a/ChangeLog b/ChangeLog index ee538a1a..9ad5afc1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,7 @@ - dzsave to szi sets suffix correctly [martinweihrauch] - dzsave szi writes "scan-properties.xml" - add vips_image_(get|set)_image() +- add openslideload option to attach all associated images as metadata 5/1/18 started 8.6.2 - vips_sink_screen() keeps a ref to the input image ... stops a rare race diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c index af735d03..9e22cb68 100644 --- a/libvips/foreign/openslide2vips.c +++ b/libvips/foreign/openslide2vips.c @@ -49,6 +49,8 @@ * - unpremultiplication speedups for fully opaque/transparent pixels * 18/1/17 * - reorganise to support invalidate on read error + * 27/1/18 + * - option to attach associated images as metadata */ /* @@ -103,6 +105,7 @@ typedef struct { int32_t level; gboolean autocrop; char *associated; + gboolean attach_associated; openslide_t *osr; @@ -229,15 +232,24 @@ get_bounds( openslide_t *osr, VipsRect *rect ) static ReadSlide * readslide_new( const char *filename, VipsImage *out, - int level, gboolean autocrop, const char *associated ) + int level, gboolean autocrop, + const char *associated, gboolean attach_associated ) { ReadSlide *rslide; if( level && associated ) { vips_error( "openslide2vips", - "%s", _( "specify only one of level or associated " - "image" ) ); + "%s", _( "specify only one of level and " + "associated image" ) ); + return( NULL ); + } + + if( attach_associated && + associated ) { + vips_error( "openslide2vips", + "%s", _( "specify only one of attach_assicated and " + "associated image" ) ); return( NULL ); } @@ -251,6 +263,7 @@ readslide_new( const char *filename, VipsImage *out, rslide->level = level; rslide->autocrop = autocrop; rslide->associated = g_strdup( associated ); + rslide->attach_associated = attach_associated; /* Non-crazy defaults, override in _parse() if we can. */ @@ -260,6 +273,94 @@ readslide_new( const char *filename, VipsImage *out, return( rslide ); } +/* Convert from ARGB to RGBA and undo premultiplication. + * + * We throw away transparency. Formats like Mirax use transparent + bg + * colour for areas with no useful pixels. But if we output + * transparent pixels and then convert to RGB for jpeg write later, we + * would have to pass the bg colour down the pipe somehow. The + * structure of dzsave makes this tricky. + * + * We could output plain RGB instead, but that would break + * compatibility with older vipses. + */ +static void +argb2rgba( uint32_t * restrict buf, int n, uint32_t bg ) +{ + const uint32_t pbg = GUINT32_TO_BE( (bg << 8) | 255 ); + + int i; + + for( i = 0; i < n; i++ ) { + uint32_t * restrict p = buf + i; + uint32_t x = *p; + uint8_t a = x >> 24; + VipsPel * restrict out = (VipsPel *) p; + + if( a == 255 ) + *p = GUINT32_TO_BE( (x << 8) | 255 ); + else if( a == 0 ) + /* Use background color. + */ + *p = pbg; + else { + /* Undo premultiplication. + */ + out[0] = 255 * ((x >> 16) & 255) / a; + out[1] = 255 * ((x >> 8) & 255) / a; + out[2] = 255 * (x & 255) / a; + out[3] = 255; + } + } +} + +static int +readslide_attach_associated( ReadSlide *rslide, VipsImage *image ) +{ + const char * const *associated_name; + + for( associated_name = + openslide_get_associated_image_names( rslide->osr ); + *associated_name != NULL; associated_name++ ) { + int64_t w, h; + VipsImage *associated; + uint32_t *p; + const char *error; + char buf[256]; + + associated = vips_image_new_memory(); + openslide_get_associated_image_dimensions( rslide->osr, + *associated_name, &w, &h ); + vips_image_init_fields( associated, w, h, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_RGB, 1.0, 1.0 ); + vips_image_pipelinev( associated, + VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + + if( vips_image_write_prepare( associated ) ) { + g_object_unref( associated ); + return( -1 ); + } + p = (uint32_t *) VIPS_IMAGE_ADDR( associated, 0, 0 ); + openslide_read_associated_image( rslide->osr, + *associated_name, p ); + error = openslide_get_error( rslide->osr ); + if( error ) { + vips_error( "openslide2vips", + _( "reading associated image: %s" ), error ); + g_object_unref( associated ); + return( -1 ); + } + argb2rgba( p, w * h, rslide->bg ); + + vips_snprintf( buf, 256, + "openslide.associated.%s", *associated_name ); + vips_image_set_image( image, buf, associated ); + g_object_unref( associated ); + } + + return( 0 ); +} + static int readslide_parse( ReadSlide *rslide, VipsImage *image ) { @@ -358,6 +459,12 @@ readslide_parse( ReadSlide *rslide, VipsImage *image ) w = rslide->bounds.width; h = rslide->bounds.height; } + + /* Attach all associated images. + */ + if( rslide->attach_associated && + readslide_attach_associated( rslide, image ) ) + return( -1 ); } rslide->bg = 0xffffff; @@ -406,57 +513,19 @@ readslide_parse( ReadSlide *rslide, VipsImage *image ) int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated ) + int level, gboolean autocrop, + char *associated, gboolean attach_associated ) { ReadSlide *rslide; if( !(rslide = readslide_new( filename, - out, level, autocrop, associated )) || + out, level, autocrop, associated, attach_associated )) || readslide_parse( rslide, out ) ) return( -1 ); return( 0 ); } -/* Convert from ARGB to RGBA and undo premultiplication. - * - * We throw away transparency. Formats like Mirax use transparent + bg - * colour for areas with no useful pixels. But if we output - * transparent pixels and then convert to RGB for jpeg write later, we - * would have to pass the bg colour down the pipe somehow. The - * structure of dzsave makes this tricky. - * - * We could output plain RGB instead, but that would break - * compatibility with older vipses. - */ -static void -argb2rgba( uint32_t * restrict buf, int n, uint32_t bg ) -{ - int i; - - for( i = 0; i < n; i++ ) { - uint32_t * restrict p = buf + i; - uint32_t x = *p; - uint8_t a = x >> 24; - VipsPel * restrict out = (VipsPel *) p; - - if( a == 255 ) - *p = GUINT32_TO_BE( (x << 8) | 255 ); - else if( a == 0 ) - /* Use background color. - */ - *p = GUINT32_TO_BE( (bg << 8) | 255 ); - else { - /* Undo premultiplication. - */ - out[0] = 255 * ((x >> 16) & 255) / a; - out[1] = 255 * ((x >> 8) & 255) / a; - out[2] = 255 * (x & 255) / a; - out[3] = 255; - } - } -} - static int vips__openslide_generate( VipsRegion *out, void *_seq, void *_rslide, void *unused, gboolean *stop ) @@ -515,7 +584,7 @@ vips__openslide_generate( VipsRegion *out, int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop ) + int level, gboolean autocrop, gboolean attach_associated ) { ReadSlide *rslide; VipsImage *raw; @@ -524,7 +593,8 @@ vips__openslide_read( const char *filename, VipsImage *out, VIPS_DEBUG_MSG( "vips__openslide_read: %s %d\n", filename, level ); - if( !(rslide = readslide_new( filename, out, level, autocrop, NULL )) ) + if( !(rslide = readslide_new( filename, out, level, autocrop, + NULL, attach_associated )) ) return( -1 ); raw = vips_image_new(); @@ -567,7 +637,8 @@ vips__openslide_read_associated( const char *filename, VipsImage *out, VIPS_DEBUG_MSG( "vips__openslide_read_associated: %s %s\n", filename, associated ); - if( !(rslide = readslide_new( filename, out, 0, FALSE, associated )) ) + if( !(rslide = readslide_new( filename, out, 0, FALSE, + associated, FALSE )) ) return( -1 ); /* Memory buffer. Get associated directly to this, then copy to out. diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c index b9380db2..a5e617ec 100644 --- a/libvips/foreign/openslideload.c +++ b/libvips/foreign/openslideload.c @@ -9,6 +9,8 @@ * 20/9/12 * - add Leica filename suffix * - drop glib log handler (unneeded with >= 3.3.0) + * 27/1/18 + * - option to attach associated images as metadata */ /* @@ -74,10 +76,14 @@ typedef struct _VipsForeignLoadOpenslide { */ gboolean autocrop; - /* Load this associated image. + /* Load just this associated image. */ char *associated; + /* Attach all associated images as metadata items. + */ + gboolean attach_associated; + } VipsForeignLoadOpenslide; typedef VipsForeignLoadClass VipsForeignLoadOpenslideClass; @@ -114,7 +120,7 @@ vips_foreign_load_openslide_header( VipsForeignLoad *load ) if( vips__openslide_read_header( openslide->filename, load->out, openslide->level, openslide->autocrop, - openslide->associated ) ) + openslide->associated, openslide->attach_associated ) ) return( -1 ); VIPS_SETSTR( load->out->filename, openslide->filename ); @@ -129,7 +135,8 @@ vips_foreign_load_openslide_load( VipsForeignLoad *load ) if( !openslide->associated ) { if( vips__openslide_read( openslide->filename, load->real, - openslide->level, openslide->autocrop ) ) + openslide->level, openslide->autocrop, + openslide->attach_associated ) ) return( -1 ); } else { @@ -210,6 +217,14 @@ vips_foreign_load_openslide_class_init( VipsForeignLoadOpenslideClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadOpenslide, associated ), NULL ); + + VIPS_ARG_BOOL( class, "attach-associated", 13, + _( "Attach associated" ), + _( "Attach all asssociated images" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslide, attach_associated ), + FALSE ); + } static void @@ -227,9 +242,10 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * * Optional arguments: * - * * @level: load this level - * * @associated: load this associated image - * * @autocrop: crop to image bounds + * * @level: %gint, load this level + * * @associated: %gchararray, load this associated image + * * @attach_associated: %gboolean, attach all associated images as metadata + * * @autocrop: %gboolean, crop to image bounds * * Read a virtual slide supported by the OpenSlide library into a VIPS image. * OpenSlide supports images in Aperio, Hamamatsu, MIRAX, Sakura, Trestle, @@ -247,6 +263,11 @@ vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) * A slide's associated images are listed in the * "slide-associated-images" metadata item. * + * If you set @attach_associated, then all associated images are attached as + * metadata items. Use vips_image_get_image() on @out to retrieve them. Images + * are attached as "openslide-associated-XXXXX", where XXXXX is the name of the + * associated image. + * * The output of this operator is always RGBA. * * See also: vips_image_new_from_file(). diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 08973569..d3c1c9a9 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -233,9 +233,10 @@ int vips__webp_write_buffer( VipsImage *out, void **buf, size_t *len, int vips__openslide_isslide( const char *filename ); int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, char *associated ); + int level, gboolean autocrop, + char *associated, gboolean attach_associated ); int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop ); + int level, gboolean autocrop, gboolean attach_associated ); int vips__openslide_read_associated( const char *filename, VipsImage *out, const char *associated );