Skip to content

Instantly share code, notes, and snippets.

@NicoKiaru
Last active December 2, 2021 08:08
Show Gist options
  • Save NicoKiaru/f18eaecfceb4a834ed402ff44aefb3c2 to your computer and use it in GitHub Desktop.
Save NicoKiaru/f18eaecfceb4a834ed402ff44aefb3c2 to your computer and use it in GitHub Desktop.
#@ImageJ ij
#@Context ctx
// RISKY SCRIPT FOR THE HDD! RECOMMENDED TO TEST ON AN EMPTY USB STICK
def imp = IJ.openImage("http://imagej.nih.gov/ij/images/Spindly-GFP.zip");
File testExport = new File("F:/mitosis.ome.tiff");
DebugTools.setRootLevel("OFF");
ImagePlusToOMETiff.writeToOMETiff(imp, testExport, 2,2);
public class ImagePlusToOMETiff {
private static Logger logger = LoggerFactory.getLogger(ImagePlusToOMETiff.class);
// Inspired from https://github.com/dgault/bio-formats-examples/blob/6cdb11e8c64566611b18f384b3a257dab5037e90/src/main/macros/jython/PyramidConversion.py
// And https://github.com/qupath/qupath/blob/430d6212e641f677dc9a411cf8033fbbe4da2fd6/qupath-extension-bioformats/src/main/java/qupath/lib/images/writers/ome/OMEPyramidWriter.java
public static void writeToOMETiff(ImagePlus image, File outFile, int resolutions, int scale) throws Exception{
// Copy metadata from ImagePlus:
IMetadata omeMeta = MetadataTools.createOMEXMLMetadata();
boolean isLittleEndian = false;
boolean isRGB = false;
boolean isInterleaved = false;
int nChannels = image.getNChannels();
int series = 0;
omeMeta.setImageID("Image:"+series, series);
omeMeta.setPixelsID("Pixels:"+series, series);
if (image.getTitle() != null)
omeMeta.setImageName(image.getTitle(), series);
//meta.setPixelsBigEndian(ByteOrder.BIG_ENDIAN.equals(endian), series);
omeMeta.setPixelsDimensionOrder(DimensionOrder.XYCZT, series);
switch (image.getType()) {
case ImagePlus.GRAY8:
omeMeta.setPixelsType(PixelType.UINT8, series);
break;
case ImagePlus.GRAY16:
isLittleEndian = false;
omeMeta.setPixelsType(PixelType.UINT16, series);
break;
case ImagePlus.GRAY32:
isLittleEndian = false;
omeMeta.setPixelsType(PixelType.FLOAT, series);
break;
case ImagePlus.COLOR_RGB:
nChannels = 3;
isInterleaved = true;
isRGB = true;
omeMeta.setPixelsType(PixelType.UINT8, series);
throw new UnsupportedOperationException("Unhandled bit depth: "+image.getBitDepth());
//break;
default:
throw new UnsupportedOperationException("Cannot convert image of type " + image.getType() + " into a valid OME PixelType");
}
omeMeta.setPixelsBigEndian(!isLittleEndian, 0);
int width = image.getWidth();
int height = image.getHeight();
int sizeZ = image.getNSlices();
int sizeT = image.getNFrames();
int sizeC = image.getNChannels();
omeMeta.setPixelsSizeZ(new PositiveInteger(sizeZ), series);
omeMeta.setPixelsSizeT(new PositiveInteger(sizeT), series);
// Set channel colors
omeMeta.setPixelsSizeC(new PositiveInteger(nChannels), series);
if (isRGB) {
omeMeta.setChannelID("Channel:0", series, 0);
omeMeta.setPixelsInterleaved(isInterleaved, series);
omeMeta.setChannelSamplesPerPixel(new PositiveInteger(3), series, 0); //nSamples = 3; // TODO : check!
} else {
omeMeta.setChannelSamplesPerPixel(new PositiveInteger(1), series, 0);
omeMeta.setPixelsInterleaved(isInterleaved, series);
for (int c = 0; c < nChannels; c++) {
omeMeta.setChannelID("Channel:0:" + c, series, c);
omeMeta.setChannelSamplesPerPixel(new PositiveInteger(1), series, c);
LUT channelLUT = image.getLuts()[c];
int colorRed = channelLUT.getRed(255);
int colorGreen = channelLUT.getGreen(255);
int colorBlue = channelLUT.getBlue(255);
int colorAlpha = channelLUT.getAlpha(255);
omeMeta.setChannelColor(new Color(colorRed, colorGreen, colorBlue, colorAlpha), series, c);
omeMeta.setChannelName("Channel_"+c, series, c);
}
}
// Set physical units, if we have them
if (image.getCalibration()!=null) {
Calibration cal = image.getCalibration();
Unit<Length> unit = getUnitFromCalibration(cal);
omeMeta.setPixelsPhysicalSizeX(new Length(cal.pixelWidth, unit), series);
omeMeta.setPixelsPhysicalSizeY(new Length(cal.pixelHeight, unit), series);
omeMeta.setPixelsPhysicalSizeZ(new Length(cal.pixelDepth, unit), series);
// set Origin in XYZ
// TODO : check if enough or other planes need to be set ?
omeMeta.setPlanePositionX(new Length(cal.xOrigin*cal.pixelWidth, unit),0,0);
omeMeta.setPlanePositionY(new Length(cal.yOrigin*cal.pixelHeight, unit),0,0);
omeMeta.setPlanePositionZ(new Length(cal.zOrigin*cal.pixelDepth, unit),0,0);
}
// Set resolutions
omeMeta.setPixelsSizeX(new PositiveInteger(width), series);
omeMeta.setPixelsSizeY(new PositiveInteger(height), series);
((IPyramidStore)omeMeta).setResolutionSizeX(new PositiveInteger(width),series, 0);
((IPyramidStore)omeMeta).setResolutionSizeY(new PositiveInteger(height),series, 0);
// setup resolutions
for (int i= 0;i<resolutions-1;i++) {
double divScale = Math.pow(scale, i + 1);
((IPyramidStore)omeMeta).setResolutionSizeX(new PositiveInteger((int)(width / divScale)),series, i + 1);
((IPyramidStore)omeMeta).setResolutionSizeY(new PositiveInteger((int)(height / divScale)),series, i + 1);
}
// setup writer
PyramidOMETiffWriter writer = new PyramidOMETiffWriter();
writer.setWriteSequentially(true); // Setting this to false can be problematic!
writer.setMetadataRetrieve(omeMeta);
writer.setId(outFile.getAbsolutePath());
// create ImageScaler for downsampling
//Map<Integer, IFD> map = new HashMap<>();
int nSamples = omeMeta.getChannelSamplesPerPixel(series, 0).getValue();
int nPlanes = (nChannels / nSamples) * sizeZ * sizeT;
// generate downsampled resolutions and write to output
for (int r = 0; r < resolutions; r++) {
logger.debug("Saving resolution size " + r);
//IJ.log("Saving resolution size " + r);
writer.setResolution(r);
// Preallocate any IFD
/*map.clear();
for (int i = 0; i < nPlanes; i++) {
IFD ifd = new IFD();
if (nSamples > 1 && !isRGB)
ifd.put(IFD.EXTRA_SAMPLES, new short[nSamples-1]);
map.put(Integer.valueOf(i), ifd);
}*/
for (int t=0;t<image.getNFrames();t++) {
for (int z=0;z<image.getNSlices();z++) {
for (int c=0;c<image.getNChannels();c++) {
//IJ.log("(" + r+","+t+","+z+","+c+")");
System.out.println("(" + r+","+t+","+z+","+c+")");
ImageProcessor processor;
if (image.getStack()==null) {
processor = image.getProcessor();
} else {
processor = image.getStack().getProcessor(image.getStackIndex(c+1, z+1, t+1));
}
if (r!=0) {
Integer x = ((IPyramidStore)omeMeta).getResolutionSizeX(0, r).getValue();
Integer y = ((IPyramidStore)omeMeta).getResolutionSizeY(0, r).getValue();
processor.setInterpolationMethod(ImageProcessor.BILINEAR);
processor = processor.resize(x,y);
}
int plane = t * sizeZ * sizeC + z * sizeC + c;
//IFD ifd = map.get(Integer.valueOf(plane));
writer.saveBytes(plane, processorToBytes(processor, processor.getWidth()*processor.getHeight()), 0,0, processor.getWidth(), processor.getHeight());
//Thread.sleep(50);
}
}
}
}
//writer.
logger.debug("Calling writer close");
System.out.println("Calling writer close");
writer.close();
logger.debug("File saved");
System.out.println("File saved");
}
public static Unit<Length> getUnitFromCalibration(Calibration cal) {
switch (cal.getUnit()) {
case "um":
case "\u03BCm":
case "\u03B5m":
case "µm":
case "micrometer":
return UNITS.MICROMETER;
case "mm":
case "millimeter":
return UNITS.MILLIMETER;
case "cm":
case "centimeter":
return UNITS.CENTIMETER;
case "m":
case "meter":
return UNITS.METRE;
default:
return UNITS.REFERENCEFRAME;
}
}
private static byte[] processorToBytes(ImageProcessor processor, int nPixels) {
ByteBuffer byteBuf;
switch (processor.getBitDepth()) {
case 8:
return (byte[])processor.getPixels();
case 16:
//https://stackoverflow.com/questions/10804852/how-to-convert-short-array-to-byte-array
// Slow...
byteBuf = ByteBuffer.allocate(2*nPixels);
short[] pixels_short = (short[]) processor.getPixels();
for (short v : pixels_short) byteBuf.putShort(v);
return byteBuf.array();
case 32:
byteBuf = ByteBuffer.allocate(4*nPixels);
float[] pixels_float = (float[]) processor.getPixels();
for (float v : pixels_float) byteBuf.putFloat(v);
return byteBuf.array();
case 24:
/*byteBuf = ByteBuffer.allocate(4*nPixels);
ColorProcessor
float[] pixels_float = (float[]) processor.getPixels();
for (float v : pixels_float) byteBuf.putFloat(v);
return byteBuf.array();
break;*/
default:
throw new UnsupportedOperationException("Unhandled bit depth: "+processor.getBitDepth());
}
}
}
//---------------------------- IMPORTS
import ij.IJ
import ij.ImagePlus;
import ij.measure.Calibration;
import ij.process.*;
import loci.formats.MetadataTools;
import loci.formats.meta.IMetadata;
import loci.formats.meta.IPyramidStore;
import loci.formats.out.PyramidOMETiffWriter;
import ome.units.UNITS;
import ome.units.quantity.Length;
import ome.units.unit.Unit;
import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.enums.PixelType;
import ome.xml.model.primitives.Color;
import ome.xml.model.primitives.PositiveInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.ByteBuffer;
import ij.ImagePlus;
import io.scif.services.DatasetIOService;
import loci.common.DebugTools;
import net.imagej.Dataset;
import net.imagej.ImageJ;
import java.io.File;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment