int vips__openslide_read_associated( const char *filename, VipsImage *out, const char *associated ) { ReadSlide *rslide; VipsImage *raw; const char *error; VIPS_DEBUG_MSG( "vips__openslide_read_associated: %s %s\n", filename, associated ); /* Memory buffer. Get associated directly to this, then copy to out. */ raw = vips_image_new_buffer(); vips_object_local( out, raw ); if( !(rslide = readslide_new( filename, raw, 0, associated )) || vips_image_write_prepare( raw ) ) return( -1 ); openslide_read_associated_image( rslide->osr, rslide->associated, (uint32_t *) VIPS_IMAGE_ADDR( raw, 0, 0 ) ); error = openslide_get_error( rslide->osr ); if( error ) { vips_error( "openslide2vips", _( "reading associated image: %s" ), error ); return( -1 ); } if( vips_image_write( raw, out ) ) return( -1 ); return( 0 ); }
static void test_image_fetch(openslide_t *osr, int64_t x, int64_t y, int64_t w, int64_t h) { uint32_t *buf = g_new(uint32_t, w * h); for (int32_t level = 0; level < openslide_get_level_count(osr); level++) { openslide_read_region(osr, buf, x, y, level, w, h); } g_free(buf); const char *err = openslide_get_error(osr); if (err) { fail("Read failed: %"G_GINT64_FORMAT" %"G_GINT64_FORMAT " %"G_GINT64_FORMAT" %"G_GINT64_FORMAT": %s", x, y, w, h, err); } }
static int vips__openslide_generate( VipsRegion *out, void *seq, void *_rslide, void *unused, gboolean *stop ) { ReadSlide *rslide = _rslide; VipsRect *r = &out->valid; const char *error; int x, y; VIPS_DEBUG_MSG( "vips__openslide_generate: %dx%d @ %dx%d\n", r->width, r->height, r->left, r->top ); /* Fill in tile-sized chunks. Some versions of OpenSlide can fail for * very large requests. */ for( y = 0; y < r->height; y += TILE_HEIGHT ) for( x = 0; x < r->width; x += TILE_WIDTH ) { int w = VIPS_MIN( TILE_WIDTH, r->width - x ); int h = VIPS_MIN( TILE_HEIGHT, r->height - y ); openslide_read_region( rslide->osr, (uint32_t *) VIPS_REGION_ADDR( out, r->left + x, r->top + y ), (r->left + x) * rslide->downsample, (r->top + y) * rslide->downsample, rslide->layer, w, h ); } error = openslide_get_error( rslide->osr ); if( error ) { vips_error( "openslide2vips", _( "reading region: %s" ), error ); return( -1 ); } return( 0 ); }
static ReadSlide * readslide_new( const char *filename, VipsImage *out, int level, gboolean autocrop, const char *associated ) { ReadSlide *rslide; int64_t w, h; const char *error; const char *background; const char * const *properties; char *associated_names; if( level && associated ) { vips_error( "openslide2vips", "%s", _( "specify only one of level or associated " "image" ) ); return( NULL ); } rslide = VIPS_NEW( out, ReadSlide ); memset( rslide, 0, sizeof( *rslide ) ); g_signal_connect( out, "close", G_CALLBACK( readslide_destroy_cb ), rslide ); rslide->level = level; rslide->autocrop = autocrop; rslide->associated = g_strdup( associated ); /* Non-crazy defaults, override below if we can. */ rslide->tile_width = 256; rslide->tile_height = 256; rslide->osr = openslide_open( filename ); if( rslide->osr == NULL ) { vips_error( "openslide2vips", "%s", _( "unsupported slide format" ) ); return( NULL ); } error = openslide_get_error( rslide->osr ); if( error ) { vips_error( "openslide2vips", _( "opening slide: %s" ), error ); return( NULL ); } if( level < 0 || level >= openslide_get_level_count( rslide->osr ) ) { vips_error( "openslide2vips", "%s", _( "invalid slide level" ) ); return( NULL ); } if( associated && check_associated_image( rslide->osr, associated ) ) return( NULL ); if( associated ) { openslide_get_associated_image_dimensions( rslide->osr, associated, &w, &h ); vips_image_set_string( out, "slide-associated-image", associated ); vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); } else { char buf[256]; const char *value; openslide_get_level_dimensions( rslide->osr, level, &w, &h ); rslide->downsample = openslide_get_level_downsample( rslide->osr, level ); vips_image_set_int( out, "slide-level", level ); vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ); /* Try to get tile width/height. An undocumented, experimental * feature. */ vips_snprintf( buf, 256, "openslide.level[%d].tile-width", level ); if( (value = openslide_get_property_value( rslide->osr, buf )) ) rslide->tile_width = atoi( value ); vips_snprintf( buf, 256, "openslide.level[%d].tile-height", level ); if( (value = openslide_get_property_value( rslide->osr, buf )) ) rslide->tile_height = atoi( value ); if( value ) VIPS_DEBUG_MSG( "readslide_new: found tile-size\n" ); /* Some images have a bounds in the header. Crop to * that if autocrop is set. */ if( rslide->autocrop ) if( !get_bounds( rslide->osr, &rslide->bounds ) ) rslide->autocrop = FALSE; if( rslide->autocrop ) { VipsRect image; rslide->bounds.left /= rslide->downsample; rslide->bounds.top /= rslide->downsample; rslide->bounds.width /= rslide->downsample; rslide->bounds.height /= rslide->downsample; /* Clip against image size. */ image.left = 0; image.top = 0; image.width = w; image.height = h; vips_rect_intersectrect( &rslide->bounds, &image, &rslide->bounds ); /* If we've clipped to nothing, ignore bounds. */ if( vips_rect_isempty( &rslide->bounds ) ) rslide->autocrop = FALSE; } if( rslide->autocrop ) { w = rslide->bounds.width; h = rslide->bounds.height; } } rslide->bg = 0xffffff; if( (background = openslide_get_property_value( rslide->osr, OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR )) ) rslide->bg = strtoul( background, NULL, 16 ); if( w <= 0 || h <= 0 || rslide->downsample < 0 ) { vips_error( "openslide2vips", _( "getting dimensions: %s" ), openslide_get_error( rslide->osr ) ); return( NULL ); } if( w > INT_MAX || h > INT_MAX ) { vips_error( "openslide2vips", "%s", _( "image dimensions overflow int" ) ); return( NULL ); } if( !rslide->autocrop ) { rslide->bounds.left = 0; rslide->bounds.top = 0; rslide->bounds.width = w; rslide->bounds.height = h; } vips_image_init_fields( out, w, 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_names = g_strjoinv( ", ", (char **) openslide_get_associated_image_names( rslide->osr ) ); vips_image_set_string( out, "slide-associated-images", associated_names ); VIPS_FREE( associated_names ); return( rslide ); }
int main(int argc, char **argv) { if (!g_thread_supported()) { g_thread_init(NULL); } if (argc != 2) { fail("No file specified"); } const char *path = argv[1]; if (g_str_equal(path, "--leak-check--")) { child_check_open_fds(); return 0; } openslide_get_version(); if (!openslide_detect_vendor(path)) { fail("No vendor for %s", path); } openslide_t *osr = openslide_open(path); if (!osr) { fail("Couldn't open %s", path); } const char *err = openslide_get_error(osr); if (err) { fail("Open failed: %s", err); } openslide_close(osr); osr = openslide_open(path); if (!osr || openslide_get_error(osr)) { fail("Reopen failed"); } int64_t w, h; openslide_get_level0_dimensions(osr, &w, &h); int32_t levels = openslide_get_level_count(osr); for (int32_t i = -1; i < levels + 1; i++) { int64_t ww, hh; openslide_get_level_dimensions(osr, i, &ww, &hh); openslide_get_level_downsample(osr, i); } openslide_get_best_level_for_downsample(osr, 0.8); openslide_get_best_level_for_downsample(osr, 1.0); openslide_get_best_level_for_downsample(osr, 1.5); openslide_get_best_level_for_downsample(osr, 2.0); openslide_get_best_level_for_downsample(osr, 3.0); openslide_get_best_level_for_downsample(osr, 3.1); openslide_get_best_level_for_downsample(osr, 10); openslide_get_best_level_for_downsample(osr, 20); openslide_get_best_level_for_downsample(osr, 25); openslide_get_best_level_for_downsample(osr, 100); openslide_get_best_level_for_downsample(osr, 1000); openslide_get_best_level_for_downsample(osr, 10000); // NULL buffer openslide_read_region(osr, NULL, 0, 0, 0, 1000, 1000); // empty region openslide_read_region(osr, NULL, 0, 0, 0, 0, 0); // read properties const char * const *property_names = openslide_get_property_names(osr); while (*property_names) { const char *name = *property_names; openslide_get_property_value(osr, name); property_names++; } // read associated images const char * const *associated_image_names = openslide_get_associated_image_names(osr); while (*associated_image_names) { int64_t w, h; const char *name = *associated_image_names; openslide_get_associated_image_dimensions(osr, name, &w, &h); uint32_t *buf = g_new(uint32_t, w * h); openslide_read_associated_image(osr, name, buf); g_free(buf); associated_image_names++; } test_image_fetch(osr, -10, -10, 200, 200); test_image_fetch(osr, w/2, h/2, 500, 500); test_image_fetch(osr, w - 200, h - 100, 500, 400); test_image_fetch(osr, w*2, h*2, 400, 400); test_image_fetch(osr, w - 20, 0, 40, 100); test_image_fetch(osr, 0, h - 20, 100, 40); // active region const char *bounds_x = openslide_get_property_value(osr, OPENSLIDE_PROPERTY_NAME_BOUNDS_X); const char *bounds_y = openslide_get_property_value(osr, OPENSLIDE_PROPERTY_NAME_BOUNDS_Y); int64_t bounds_xx = 0; int64_t bounds_yy = 0; if (bounds_x && bounds_y) { bounds_xx = g_ascii_strtoll(bounds_x, NULL, 10); bounds_yy = g_ascii_strtoll(bounds_y, NULL, 10); test_image_fetch(osr, bounds_xx, bounds_yy, 200, 200); } openslide_close(osr); check_cloexec_leaks(path, argv[0], bounds_xx, bounds_yy); return 0; }
static void aperio_tiff_tilereader(openslide_t *osr, TIFF *tiff, uint32_t *dest, int64_t x, int64_t y, int32_t w, int32_t h) { // which compression? uint16_t compression_mode; TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression_mode); // not for us? fallback if ((compression_mode != APERIO_COMPRESSION_JP2K_YCBCR) && (compression_mode != APERIO_COMPRESSION_JP2K_RGB)) { _openslide_generic_tiff_tilereader(osr, tiff, dest, x, y, w, h); return; } // else, JPEG 2000! opj_cio_t *stream = NULL; opj_dinfo_t *dinfo = NULL; opj_image_t *image = NULL; opj_image_comp_t *comps = NULL; // note: don't use info_handler, it outputs lots of junk opj_event_mgr_t event_callbacks = { .error_handler = error_callback, .warning_handler = warning_callback, }; // get tile number ttile_t tile_no = TIFFComputeTile(tiff, x, y, 0, 0); // g_debug("aperio reading tile_no: %d", tile_no); // get tile size toff_t *sizes; if (TIFFGetField(tiff, TIFFTAG_TILEBYTECOUNTS, &sizes) == 0) { _openslide_set_error(osr, "Cannot get tile size"); return; // ok, haven't allocated anything yet } tsize_t tile_size = sizes[tile_no]; // a slide with zero-length tiles has been seen in the wild if (!tile_size) { // fill with transparent memset(dest, 0, w * h * 4); //g_debug("skipping tile %d", tile_no); return; // ok, haven't allocated anything yet } // get raw tile tdata_t buf = g_slice_alloc(tile_size); tsize_t size = TIFFReadRawTile(tiff, tile_no, buf, tile_size); if (size == -1) { _openslide_set_error(osr, "Cannot get raw tile"); goto DONE; } // init decompressor opj_dparameters_t parameters; dinfo = opj_create_decompress(CODEC_J2K); opj_set_default_decoder_parameters(¶meters); opj_setup_decoder(dinfo, ¶meters); stream = opj_cio_open((opj_common_ptr) dinfo, buf, size); opj_set_event_mgr((opj_common_ptr) dinfo, &event_callbacks, osr); // decode image = opj_decode(dinfo, stream); // check error if (openslide_get_error(osr)) { goto DONE; } comps = image->comps; // sanity check if (image->numcomps != 3) { _openslide_set_error(osr, "image->numcomps != 3"); goto DONE; } // TODO more checks? copy_aperio_tile(compression_mode, comps, dest, w, h, w / comps[0].w, h / comps[0].h, w / comps[1].w, h / comps[1].h, w / comps[2].w, h / comps[2].h); DONE: // erase g_slice_free1(tile_size, buf); if (image) opj_image_destroy(image); if (stream) opj_cio_close(stream); if (dinfo) opj_destroy_decompress(dinfo); }
static ReadSlide * readslide_new( const char *filename, VipsImage *out, int layer, const char *associated ) { ReadSlide *rslide; int64_t w, h; const char *background; const char * const *properties; rslide = VIPS_NEW( out, ReadSlide ); memset( rslide, 0, sizeof( *rslide ) ); g_signal_connect( out, "close", G_CALLBACK( readslide_destroy_cb ), rslide ); rslide->layer = layer; rslide->associated = g_strdup( associated ); rslide->osr = openslide_open( filename ); if( rslide->osr == NULL ) { vips_error( "openslide2vips", "%s", _( "failure opening slide" ) ); return( NULL ); } if( layer < 0 || layer >= openslide_get_layer_count( rslide->osr ) ) { vips_error( "openslide2vips", "%s", _( "invalid slide level" ) ); return( NULL ); } if( associated && check_associated_image( rslide->osr, associated ) ) return( NULL ); if( associated ) { openslide_get_associated_image_dimensions( rslide->osr, associated, &w, &h ); vips_image_set_string( out, "slide-associated-image", associated ); vips_demand_hint( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); } else { openslide_get_layer_dimensions( rslide->osr, layer, &w, &h ); rslide->downsample = openslide_get_layer_downsample( rslide->osr, layer ); vips_image_set_int( out, "slide-level", layer ); vips_demand_hint( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ); } /* This tag is used by argb2rgba() to paint fully-transparent pixels. */ background = openslide_get_property_value( rslide->osr, OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR ); if( background != NULL ) vips_image_set_int( out, VIPS_META_BACKGROUND_RGB, strtoul( background, NULL, 16 ) ); else vips_image_set_int( out, VIPS_META_BACKGROUND_RGB, 0xffffff ); if( w < 0 || h < 0 || rslide->downsample < 0 ) { vips_error( "openslide2vips", _( "getting dimensions: %s" ), openslide_get_error( rslide->osr ) ); return( NULL ); } if( w > INT_MAX || h > INT_MAX ) { vips_error( "openslide2vips", "%s", _( "image dimensions overflow int" ) ); return( NULL ); } vips_image_init_fields( out, w, 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 ); return( rslide ); }
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { /*variables*/ char *buffer; /*array for input string*/ int32_t levels; /*number of levels*/ int64_t w, h; /*width, height of each level*/ double *dims, *ds; /*pointers to outputs - for convenience*/ const char *objective, *mppx, *mppy; openslide_t *slide; /*openslide struct*/ int i; /*loop iterator*/ /*check input arguments*/ if(nrhs != 1) { mexErrMsgTxt("'openslide_check_levels.m' requires one input argument."); } if(nlhs > 5) { mexErrMsgTxt("'openslide_check_levels.m' produces two outputs."); } /*check input type*/ if(!mxIsChar(prhs[0]) || (mxGetM(prhs[0]) != 1)) { mexErrMsgTxt("Input must be character array."); } /*copy*/ buffer = mxArrayToString(prhs[0]); /*copy input*/ if(openslide_can_open(buffer) == 1) { /*open*/ slide = openslide_open(buffer); /*check for error*/ if(openslide_get_error(slide) != NULL) { mexErrMsgTxt("'openslide_check_levels.m' cannot open slide."); } else { /*get # of levels*/ levels = openslide_get_layer_count(slide); /*allocate arrays*/ plhs[0] = mxCreateDoubleMatrix((mwSize)levels, 2, mxREAL); plhs[1] = mxCreateDoubleMatrix((mwSize)levels, 1, mxREAL); /*get output addresses*/ dims = mxGetPr(plhs[0]); ds = mxGetPr(plhs[1]); /*loop through levels, get dimensions/downsample factor of each*/ for(i = 0; i < levels; i++) { /*get level info*/ openslide_get_layer_dimensions(slide, (int32_t)i, &w, &h); *ds = openslide_get_layer_downsample(slide, (int32_t)i); ds = ds + 1; /*copy dimensions to outputs*/ *dims = (double)h; *(dims+levels) = (double)w; dims = dims + 1; } /*get properties*/ objective = openslide_get_property_value(slide, OPENSLIDE_PROPERTY_NAME_OBJECTIVE_POWER); mppx = openslide_get_property_value(slide, OPENSLIDE_PROPERTY_NAME_MPP_X); mppy = openslide_get_property_value(slide, OPENSLIDE_PROPERTY_NAME_MPP_Y); /*assign properties to outputs*/ plhs[2] = mxCreateString(objective); plhs[3] = mxCreateString(mppx); plhs[4] = mxCreateString(mppy); } } else{ /*can't open slide*/ mexErrMsgTxt("'openslide_check_levels.m' cannot open slide."); } /*free input buffer copy*/ mxFree(buffer); }