Skip to content

Instantly share code, notes, and snippets.

@lacan
Last active November 30, 2018 11:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lacan/e0a33a6cf713947994a7bcde51dd8327 to your computer and use it in GitHub Desktop.
Save lacan/e0a33a6cf713947994a7bcde51dd8327 to your computer and use it in GitHub Desktop.
[Stitching with Flatfield Correction] Takes a folder as input and processed all multiseries files there using a provided flatfield file #Beanshell #Stitching #Fiji #ImageJ
//@File(label="Multiposition Files Directory",style="directory") image_directory
//@String(value="Select a file for flat field correction or write none", visibility="MESSAGE") textFF
//@File(label="Flatfield Correction File", value="none") ff_file
//@int(label="Downsample Factor", style = "slider", min = "1", max = "16", stepSize = "1") downsample
//@Boolean(label="Compute Overlap") is_compute
//@String(label="Macro Mode",choices={"Fuse and display","Write to disk"}) output_type
//@LogService log
import ij.IJ;
import loci.formats.ImageReader;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.MetadataTools;
import loci.common.services.ServiceFactory;
import loci.formats.services.OMEXMLService;
import java.util.Arrays;
import java.util.Collections;
import org.apache.commons.lang.ArrayUtils;
import ij.plugin.ChannelSplitter;
import ij.plugin.RGBStackMerge;
import ij.ImagePlus;
import ij.plugin.ImageCalculator;
import loci.plugins.in.ImagePlusReader;
import loci.plugins.in.ImporterOptions;
import loci.plugins.in.ImportProcess;
import java.io.FilenameFilter;
/*
* This simple script will open a multiposition series and perform a flatfield correction of each image and each channel before
* returning a text file with the coordinates of each file to use qith Stephan Preibish's stitching plugins
*
* - works for lsm and lif files format
* - correction factor is applied to metadata of lif iles
* - 1 tilling per file (without the tilled from microscope)
* - Flat field correction file should have the same number of channel (but 1z)
*
*/
ImagePlus divideImages(ImagePlus[] image, ImagePlus[] flatfield) {
if(image.length == flatfield.length) {
ic = new ImageCalculator();
for(int c = 0; c < image.length; c++) {
image[c] = ic.run("Divide create 32-bit stack", image[c], flatfield[c]);
}
return RGBStackMerge.mergeChannels(image, false);
}
}
void exportAndStitch(File image_file, ImagePlus ff_image, int downsample) {
// Get the base directory for the multiposition file
base_dir = image_file.getParent();
// Use it to create a save directory
File saveDir = new File(base_dir+File.separator+image_file.getName().replaceFirst("[.][^.]+$", "")+File.separator);
IJ.log(saveDir.getAbsolutePath());
saveDir.mkdir();
// Get the full path of the file.
String theFile = image_file.getPath();
// lif files have their coordiante metadata stored in another unit...
// or at least bioformats has issues with them...
if(theFile.endsWith(".lif")) corr_factor= 1e6;
// All this code parses the metadata of the lif file so we can access the coordinates
ImageReader reader = new ImageReader();
ServiceFactory factory = new ServiceFactory();
OMEXMLService service = factory.getInstance( OMEXMLService.class );
IMetadata meta = service.createOMEXMLMetadata();
reader.setMetadataStore( meta );
// Here we set the Bioformats reader to work on our file
reader.setId(theFile);
// And the retreive function will help us get all the metadata back
MetadataRetrieve retrieve = service.asRetrieve(reader.getMetadataStore());
/* This is the reader for bioformats
*
*/
opts = new ImporterOptions();
opts.setId(theFile);
opts.setUngroupFiles(true);
//set up import process
process = new ImportProcess(opts);
process.execute();
nseries = process.getSeriesCount();
//reader belonging to the import process
i_reader = process.getReader();
impReader = new ImagePlusReader(process);
double[] posX = new double[reader.getSeriesCount()];
double[] posY = new double[reader.getSeriesCount()];
String[] names = new String[reader.getSeriesCount()];
vx = retrieve.getPixelsPhysicalSizeX(0).value();
all_dims = null;
suffix = "";
for (int i=0; i<nseries; i++) {
// for (int i=0; i<2; i++) {
if( retrieve.getPixelsSizeX(i). getNumberValue().intValue() < 2000) {
posX[i] = (retrieve.getPlanePositionX( i, 0 ).value());
posY[i] = (retrieve.getPlanePositionY( i, 0 ).value());
names[i] = retrieve.getImageName(i);
unit = retrieve.getPlanePositionY( i, 0 ).unit();
opts.setSeriesOn(i,true);
i_reader.setSeries(i);
//read and process all images in series
imps = impReader.openImagePlus();
//IJ.log("Length "+imps.length);
imp=imps[0];
bd= imp.getBitDepth();
ff_imp = imp;
// Perform the flatfield, if there is a file
if( ff_image != null ) {
ff_imp = divideImages(ChannelSplitter.split(imp),ChannelSplitter.split(ff_image));
suffix = "_FF";
}
small_ff_imp = ff_imp;
if (downsample>1) {
IJ.run(ff_imp, "Scale...", "x="+(1.0/downsample)+" y="+(1.0/downsample)+" z="+(1.0/downsample)+" create interpolation=Bilinear average");
small_ff_imp = IJ.getImage();
}
// Save image as TIFF
IJ.saveAs(small_ff_imp, "Tiff", saveDir.getAbsolutePath()+File.separator+names[i]+"_"+(i+1)+suffix+".tif");
// Some cleanup
imp.changes=false;
imp.close();
ff_imp.close();
all_dims = small_ff_imp.getDimensions();
small_ff_imp.close();
opts.setSeriesOn(i, false);
}
}
reader.close();
i_reader.close();
// Get min in X and Y
List lX = Arrays.asList(ArrayUtils.toObject(posX));
List lY = Arrays.asList(ArrayUtils.toObject(posY));
minX = Collections.min(lX);
minY = Collections.min(lY);
dim=2;
z = "";
if(all_dims[3] > 1) {
dim = 2;
z = ", 0.0";
}
PrintWriter out = new PrintWriter(saveDir.getAbsolutePath()+File.separator+"positions.txt");
out.println("#Define the number of dimensions we are working on:");
out.println("dim = "+dim);
out.println("# Define the image coordinates");
for (i=0; i<posX.length; i++) {
fposX = Math.round(((posX[i]-minX)*corr_factor/vx/downsample));
fposY = Math.round(((posY[i]-minY)*corr_factor/vx/downsample));
out.println(names[i]+"_"+(i+1)+suffix+".tif; ; ("+fposX+", "+fposY + z+")");
}
out.close();
compute = "";
if(is_compute) { compute = "compute overlap "; }
// Define an Output directory if needed
output_dir = "";
if (output_type != "Fuse and display") output_dir = "output_directory=["+saveDir.getAbsolutePath()+File.separator+"]";
IJ.run("Grid/Collection stitching", "type=[Positions from file] "+
"order=[Defined by TileConfiguration] "+
"directory=["+saveDir.getAbsolutePath()+"] "+
"layout_file=positions.txt "+
"fusion_method=[Linear Blending] "+
"regression_threshold=0.30 "+
"max/avg_displacement_threshold=2.50 "+
"absolute_displacement_threshold=3.50 "+
compute+
"computation_parameters=[Save memory (but be slower)] "+
"image_output=["+output_type+"] "+
output_dir);
// Save image in case output type was set to display
if (output_type.equals("Fuse and display")) {
final_image = IJ.getImage();
IJ.saveAs(final_image, "Tiff", saveDir.getAbsolutePath()+File.separator+image_file.getName()+suffix+"-fused.tif");
final_image.close();
log.info("Fused image Saved");
}
}
/*
* flatfield file opening and management
* We need to open the Flatfield image and split the channels.
*/
is_not_flatfield = false;
ImagePlus ff_image = null;
ff_string = ff_file.getName();
if (!ff_string.equals("none")) ff_image = IJ.openImage(ff_file.getPath());
// Correction factor, needed for LIF files
corr_factor = 1.0;
File[] all_files = image_directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".lsm") || (name.toLowerCase().endsWith(".lif")));
}
});
for(File f : all_files) {
exportAndStitch(f, ff_image, downsample);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment