Merge pull request #6 from bgilbert/openslide

Support reading virtual slide files with OpenSlide
This commit is contained in:
John Cupitt 2011-12-09 04:12:54 -08:00
commit 9d8f0188d2
5 changed files with 463 additions and 4 deletions

4
README
View File

@ -140,6 +140,10 @@ OpenEXR
if available, libvips will directly read (but not write, sadly) if available, libvips will directly read (but not write, sadly)
OpenEXR images OpenEXR images
OpenSlide
if available, libvips can load OpenSlide-supported virtual slide
files: Aperio, Hamamatsu VMS and VMU, MIRAX, and Trestle
swig swig
python python
python-dev python-dev

View File

@ -438,6 +438,20 @@ if test x"$with_OpenEXR" != "xno"; then
]) ])
fi fi
# OpenSlide
AC_ARG_WITH([openslide],
AS_HELP_STRING([--without-openslide], [build without OpenSlide (default: test)]))
if test x"$with_openslide" != "xno"; then
PKG_CHECK_MODULES(OPENSLIDE, openslide >= 3.2.0,
[AC_DEFINE(HAVE_OPENSLIDE,1,[define if you have OpenSlide >= 3.2.0 installed.])
with_openslide=yes
PACKAGES_USED="$PACKAGES_USED openslide"],
[AC_MSG_WARN([OpenSlide >= 3.2.0 not found; disabling virtual slide support])
with_openslide=no
])
fi
# matio # matio
AC_ARG_WITH([matio], AC_ARG_WITH([matio],
AS_HELP_STRING([--without-matio], [build without matio (default: test)])) AS_HELP_STRING([--without-matio], [build without matio (default: test)]))
@ -598,14 +612,14 @@ fi
# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS and VIPS_CXX_LIBS # Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS and VIPS_CXX_LIBS
# sort includes to get longer, more specific dirs first # sort includes to get longer, more specific dirs first
# helps, for example, selecting graphicsmagick over imagemagick # helps, for example, selecting graphicsmagick over imagemagick
VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $PANGOFT2_CFLAGS $FFTW3_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $OPENEXR_CFLAGS $ORC_CFLAGS VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $PANGOFT2_CFLAGS $FFTW3_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS
do do
echo $i echo $i
done | sort -ru` done | sort -ru`
VIPS_CFLAGS=`echo $VIPS_CFLAGS` VIPS_CFLAGS=`echo $VIPS_CFLAGS`
VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS"
VIPS_INCLUDES="$PNG_INCLUDES $TIFF_INCLUDES $ZIP_INCLUDES $JPEG_INCLUDES $FFTW_INCLUDES $LCMS_INCLUDES" VIPS_INCLUDES="$PNG_INCLUDES $TIFF_INCLUDES $ZIP_INCLUDES $JPEG_INCLUDES $FFTW_INCLUDES $LCMS_INCLUDES"
VIPS_LIBS="$MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $ZIP_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $PANGOFT2_LIBS $FFTW3_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $OPENEXR_LIBS $CFITSIO_LIBS $MATIO_LIBS $EXIF_LIBS -lm" VIPS_LIBS="$MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $ZIP_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $PANGOFT2_LIBS $FFTW3_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $MATIO_LIBS $EXIF_LIBS -lm"
# we need this to generate paths in swig/python/setup.py.in # we need this to generate paths in swig/python/setup.py.in
AC_SUBST(top_srcdir) AC_SUBST(top_srcdir)
@ -681,6 +695,7 @@ accelerate loops with orc: $with_orc
(requires orc-0.4.11 or later) (requires orc-0.4.11 or later)
ICC profile support with lcms: $with_lcms (version $with_lcms_ver) ICC profile support with lcms: $with_lcms (version $with_lcms_ver)
file import with OpenEXR: $with_OpenEXR file import with OpenEXR: $with_OpenEXR
file import with OpenSlide: $with_openslide
file import with matio: $with_matio file import with matio: $with_matio
file import with cfitsio: $with_cfitsio file import with cfitsio: $with_cfitsio
text rendering with pangoft2: $with_pangoft2 text rendering with pangoft2: $with_pangoft2

View File

@ -22,6 +22,7 @@ libformat_la_SOURCES = \
im_vips2raw.c \ im_vips2raw.c \
matlab.c \ matlab.c \
fits.c \ fits.c \
radiance.c radiance.c \
openslide.c
INCLUDES = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ INCLUDES = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@

View File

@ -57,7 +57,7 @@
* transparently supported by im_open(). * transparently supported by im_open().
* *
* VIPS comes with VipsFormat for TIFF, JPEG, PNG, Analyze, PPM, OpenEXR, CSV, * VIPS comes with VipsFormat for TIFF, JPEG, PNG, Analyze, PPM, OpenEXR, CSV,
* Matlab, Radiance, RAW, VIPS and one that wraps libMagick. * Matlab, Radiance, RAW, VIPS and ones that wrap libMagick and OpenSlide.
*/ */
/** /**
@ -464,6 +464,10 @@ im__format_init( void )
extern GType vips_format_exr_get_type(); extern GType vips_format_exr_get_type();
vips_format_exr_get_type(); vips_format_exr_get_type();
#endif /*HAVE_OPENEXR*/ #endif /*HAVE_OPENEXR*/
#ifdef HAVE_OPENSLIDE
extern GType vips_format_openslide_get_type();
vips_format_openslide_get_type();
#endif /*HAVE_OPENSLIDE*/
#ifdef HAVE_MATIO #ifdef HAVE_MATIO
extern GType vips_format_mat_get_type(); extern GType vips_format_mat_get_type();
vips_format_mat_get_type(); vips_format_mat_get_type();

435
libvips/format/openslide.c Normal file
View File

@ -0,0 +1,435 @@
/* 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
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#ifdef HAVE_OPENSLIDE
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
#include <openslide.h>
#include <vips/vips.h>
#include <vips/intl.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", _( "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", _( "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",
_( "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",
_( "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;
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( NULL, w * h, uint32_t );
line = VIPS_ARRAY( NULL, 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 )) {
vips_free( line );
vips_free( buf );
return( -1 );
}
}
vips_free( line );
vips_free( buf );
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;
rslide = readslide_new( filename, out );
if( rslide == NULL )
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;
rslide = readslide_new( filename, out );
if( rslide == NULL )
return( -1 );
if( rslide->associated ) {
if( vips_image_wio_output( out ))
return( -1 );
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;
}
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*/