speed up openslide read

fix a problem with hints for lazy read
This commit is contained in:
John Cupitt 2011-12-10 21:48:34 +00:00
parent cace9d5da8
commit c54bfddfee
4 changed files with 73 additions and 480 deletions

54
TODO
View File

@ -1,10 +1,56 @@
- openslide.c:fill_region() should not allocate memory
- why does openslide make argb? can we ask for rgb instead?
alternatively, have a new coding type and a separate unpack operation
can we get the underlying tile size? we could use that to size the vips tile
cache
can we size the openslide tile cache? needs to hold enough tiles for a
complete line in the image
with dhint fixed, no tile cache (vips is asking for 128x128 tiles, so
everything gets decoded 4 times)
$ time vips im_copy CMU-2.svs x.v
real 15m27.833s
user 5m4.907s
sys 1m31.110s
dhint fix plus a tile cache
$ time vips im_copy CMU-2.svs x.v
real 9m4.254s
user 3m36.842s
sys 0m46.271s
read directly to vips tile cache, no unpacking
$ time vips im_copy CMU-2.svs x.v
real 7m23.040s
user 2m44.262s
sys 0m47.791s
copy from a 256x256 tiled tiff
$ time vips im_copy cmu-2.tif x.v
real 6m4.798s
user 0m49.567s
sys 0m17.973s
copy from a 128x128 tiled tiff
$ vips im_copy x.v cmu-2.tif:jpeg,tile
$ time vips im_copy cmu-2.tif x.v
real 3m32.521s
user 0m55.295s
sys 0m11.693s
also, cairo fails for very long lines, fill piecewise
tried with a tile cache and performance is pretty bad
try just piecewise fill?

View File

@ -1,463 +0,0 @@
/* Read a virtual microscope slide using OpenSlide.
*
* Benjamin Gilbert
*
* Copyright (c) 2011 Carnegie Mellon University
*
* 26/11/11
* - initial version
* 27/11/11
* - fix black background in transparent areas
* - no need to set *stop on fill_region() error return
* - add OpenSlide properties to image metadata
* - consolidate setup into one function
* - support reading arbitrary layers
* - use VIPS_ARRAY()
* - add helper to copy a line of pixels
* - support reading associated images
* 7/12/11
* - redirect OpenSlide error logging to vips_error()
* 8/12/11
* - add more exposition to documentation
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
#define VIPS_DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#ifdef HAVE_OPENSLIDE
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
#include <vips/vips.h>
#include <vips/debug.h>
#include <openslide.h>
typedef struct {
openslide_t *osr;
uint32_t background;
const char *associated;
/* Only valid if associated == NULL.
*/
int32_t layer;
double downsample;
} ReadSlide;
static void
readslide_destroy_cb( VipsImage *image, ReadSlide *rslide )
{
VIPS_FREEF( openslide_close, rslide->osr );
}
static int
check_associated_image( openslide_t *osr, const char *name )
{
const char * const *associated;
for( associated = openslide_get_associated_image_names( osr );
*associated != NULL; associated++ )
if( strcmp( *associated, name ) == 0 )
return( 0 );
vips_error( "im_openslide2vips",
"%s", _( "invalid associated image name" ) );
return( -1 );
}
static ReadSlide *
readslide_new( const char *filename, VipsImage *out )
{
ReadSlide *rslide;
char name[FILENAME_MAX];
char mode[FILENAME_MAX];
const char *background;
char *endp;
int64_t w, h;
const char * const *properties;
char *associated;
rslide = VIPS_NEW( out, ReadSlide );
memset( rslide, 0, sizeof( *rslide ) );
g_signal_connect( out, "close", G_CALLBACK( readslide_destroy_cb ),
rslide );
vips_filename_split( filename, name, mode );
rslide->osr = openslide_open( name );
if( rslide->osr == NULL ) {
vips_error( "im_openslide2vips",
"%s", _( "failure opening slide" ) );
return( NULL );
}
background = openslide_get_property_value( rslide->osr,
OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR );
if( background != NULL )
rslide->background = strtoul( background, NULL, 16 );
else
rslide->background = 0xffffff;
/* Parse optional mode.
*/
rslide->layer = strtol( mode, &endp, 10 );
if( *mode != 0 && *endp == 0 ) {
/* Mode specifies slide layer.
*/
if( rslide->layer < 0 || rslide->layer >=
openslide_get_layer_count( rslide->osr ) ) {
vips_error( "im_openslide2vips",
"%s", _( "invalid slide layer" ) );
return( NULL );
}
}
else if( *mode != 0 ) {
/* Mode specifies associated image.
*/
if ( check_associated_image( rslide->osr, mode ) )
return( NULL );
rslide->associated = vips_strdup( VIPS_OBJECT( out ), mode );
}
if( rslide->associated ) {
openslide_get_associated_image_dimensions( rslide->osr,
rslide->associated, &w, &h );
vips_image_set_string( out, "slide-associated-image",
rslide->associated );
}
else {
openslide_get_layer_dimensions( rslide->osr, rslide->layer,
&w, &h );
rslide->downsample = openslide_get_layer_downsample(
rslide->osr, rslide->layer );
vips_image_set_int( out, "slide-layer", rslide->layer );
}
if( w < 0 || h < 0 || rslide->downsample < 0 ) {
vips_error( "im_openslide2vips", _( "getting dimensions: %s" ),
openslide_get_error( rslide->osr ) );
return( NULL );
}
if( w > INT_MAX || h > INT_MAX ) {
vips_error( "im_openslide2vips",
"%s", _( "image dimensions overflow int" ) );
return( NULL );
}
vips_image_init_fields( out, (int) w, (int) h, 4, VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, VIPS_INTERPRETATION_RGB, 1.0, 1.0 );
for( properties = openslide_get_property_names( rslide->osr );
*properties != NULL; properties++ )
vips_image_set_string( out, *properties,
openslide_get_property_value( rslide->osr,
*properties ) );
associated = g_strjoinv( ", ", (char **)
openslide_get_associated_image_names( rslide->osr ) );
vips_image_set_string( out, "slide-associated-images", associated );
g_free( associated );
return( rslide );
}
static void
copy_line( ReadSlide *rslide, uint32_t *in, int count, PEL *out )
{
uint8_t a;
int i;
for( i = 0; i < count; i++ ) {
/* Convert from ARGB to RGBA and undo premultiplication.
*/
a = in[i] >> 24;
if( a != 0 ) {
out[4 * i + 0] = 255 * ((in[i] >> 16) & 255) / a;
out[4 * i + 1] = 255 * ((in[i] >> 8) & 255) / a;
out[4 * i + 2] = 255 * (in[i] & 255) / a;
}
else {
/* Use background color.
*/
out[4 * i + 0] = (rslide->background >> 16) & 255;
out[4 * i + 1] = (rslide->background >> 8) & 255;
out[4 * i + 2] = rslide->background & 255;
}
out[4 * i + 3] = a;
}
}
static int
fill_region( VipsRegion *out, void *seq, void *_rslide, void *unused,
gboolean *stop )
{
ReadSlide *rslide = _rslide;
uint32_t *buf;
const char *error;
int y;
VIPS_DEBUG_MSG( "openslide.c:fill_region: %d x %d @ %d x %d\n",
out->valid.width, out->valid.height,
out->valid.left, out->valid.top );
/* We should really not alloc in the render thread. But
* openslide_read_region() does a lot of allocing anyway, so we might
* as well too.
*/
buf = VIPS_ARRAY( NULL, out->valid.width * out->valid.height,
uint32_t );
openslide_read_region( rslide->osr, buf,
out->valid.left * rslide->downsample,
out->valid.top * rslide->downsample, rslide->layer,
out->valid.width, out->valid.height );
for( y = 0; y < out->valid.height; y++ )
copy_line( rslide, buf + y * out->valid.width,
out->valid.width,
VIPS_REGION_ADDR_TOPLEFT( out ) +
y * VIPS_REGION_LSKIP( out ) );
vips_free( buf );
error = openslide_get_error( rslide->osr );
if( error ) {
vips_error( "im_openslide2vips", _( "reading region: %s" ),
error );
return( -1 );
}
return( 0 );
}
static int
fill_associated( VipsImage *out, ReadSlide *rslide )
{
uint32_t *buf;
PEL *line;
int64_t w, h;
int y;
const char *error;
openslide_get_associated_image_dimensions( rslide->osr,
rslide->associated, &w, &h );
if( w == -1 || h == -1 ) {
vips_error( "im_openslide2vips", _( "getting dimensions: %s" ),
openslide_get_error( rslide->osr ) );
return( -1 );
}
buf = VIPS_ARRAY( out, w * h, uint32_t );
line = VIPS_ARRAY( out, VIPS_IMAGE_SIZEOF_LINE( out ), PEL );
openslide_read_associated_image( rslide->osr, rslide->associated,
buf );
for( y = 0; y < h; y++ ) {
copy_line( rslide, buf + y * w, w, line );
if( vips_image_write_line( out, y, line ) )
return( -1 );
}
error = openslide_get_error( rslide->osr );
if( error ) {
vips_error( "im_openslide2vips",
_( "reading associated image: %s" ), error );
return( -1 );
}
return( 0 );
}
static int
openslide2vips_header( const char *filename, VipsImage *out )
{
ReadSlide *rslide;
if( !(rslide = readslide_new( filename, out )) )
return( -1 );
return( 0 );
}
/**
* im_openslide2vips:
* @filename: file to load
* @out: image to write to
*
* Read a virtual slide supported by the OpenSlide library into a VIPS image.
* OpenSlide supports images in Aperio, Hamamatsu VMS, Hamamatsu VMU, MIRAX,
* and Trestle formats. It also supports generic tiled TIFF images, but
* im_openslide2vips() does not.
*
* To facilitate zooming, virtual slide formats include multiple scaled-down
* versions of the high-resolution image. These are typically called
* "levels", though OpenSlide and im_openslide2vips() call them "layers".
* By default, im_openslide2vips() reads the highest-resolution layer
* (layer 0). To read a different layer, specify the layer number as part
* of the filename (for example, "CMU-1.mrxs:3").
*
* In addition to the slide image itself, virtual slide formats sometimes
* include additional images, such as a scan of the slide's barcode.
* OpenSlide calls these "associated images". To read an associated image,
* specify the image's name as part of the filename (for example,
* "CMU-1.mrxs:label"). A slide's associated images are listed in the
* "slide-associated-images" metadata item.
*
* See also: #VipsFormat
*
* Returns: 0 on success, -1 on error.
*/
static int
im_openslide2vips( const char *filename, VipsImage *out )
{
ReadSlide *rslide;
VIPS_DEBUG_MSG( "im_openslide2vips: %s\n", filename );
if( !(rslide = readslide_new( filename, out )) )
return( -1 );
if( rslide->associated ) {
if( vips_image_wio_output( out ) )
return( -1 );
VIPS_DEBUG_MSG( "fill_associated:\n" );
return( fill_associated( out, rslide ) );
}
else {
if( vips_image_pio_output( out ) )
return( -1 );
vips_demand_hint( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL );
return( vips_image_generate( out,
NULL, fill_region, NULL, rslide, NULL ) );
}
}
static int
isslide( const char *filename )
{
openslide_t *osr;
const char *vendor;
int ok;
ok = 1;
osr = openslide_open( filename );
if( osr != NULL ) {
/* If this is a generic tiled TIFF image, decline to support
* it, since im_tiff2vips can do better.
*/
vendor = openslide_get_property_value( osr,
OPENSLIDE_PROPERTY_NAME_VENDOR );
if( vendor == NULL ||
strcmp( vendor, "generic-tiff" ) == 0 )
ok = 0;
openslide_close( osr );
}
else
ok = 0;
VIPS_DEBUG_MSG( "isslide: %s - %d\n", filename, ok );
return( ok );
}
static VipsFormatFlags
slide_flags( const char *filename )
{
char name[FILENAME_MAX];
char mode[FILENAME_MAX];
char *endp;
vips_filename_split( filename, name, mode );
strtol( mode, &endp, 10 );
if( *mode == 0 || *endp == 0 )
/* Slide layer or no mode specified.
*/
return( VIPS_FORMAT_PARTIAL );
else
/* Associated image specified.
*/
return( 0 );
}
static void
error_handler( const char *domain, GLogLevelFlags level, const char *message,
void *data )
{
vips_error( "im_openslide2vips", "%s", message );
}
/* openslide format adds no new members.
*/
typedef VipsFormat VipsFormatOpenslide;
typedef VipsFormatClass VipsFormatOpenslideClass;
static const char *slide_suffs[] = {
".svs", /* Aperio */
".vms", ".vmu", /* Hamamatsu */
".mrxs", /* MIRAX */
".tif", /* Trestle */
NULL
};
static void
vips_format_openslide_class_init( VipsFormatOpenslideClass *class )
{
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsFormatClass *format_class = (VipsFormatClass *) class;
object_class->nickname = "openslide";
object_class->description = _( "OpenSlide-supported" );
format_class->is_a = isslide;
format_class->header = openslide2vips_header;
format_class->load = im_openslide2vips;
format_class->get_flags = slide_flags;
format_class->suffs = slide_suffs;
/* Some TIFF files are virtual slides with odd vendor extensions
* (or outright format violations!). Ensure we look at them before
* im_tiff2vips does. OpenSlide tries very hard to reject files it
* doesn't understand, so this should be safe.
*/
format_class->priority = 100;
g_log_set_handler( "Openslide",
G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING,
error_handler, NULL );
}
static void
vips_format_openslide_init( VipsFormatOpenslide *object )
{
}
G_DEFINE_TYPE( VipsFormatOpenslide, vips_format_openslide, VIPS_TYPE_FORMAT );
#endif /*HAVE_OPENSLIDE*/

View File

@ -210,26 +210,29 @@ seq_start( VipsImage *out, void *a, void *b )
static void
copy_line( ReadSlide *rslide, uint32_t *in, int count, PEL *out )
{
uint8_t a;
int i;
for( i = 0; i < count; i++ ) {
uint32_t x = in[i];
uint8_t a = x >> 24;
/* Convert from ARGB to RGBA and undo premultiplication.
*/
a = in[i] >> 24;
if( a != 0 ) {
out[4 * i + 0] = 255 * ((in[i] >> 16) & 255) / a;
out[4 * i + 1] = 255 * ((in[i] >> 8) & 255) / a;
out[4 * i + 2] = 255 * (in[i] & 255) / a;
out[0] = 255 * ((x >> 16) & 255) / a;
out[1] = 255 * ((x >> 8) & 255) / a;
out[2] = 255 * (x & 255) / a;
}
else {
/* Use background color.
*/
out[4 * i + 0] = (rslide->background >> 16) & 255;
out[4 * i + 1] = (rslide->background >> 8) & 255;
out[4 * i + 2] = rslide->background & 255;
out[0] = (rslide->background >> 16) & 255;
out[1] = (rslide->background >> 8) & 255;
out[2] = rslide->background & 255;
}
out[4 * i + 3] = a;
out[3] = a;
out += 4;
}
}
@ -255,6 +258,11 @@ fill_region( VipsRegion *out, void *seq, void *_rslide, void *unused,
int h = VIPS_MIN( TILE_HEIGHT, r->height - y );
openslide_read_region( rslide->osr,
/* or read directly to the output with this:
VIPS_REGION_ADDR( out,
r->left + x,
r->top + y ),
*/
buf,
(r->left + x) * rslide->downsample,
(r->top + y) * rslide->downsample,

View File

@ -621,13 +621,15 @@ vips_image_open_lazy( VipsImage *image,
lazy = lazy_new( image, format, filename, disc );
/* Read header fields to init the return image. THINSTRIP since this is
* probably a disc file. We can't tell yet whether we will be opening
* to memory, sadly, so we can't suggest ANY.
/* Read header fields to init the return image. We rely on ->header()
* to set the demand hint. A tiled image might set SMALLTILE, a
* linear image might set THINSTRIP. Call hint ourselves in case the
* ->header() calls leaves it at the default and does not set
* ->hint_set.
*/
if( format->header( filename, image ) )
return( -1 );
vips_demand_hint( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
vips_demand_hint( image, image->dhint, NULL );
/* Then 'start' creates the real image and 'gen' paints 'out' with
* pixels from the real image on demand.