diff --git a/BIOP_VSI_reader.ijm b/BIOP_VSI_reader.ijm index ca7069e..0cb7eb1 100644 --- a/BIOP_VSI_reader.ijm +++ b/BIOP_VSI_reader.ijm @@ -1,1502 +1,1509 @@ // Action Bar description file :BIOP_VSI_reader // By Olivier Burri & Romain Guiet // EPFL BIOP 2014 /* * DESCRIPTION: Simple Action Bar to open and make sense of the Olympus OlyVIA Slide Scanner * The macro works as follows * Uses Jerome Mutterer's Action Bar Plugin as the interface. * Takes advantage of the print "Update" Function as of IJ 1.38m to keep data in memory. * Uses LOCI BioFormats to read and parse the data from the VSI reader * * MOTIVATION: Images produced by the OlyVIA Scanner are extremely large (>48'000px) * The inherent limitation of Java (or IJ) for images is about 48'000px * Because the .vsi files have preview thumbnails, we want to use those in order to * navigate the data and open only the part of the image we're interested in. * * * DISCLAIMER: This is a work in progress and some bugs are bound to pop up, don't hesitate to contact us in case you have problems * at olivier.burri at epfl.ch - * Last Update: October 2015 - * As BioFormats can now read the metadata more easily, we should have an easier time with the scaling and series association + * Last Update: January 2018 + * Change Log Jan 2018: + * - Added possibility to save images in RGB or composite solely based on the metadata + * This removes the problem when exporting an entire folder of images with different image types + * Before, one had to cheese Composite or RGB and all images were treated the same way, which was troublesome + * - Changed name of menu item to BIOP VSI Reader + * - Thumbnails now close when exporting all VSI files in batch + * - Multichannel images forced to be saved as RGB are now composited then converted to RGB. + * */ // Action Bar settings sep = File.separator; // Install the BIOP Library call("BIOP_LibInstaller.installLibrary", "BIOP"+sep+"BIOPLib.ijm"); runFrom = "jar:file:BIOP/BIOP_VSI_reader.jar!/BIOP_VSI_reader.ijm"; ////////////////////////////////////////////////////////////////////////////////////////////// // The line below is for debugging. Place this VSI file in the ActionBar folder within Plugins ////////////////////////////////////////////////////////////////////////////////////////////// //runFrom = "/plugins/ActionBar/Debug/BIOP_VSI_reader.ijm"; //print("Running From: "+runFrom); run("Action Bar", runFrom ); exit(); //Start of ActionBar /* * Must be set for the function library to work */ function toolName() { return "VSI Reader"; } /* * Flag to set for debugging */ function isDebug() { return false; } /* * Debug print. If a debug flag is set, then print, otherwise do nothing. */ function dprint(text) { deb = isDebug(); if (deb) { print(text); } } /* * isVSI just checks at the file extension */ function isVSI(filename) { if(endsWith(filename, ".vsi")) { return true; } return false; } function getThumbOffset() { thumbSize = call("ij.Prefs.get", "vsireader.thumbsize", "Tiny"); if ( thumbSize == "Small") { return 1; } else if ( thumbSize == "Medium") { return 2; } return 0; } /* * Setting and recovering the ID of the current file */ function getID() { id = getData("Series ID"); return id; } function setFileID(id) { setData("Series ID",id); dir = substring(id, 0,lastIndexOf(id,File.separator)+1); setData("Image Folder",dir); } /* * Extracts the name of the image from its directory+file string */ function getImageName() { name = getID(); name = substring(name, lastIndexOf(name, File.separator)+1,lengthOf(name)); return name; } /* * Extracts the directory */ function getImageDirectory() { name = getData("Image Folder"); dir = substring(name, 0, lastIndexOf(name, File.separator)+1); return dir; } /* * For the BIOP Needs, this returns the available magnifications * // IS THIS REALLY USEFUL? */ function getAvailableMagnifications() { return newArray(2, 4, 10, 20, 40); } /* * This function makes sense of the data inside the VSI file. How, you ask? * Oli will tell you. * As of 20-10-2015 Loci Bioformats reads the contents of a VSI file as follows * There are up to 3 files with specific unique names * - label: A scan of the slide's label * - overview: a 4X overview of the slide * - macro image: a tiny thumbnail of the slide+label * * The last one is usually always there, the other two are optional. * * After are the pyramidal files that make up the actual SERIES with the images the user wants to extract * These come in many flavors * 4x, 10x, 20x, and 40x are the names that appear on our systems * Other systems provide either a custom name or something else. * * HOWEVER, the files after follow a nice understandable logic * As long as it is part of a pyramid, the files have the following nomenclature * 'filename'.vsi #'number' * * So it is easy to know when we are at the end of a pyramidal series, for example * A Typical VSI file has the following file names * Series: 0 Is called label * Series: 1 Is called Image_02.vsi #2 * Series: 2 Is called Image_02.vsi #3 * Series: 3 Is called Image_02.vsi #4 * Series: 4 Is called Image_02.vsi #5 * Series: 5 Is called Image_02.vsi #6 * Series: 6 Is called overview * Series: 7 Is called Image_02.vsi #8 * Series: 8 Is called Image_02.vsi #9 * Series: 9 Is called Image_02.vsi #10 * Series: 10 Is called Image_02.vsi #11 * Series: 11 Is called Image_02.vsi #12 * Series: 12 Is called Image_02.vsi #13 * Series: 13 Is called 20x * Series: 14 Is called Image_02.vsi #15 * Series: 15 Is called Image_02.vsi #16 * Series: 16 Is called Image_02.vsi #17 * Series: 17 Is called Image_02.vsi #18 * Series: 18 Is called Image_02.vsi #19 * Series: 19 Is called Image_02.vsi #20 * Series: 20 Is called Image_02.vsi #21 * Series: 21 Is called macro image * * So we know that the label has 5 sub-images with Image_02.vsi #6 being the smallest pyramid * Here is what we do: */ function parseSeriesData(fileID) { dprint("Parsing Series Data for "+fileID); start = getTime(); // Run BioFormats Macro Extensions to read all series data run("Bio-Formats Macro Extensions"); setFileID(fileID); // Set the file to use the Bioformats Macro Extensions Ext.setId(fileID); // Get the total number of images in the .VSI file. Ext.getSeriesCount(seriesCount); setData("totSeries", seriesCount); dprint("Total number of series in VSI file: "+seriesCount); // For our needs, we will choose the series name based on the magnification value objectives_str = ".*\\d{1,2}x.*"; // anything followed by either 1 or 2 digits followed by an 'x' followed by anything. // REMOVED IN FAVOR OF A REGULAR EXPRESSION getAvailableMagnifications(); // Starting the search for series based on name nSer = 0; hasLabel= false; hasOverview = false; hasMacro= false; hasMask = false; for(i=0; i 4) ispreselect = false; // Handle basic series for(i=1; i camera) { camera = camera2; } dprint("Camera is: "+camera); // Because getSeriesMetadataValue can return a string or a number we need to be careful when comparing // We cannot use d2s in case because if it IS a string, there will be an error // and we cannot use if(camera == "VC50") directly because if it's a number, then there will be an error if (camera == 0) { setData("Camera Mode", "Unknown"); } else { if(camera == "VC50") { setData("Camera Mode", "Brightfield"); } else { setData("Camera Mode", "Fluorescence"); } } return camera; } // Loads the current selection made on a thumbnail at 100% Size function loadCurrentSelection(rescaling) { step = 1; run("Bio-Formats Macro Extensions"); id = getID(); Ext.setId(id); name = getTitle(); vsiName = substring(name,0, lastIndexOf(name,".vsi")+4); series = parseInt(substring(name,lastIndexOf(name,"#")+1,lastIndexOf(name,"("))); dprint("Loading Selection of Series #"+series); //Get the coordinates of the box getSelectionBounds(x, y, width, height); //And the sizes of the image //Get the sampling factor of the thumbnail. This is actually the pixel size of the thumbnail. getPixelSize(unit, pixelWidth, pixelHeight); mag = parseInt(pixelWidth); dprint("Loading Selection of Series #"+series); serPos = getPositionOfSeries(series); // Magic: This metadata gives us the Camera camera = getCamera(serPos); if ( rescaling%2 == 0) { mag = mag/rescaling; serPos = serPos + round((log(rescaling)/log(2))); } dprint("Series Position at: "+serPos); // Get the size of the new series Ext.setSeries(serPos); Ext.getSizeX(sizeX); Ext.getSizeY(sizeY); oriX = round( (mag * (x)) * (1)); oriY = round( (mag * (y)) * (1)); oriW = ( floor(mag * (width) / step) ) * step; oriH = ( floor(mag * (height) / step) ) * step; // Resize in case the selection is too big in X if (oriX + oriW > sizeX) { neworiW = sizeX - oriX; print("Size of image exceeds image dimensions, cropping:"); print("Original Width: "+oriW+" , New Width: "+neworiW); oriW = neworiW; } // Resize in case the selection is too big in Y if (oriY + oriH > sizeY) { neworiH = sizeY - oriY; print("Size of image exceeds image dimensions, cropping:"); print("Original Height: "+oriH+" , New Height: "+neworiH); oriH = neworiH; } // Append rescaling factor to image name if (rescaling != 1) { rescaleText = " Scaled "+rescaling; } else { rescaleText = ""; } // Make sure that the chosen size is not larger than what imageJ can handle // NOTE, maybe this will change sometime... if( oriW * oriH > 42000*42000) { showMessage("Your selection is too large ("+oriX+"x"+oriY+" > 1.764e9 pixels)\nPlease select a smaller area"); } else if(matches(name,".*Thumbnail.*")) { openLociSubStack(vsiName+ " - Series #"+series+" at ("+oriX+", "+oriY+")"+rescaleText, (serPos), oriX, oriY, oriW , oriH); calibrateImage(series, rescaling); } else { showMessage("Please Select a Thumbnail Image!"); } } /* * Sets the calibration of the image based on the metadata */ function calibrateImage(series, rescaling) { // Get the magnification of this series from the name serPos = getPositionOfSeries(series); Ext.getPixelsPhysicalSizeX(sizeX); cal = sizeX * rescaling; setVoxelSize(cal, cal, cal, "micron"); } /* * openLociStack opens the stack at the given index. This index is the raw index of the image as interpreted by LOCI * It starts at 1 and should be used in conjuction with the "getSeriesIndex... series of functions to ensure it works properly * Based on an example by Wayne Rasband on the LOCI Website */ function openLociStack(name, index) { run("Bio-Formats Macro Extensions"); id = getID(); Ext.setId(id); // Place yourself at the desired series Index (starts at 0); Ext.setSeries(index); // Get ther dimensions Ext.getSizeC(sizeC); Ext.getSizeZ(sizeZ); Ext.getSizeT(sizeT); Ext.getSizeX(sizeX); Ext.getSizeY(sizeY); Ext.getImageCount(n); //Start opening the series setBatchMode(true); for (i=0; i1) { Stack.setDimensions(sizeC, sizeZ, sizeT); if (sizeC>1) { if (sizeC==3&&sizeC==nSlices) mode = "Composite"; else mode = "Color"; run("Make Composite", "display="+mode); } setOption("OpenAsHyperStack", true); } setBatchMode(false); run("Select None"); } /* * Same as above, but opens a sub-region of the image */ function openLociSubStack(name, index, posX, posY, w, h) { run("Bio-Formats Macro Extensions"); id = getID(); Ext.setId(id); dprint("Opening Series "+index+"-"+name+" ("+w+","+h+")"); start = getTime(); Ext.setSeries(index); Ext.getSizeC(sizeC); Ext.getSizeZ(sizeZ); Ext.getSizeT(sizeT); Ext.getImageCount(n); setBatchMode(true); for (i=0; i1) { cMode = getData("Camera Mode"); Stack.setDimensions(sizeC, sizeZ, sizeT); if (sizeC>1 || sizeT>1) { if ( (sizeC==3&&sizeC==nSlices) || (sizeT==3&&sizeT==nSlices)) // BUG: TIME AS CHANNELS? mode = "Composite"; else mode = "Color"; run("Make Composite", "display="+mode); } setOption("OpenAsHyperStack", true); if (cMode == "Brightfield") { for (i=0; i tol) { return round(value)+1; } else { return round(value); } } /* * Counting the number or .zip files that the Roi folder contains, helpf for batch processing */ function countRoiFiles() { roiDir = getRoiFolder("Open"); roiFiles = getFileList(roiDir); c=0; for(i=0; i1) { run("Make Composite", "display=Composite"); dprint("Made Composite"); } run("RGB Color"); setVoxelSize(px,py,pz,u); saveFile = savingPathName+fileName+"_"+obj+"_Series_"+serNum+"_ROI_"+roiNum+scaling+"_RGB"; rgbName = getTitle(); // Odd behaviour, RGB Conversion sometimes does not generate a new series... if (rgbName!=name) { close(name); selectImage(rgbName); } } else { saveFile = savingPathName+fileName+"_"+obj+"_Series_"+serNum+"_ROI_"+roiNum+scaling+"_Ori"; } // Save proper if(isJpeg) { saveAs("Jpeg", saveFile+".jpeg"); } else { saveAs("Tiff", saveFile+".tif"); } close(); } return n; } /* * This function calls a macro that should create selections (to define ROIs automatically) * then renames the ROIs to match the requirements of the VSI reader */ function runRoiCreationMacro(isWholeImage, isDrawManual, path, allSeriesStatus, seriesSelectedByUserString) { ////////////////////////////////////////////////////////////////////added by Romain 2014.03.26 if (allSeriesStatus){ selectedSeries = "all"; }else{ selectedSeries = split(seriesSelectedByUserString, ","); } macroFile = getData("Macro Path for ROI detection"); if(macroFile == "" && !isWholeImage && !isDrawManual){ macroFile = File.openDialog("Macro Path for ROI detection"); setData("Macro Path for ROI detection", macroFile); } fileList = getFileList(path); for (fileIndex = 0 ; fileIndex < lengthOf(fileList);fileIndex++){ if(isVSI(fileList[fileIndex])){ id = path+fileList[fileIndex]; //parseSeriesData re-reads the .vsi file to make sure all the following functions are working properly. parseSeriesData(id); if (allSeriesStatus) { // Get number of series selectedSeries = getSeriesAsArray(); } for (index = 0 ; index