TrakEM2; CATMAID Skript export of multiple panoramas (layers)

Hi,

I want to export my 2D panoramas in TrakEM2 to non-overlapping and non-pyramidal image tiles (tif) of 25.000x25.000 pixels using this CATMAID script (see below).
If I enter the tile size and layers (e.g. 0-4 for layer 1,2,3,4,5), I get an error (Source file: null : Object constructor : at Line: 147 : in file: : new ByteProcessor ( tileWidth * 2 , tileHeight " 2 ).

Then, only the first layer is exported and the script stops.

Does some know this problem? I want to do the export of 5-10 large datasets overnight to save time, this would really help me.

My second problem with this script is that I get tif files and png files. I only need tif files. Do I only have to uncomment the line with "exportFormat = “png”?
I received different errors when I tried to get only tif files, maybe I missed antoher line here.

My last problem is the time of mipmaps generation. To further speed up my data processing workflow, I tried to change the paramets as shown here:

I used the following parameters: (Properties)
Image resizing mode (Area downsampling)
mipmaps format (jpg; to reduce the size of my project)
Bucket side length (100 000 pixels)
Number of threads for mipmaps (16; all I have)
Default mesh resolution for images (32)

The boxes are all unchecked (layer mipmaps, etc…)

Best regards,
Carsten

/**
 * Export tiles for CATMAID.
 *
 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
*
* Usage instructions:
*   - open a project in TrakEM2, regenerate mipmaps
*   - open the script editor (File/New/Script), select "Beanshell" as language, paste this script in
*   - select layer range you wish to export (firstLayer and lastLayer)
*   - select appropriate tileWidth and tileHeight
*   - select appropriate path
*   - click "Run"
*
*/

import ij.ImagePlus;
import ij.process.ByteProcessor;
import ij.gui.Roi;
import ij.io.FileSaver;
import ij.IJ;
import ij.process.Blitter;
import ini.trakem2.display.*;
import mpicbg.trakem2.transform.*;
import mpicbg.ij.util.*;
import mpicbg.models.Model;

firstLayer = 2;
lastLayer = 3;

tileWidth = 25000;
tileHeight = 25000;

emptyImage = new ImagePlus("",new ByteProcessor(tileWidth,tileHeight));

exportFormat = "png";  // "jpg" or "png"
jpegQuality = 85;

path = "D:\\Puffer\\Export\\";

front = Display.getFront();
layerSet = front.getLayerSet();

layers = front.getLayerSet().getLayers();
roi = front.getRoi();
if (roi == null) {
	roi = new Roi(0, 0, layerSet.getLayerWidth(), layerSet.getLayerHeight());
}
left = roi.getBounds().x;
top = roi.getBounds().y;
w = roi.getBounds().width;
h = roi.getBounds().height;

ImagePlus openAndDelete(path)
{
	file = new File(path);
	if ( file.exists() )
	{
		imp = new ImagePlus( path );
		//file.delete();
		return imp;
	}	
	else
		return emptyImage;
}

emptySections = new ArrayList();

for ( int l = firstLayer; l <= lastLayer; ++l )
{
	l1 = l - firstLayer;
	layer = layers.get( l );
	if ( layer.getDisplayables( Patch.class ).size() == 0 )
	{
		IJ.log( "Section " + l + " empty" );
		emptySections.add( l );
		continue;
	}
	
	dirPath = path + l1;
	dir = new File( dirPath );
	if ( !dir.exists() )
		dir.mkdirs();

	/* level 0 tiles */
	for ( y = 0; y < h; y += tileHeight )
	{
		for ( x = 0; x < w; x += tileWidth )
		{
			box = new Rectangle( x + left, y + top, tileWidth, tileHeight );
			
			impTile = layer.getProject().getLoader().getFlatImage(
				layer,
				box,
				1.0,
				-1,
				ImagePlus.GRAY8,
				Patch.class,
				true );

			fileSaver = new FileSaver(impTile);
			if (exportFormat == "png") {
				fileSaver.saveAsPng(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.png");
			} else if (exportFormat == "jpg") {
				fileSaver.setJpegQuality(jpegQuality);
				fileSaver.saveAsJpeg(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.jpg");
			} else {
				IJ.log("ERROR selecting file format.");
			}
			fileSaver.saveAsTiff(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.tif");
		}
	}

	/* level [1,n] tiles */
	for (
		int w1 = ( int )Math.ceil( w / 2.0 ), h1 = ( int )Math.ceil( h / 2.0 ), s = 1;
		w1 > tileWidth && h1 > tileHeight;
		w1 = ( int )Math.ceil( w1 / 2.0 ), h1 = ( int )Math.ceil( h1 / 2.0 ), ++s )
	{
		IJ.log( w1 + " " + h1 );
		for ( y = 0; y < h1; y += tileHeight )
		{
			yt = y / tileHeight;
			for ( x = 0; x < w1; x += tileWidth )
			{
				xt = x / tileWidth;
				imp1 = openAndDelete(
					dirPath + "/" +
					(2 * yt) + "_" +
					(2 * xt) + "_" +
					(s - 1) + ".tif");
				imp2 = openAndDelete(
					dirPath + "/" +
					(2 * yt) + "_" +
					(2 * xt + 1) + "_" +
					(s - 1) + ".tif");
				imp3 = openAndDelete(
					dirPath + "/" +
					(2 * yt + 1) + "_" +
					(2 * xt) + "_" +
					(s - 1) + ".tif");
				imp4 = openAndDelete(
					dirPath + "/" +
					(2 * yt + 1) + "_" +
					(2 * xt + 1) + "_" +
					(s - 1) + ".tif");

				ip4 = new ByteProcessor(tileWidth * 2, tileHeight * 2);
				ip4.copyBits(imp1.getProcessor(), 0, 0, Blitter.COPY);
				ip4.copyBits(imp2.getProcessor(), tileWidth, 0, Blitter.COPY);
				ip4.copyBits(imp3.getProcessor(), 0, tileHeight, Blitter.COPY);
				ip4.copyBits(imp4.getProcessor(), tileWidth, tileHeight, Blitter.COPY);

				ip = mpicbg.trakem2.util.Downsampler.downsampleImageProcessor(ip4);
				impTile = new ImagePlus("", ip);

				fileSaver = new FileSaver( impTile );
				if (exportFormat == "png") {
					fileSaver.saveAsPng( dirPath + "/" + yt + "_" + xt + "_" + s + ".png" );
				} else if (exportFormat == "jpg") {
					fileSaver.setJpegQuality( jpegQuality );
					fileSaver.saveAsJpeg( dirPath + "/" + yt + "_" + xt + "_" + s + ".jpg" );
				} else {
					IJ.log("ERROR selecting file format.");
				}
				if ( Math.ceil( w1 / 2.0 ) > tileWidth && Math.ceil( h1 / 2.0 ) > tileHeight )
					fileSaver.saveAsTiff( dirPath + "/" + yt + "_" + xt + "_" + s + ".tif" );
			}
		}
	}
}

emptySectionsString = "";
for ( l : emptySections )
	emptySectionsString += ( l + ", " );

emptySectionsString = emptySectionsString.substring( 0, Math.max( 0, emptySectionsString.length() - 2 ) );

calibration = front.getLayerSet().getCalibration();
box = roi.getBounds();
fos = new FileOutputStream( path + "metadata.json" );
out = new OutputStreamWriter( fos, "UTF-8" );
text =
	"{ volume_width_px : " + box.width + ",\n" +
	"  volume_height_px : " + box.height + ",\n" +
	"  volume_sections : " + ( lastLayer - firstLayer + 1 ) + ",\n" +
	"  extension : \"." + exportFormat + "\",\n" +
	"  resolution_x : " + calibration.pixelWidth + ",\n" +
	"  resolution_y : " + calibration.pixelHeight + ",\n" +
	"  resolution_z : " + calibration.pixelDepth + ",\n" +
	"  units : \"" + calibration.getXUnit() + "\"" + ",\n" +
	"  offset_x_px : " + box.x + ",\n" + 
	"  offset_y_px : " + box.y + ",\n" +
	"  offset_z_px : " + firstLayer + ",\n" +
	"  missing_layers : [" + emptySectionsString + "] }";
out.write( text, 0, text.length() );
out.close();

Hi @CaDi,

Where do you enter “0-4”?
Looking at the script, I’d expect you need to change lines 27 and 28 from:

firstLayer = 2;
lastLayer = 3;

to

firstLayer = 0;
lastLayer = 4;

Have a look at lines 100-109:

if (exportFormat == "png") {                                                                                                                 
   fileSaver.saveAsPng(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.png");
} else if (exportFormat == "jpg") {
   fileSaver.setJpegQuality(jpegQuality);
   fileSaver.saveAsJpeg(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.jpg");
} else {
   IJ.log("ERROR selecting file format.");
}
fileSaver.saveAsTiff(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.tif");

If you want only png files, you can comment out the last line there. If you want only tifs, you can comment away the if/else statements (or set the exportFormat variable to anything other than "png" or "jpg".

Is the problem that it’s slow?

John

Hi John,

thanks for your reply.
Yes, I changed the lines 27 and 28, but I received the error (Source file: null : Object constructor : at Line: 147 : in file: : new ByteProcessor ( tileWidth * 2 , tileHeight " 2).

I will try to set the exportFormat to “xyz” and see if it works. Do you mean with “if/else statements” that I would have to comment the 3 lines where “if”, “else if” and “else” are bold??

//if (exportFormat == "png") {                                                                                                                 
   fileSaver.saveAsPng(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.png");
//} else if (exportFormat == "jpg") {
   fileSaver.setJpegQuality(jpegQuality);
   fileSaver.saveAsJpeg(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.jpg");
//} else {
   IJ.log("ERROR selecting file format.");
}
fileSaver.saveAsTiff(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.tif");

Yes, the mipmaps generation is quite slow, but I process large files (e.g. 5 datasets with about 200 10K-tiles, in total about 150 GB). It takes about 2 hours, but it’s ok if the speed can’t be increased any further.

Best regards,
Carsten

@CaDi,

Weird. Would you mind attaching the entire output and error, that might help us figure it out.

For commenting, I meant:

//if (exportFormat == "png") {                                                                                                                 
//   fileSaver.saveAsPng(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.png");
//} else if (exportFormat == "jpg") {
//   fileSaver.setJpegQuality(jpegQuality);
//   fileSaver.saveAsJpeg(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.jpg");
//} else {
//   IJ.log("ERROR selecting file format.");
//}
fileSaver.saveAsTiff(dirPath + "/" + (y / tileHeight) + "_" + (x / tileWidth) + "_0.tif");

but exportFormat=“xyz” is nicer/easier :slight_smile:

Not sure if I have any tips in particular about speeding this up except to try to parallelize more :-/

John

Hi John,

I’ll have to repeat the export, unfortunately, I didn’t copy the whole error, took only a screenshot:

Maybe it helps to figure out the problem?

Best regards,
Carsten

Hi,

I modified the script. I commented the lines as you posted, because “xyz” caused the error “ERROR selecting file format.”. Probably, this wouldn’t be a problem, but the layer export didn’t work, thus, I used the other option.

Using a smaller test dataset, consisting of 5 layers (0-4), the scripts successfully was finished and only .tif files were exportet. However, only the first layer was exported.

Maybe, there is a problem in defining or setting the layer range in the script?

Best regards,
Carsten

Edit: Sorry, I didn’t notice that separate folders were created “0”, “1”, etc. instead of an export in one single folder but with different layer names. Using this script, I successfully exported my (small) test dataset. I’ll try to do the export of my large datasets next.

/**

  • Export tiles for CATMAID.
  • @author Stephan Saalfeld saalfeld@mpi-cbg.de
  • Usage instructions:
    • open a project in TrakEM2, regenerate mipmaps
    • open the script editor (File/New/Script), select “Beanshell” as language, paste this script in
    • select layer range you wish to export (firstLayer and lastLayer)
    • select appropriate tileWidth and tileHeight
    • select appropriate path
    • click “Run”

*/

import ij.ImagePlus;
import ij.process.ByteProcessor;
import ij.gui.Roi;
import ij.io.FileSaver;
import ij.IJ;
import ij.process.Blitter;
import ini.trakem2.display.;
import mpicbg.trakem2.transform.
;
import mpicbg.ij.util.*;
import mpicbg.models.Model;

firstLayer = 0;
lastLayer = 4;

tileWidth = 10000;
tileHeight = 10000;

emptyImage = new ImagePlus("",new ByteProcessor(tileWidth,tileHeight));

exportFormat = “png”; // “jpg” or “png”
jpegQuality = 85;

path = “E:\Puffer\Export\”;

front = Display.getFront();
layerSet = front.getLayerSet();

layers = front.getLayerSet().getLayers();
roi = front.getRoi();
if (roi == null) {
roi = new Roi(0, 0, layerSet.getLayerWidth(), layerSet.getLayerHeight());
}
left = roi.getBounds().x;
top = roi.getBounds().y;
w = roi.getBounds().width;
h = roi.getBounds().height;

ImagePlus openAndDelete(path)
{
file = new File(path);
if ( file.exists() )
{
imp = new ImagePlus( path );
file.delete();
return imp;
}
else
return emptyImage;
}

emptySections = new ArrayList();

for ( int l = firstLayer; l <= lastLayer; ++l )
{
l1 = l - firstLayer;
layer = layers.get( l );
if ( layer.getDisplayables( Patch.class ).size() == 0 )
{
IJ.log( “Section " + l + " empty” );
emptySections.add( l );
continue;
}

dirPath = path + l1;
dir = new File( dirPath );
if ( !dir.exists() )
	dir.mkdirs();

/* level 0 tiles */
for ( y = 0; y < h; y += tileHeight )
{
	for ( x = 0; x < w; x += tileWidth )
	{
		box = new Rectangle( x + left, y + top, tileWidth, tileHeight );
		
		impTile = layer.getProject().getLoader().getFlatImage(
			layer,
			box,
			1.0,
			-1,
			ImagePlus.GRAY8,
			Patch.class,
			true );

		fileSaver = new FileSaver(impTile);

// if (exportFormat == “png”) {
// fileSaver.saveAsPng(dirPath + “/” + (y / tileHeight) + “_” + (x / tileWidth) + “0.png");
// } else if (exportFormat == “jpg”) {
// fileSaver.setJpegQuality(jpegQuality);
// fileSaver.saveAsJpeg(dirPath + “/” + (y / tileHeight) + "
” + (x / tileWidth) + “0.jpg");
// } else {
// IJ.log(“ERROR selecting file format.”);
// }
fileSaver.saveAsTiff(dirPath + “/” + (y / tileHeight) + "
” + (x / tileWidth) + “_0.tif”);
}
}

/* level [1,n] tiles */
for (
	int w1 = ( int )Math.ceil( w / 2.0 ), h1 = ( int )Math.ceil( h / 2.0 ), s = 1;
	w1 > tileWidth && h1 > tileHeight;
	w1 = ( int )Math.ceil( w1 / 2.0 ), h1 = ( int )Math.ceil( h1 / 2.0 ), ++s )
{
	IJ.log( w1 + " " + h1 );
	for ( y = 0; y < h1; y += tileHeight )
	{
		yt = y / tileHeight;
		for ( x = 0; x < w1; x += tileWidth )
		{
			xt = x / tileWidth;
			imp1 = openAndDelete(
				dirPath + "/" +
				(2 * yt) + "_" +
				(2 * xt) + "_" +
				(s - 1) + ".tif");
			imp2 = openAndDelete(
				dirPath + "/" +
				(2 * yt) + "_" +
				(2 * xt + 1) + "_" +
				(s - 1) + ".tif");
			imp3 = openAndDelete(
				dirPath + "/" +
				(2 * yt + 1) + "_" +
				(2 * xt) + "_" +
				(s - 1) + ".tif");
			imp4 = openAndDelete(
				dirPath + "/" +
				(2 * yt + 1) + "_" +
				(2 * xt + 1) + "_" +
				(s - 1) + ".tif");

			ip4 = new ByteProcessor(tileWidth * 2, tileHeight * 2);
			ip4.copyBits(imp1.getProcessor(), 0, 0, Blitter.COPY);
			ip4.copyBits(imp2.getProcessor(), tileWidth, 0, Blitter.COPY);
			ip4.copyBits(imp3.getProcessor(), 0, tileHeight, Blitter.COPY);
			ip4.copyBits(imp4.getProcessor(), tileWidth, tileHeight, Blitter.COPY);

			ip = mpicbg.trakem2.util.Downsampler.downsampleImageProcessor(ip4);
			impTile = new ImagePlus("", ip);

			fileSaver = new FileSaver( impTile );
			if (exportFormat == "png") {
				fileSaver.saveAsPng( dirPath + "/" + yt + "_" + xt + "_" + s + ".png" );
			} else if (exportFormat == "jpg") {
				fileSaver.setJpegQuality( jpegQuality );
				fileSaver.saveAsJpeg( dirPath + "/" + yt + "_" + xt + "_" + s + ".jpg" );
			} else {
				IJ.log("ERROR selecting file format.");
			}
			if ( Math.ceil( w1 / 2.0 ) > tileWidth && Math.ceil( h1 / 2.0 ) > tileHeight )
				fileSaver.saveAsTiff( dirPath + "/" + yt + "_" + xt + "_" + s + ".tif" );
		}
	}
}

}

emptySectionsString = “”;
for ( l : emptySections )
emptySectionsString += ( l + ", " );

emptySectionsString = emptySectionsString.substring( 0, Math.max( 0, emptySectionsString.length() - 2 ) );

calibration = front.getLayerSet().getCalibration();
box = roi.getBounds();
fos = new FileOutputStream( path + “metadata.json” );
out = new OutputStreamWriter( fos, “UTF-8” );
text =
“{ volume_width_px : " + box.width + “,\n” +
" volume_height_px : " + box.height + “,\n” +
" volume_sections : " + ( lastLayer - firstLayer + 1 ) + “,\n” +
" extension : “.” + exportFormat + “”,\n” +
" resolution_x : " + calibration.pixelWidth + “,\n” +
" resolution_y : " + calibration.pixelHeight + “,\n” +
" resolution_z : " + calibration.pixelDepth + “,\n” +
" units : “” + calibration.getXUnit() + “”" + “,\n” +
" offset_x_px : " + box.x + “,\n” +
" offset_y_px : " + box.y + “,\n” +
" offset_z_px : " + firstLayer + “,\n” +
" missing_layers : [" + emptySectionsString + “] }”;
out.write( text, 0, text.length() );
out.close();

Hi,

I tried the export of 8 layers of large datasets and got the same error. Here is the full error.
Again, I only got the export tiles of the first layer, then it stopped.

Best regards,
Carsten

Edit: I solved my problem by just opening and running 7 different versions of the script in parallel, and using different export folders for export of only single layers each (0-1, 1-2, …).

Started CATMAID_MultiLayerExport_03_worksTest_.bsh at Mon Dec 16 17:37:09 CET 2019
Sourced file: null : Object constructor : at Line: 147 : in file: : new ByteProcessor ( tileWidth * 2 , tileHeight * 2 )

Target exception: java.lang.NegativeArraySizeException

at bsh.BSHAllocationExpression.constructObject(BSHAllocationExpression.java:126)
at bsh.BSHAllocationExpression.objectAllocation(BSHAllocationExpression.java:108)
at bsh.BSHAllocationExpression.eval(BSHAllocationExpression.java:56)
at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:96)
at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:41)
at bsh.BSHAssignment.eval(BSHAssignment.java:71)
at bsh.BSHBlock.evalBlock(BSHBlock.java:125)
at bsh.BSHBlock.eval(BSHBlock.java:75)
at bsh.BSHBlock.eval(BSHBlock.java:41)
at bsh.BSHForStatement.eval(BSHForStatement.java:105)
at bsh.BSHBlock.evalBlock(BSHBlock.java:125)
at bsh.BSHBlock.eval(BSHBlock.java:75)
at bsh.BSHBlock.eval(BSHBlock.java:41)
at bsh.BSHForStatement.eval(BSHForStatement.java:105)
at bsh.BSHBlock.evalBlock(BSHBlock.java:125)
at bsh.BSHBlock.eval(BSHBlock.java:75)
at bsh.BSHBlock.eval(BSHBlock.java:41)
at bsh.BSHForStatement.eval(BSHForStatement.java:105)
at bsh.BSHBlock.evalBlock(BSHBlock.java:125)
at bsh.BSHBlock.eval(BSHBlock.java:75)
at bsh.BSHBlock.eval(BSHBlock.java:41)
at bsh.BSHForStatement.eval(BSHForStatement.java:105)
at bsh.Interpreter.eval(Interpreter.java:659)
at org.scijava.plugins.scripting.beanshell.BeanshellScriptEngine.eval(BeanshellScriptEngine.java:80)
at org.scijava.script.ScriptModule.run(ScriptModule.java:177)
at org.scijava.module.ModuleRunner.run(ModuleRunner.java:167)
at org.scijava.module.ModuleRunner.call(ModuleRunner.java:126)
at org.scijava.module.ModuleRunner.call(ModuleRunner.java:65)
at org.scijava.thread.DefaultThreadService$2.call(DefaultThreadService.java:191)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

Caused by: java.lang.NegativeArraySizeException
at ij.process.ByteProcessor.(ByteProcessor.java:55)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at bsh.Reflect.constructObject(Reflect.java:621)
at bsh.BSHAllocationExpression.constructObject(BSHAllocationExpression.java:117)
… 32 more

1 Like

Weird, but glad you figured out something!