package gdsc.smlm.ij.plugins; import gdsc.core.ij.Utils; import ij.ImagePlus; import ij.ImageStack; import ij.gui.GenericDialog; import ij.measure.Calibration; import ij.plugin.PlugIn; import ij.process.Blitter; import ij.process.ByteProcessor; import ij.process.ImageProcessor; /** * This plugin creates a mask image of a Yeast cell for use in diffusion simulations */ public class YeastMask implements PlugIn { private static final String TITLE = "Yeast Mask"; private static double length = 8, radius = 1.5; private static double nucleus = 0.9; private static double nmPerPixel = 107; private static double nmPerSlice = 20; private static boolean excludeNucleus = true; private static boolean squareOutput = true; private static int border = 3; private static boolean is2D = false; /* * (non-Javadoc) * * @see ij.plugin.PlugIn#run(java.lang.String) */ public void run(String arg) { SMLMUsageTracker.recordPlugin(this.getClass(), arg); if (!showDialog()) return; createMask(); } private boolean showDialog() { GenericDialog gd = new GenericDialog(TITLE); gd.addHelp(About.HELP_URL); gd.addMessage("Create a mask of a yeast cell as a tube plus end-caps"); gd.addSlider("Tube_length (um)", 10, 20, length); gd.addSlider("Radius (um)", 0.5, 5, radius); gd.addCheckbox("Exclude_nucleus", excludeNucleus); gd.addSlider("Nucleus (fraction)", 0.5, 1, nucleus); gd.addNumericField("Pixel_pitch", nmPerPixel, 1, 6, "nm"); gd.addNumericField("Pixel_depth", nmPerSlice, 1, 6, "nm"); gd.addCheckbox("Square_output", squareOutput); gd.addSlider("Border", 0, 10, border); gd.addCheckbox("2D", is2D); gd.showDialog(); if (gd.wasCanceled()) return false; length = gd.getNextNumber(); radius = gd.getNextNumber(); excludeNucleus = gd.getNextBoolean(); nucleus = gd.getNextNumber(); nmPerPixel = gd.getNextNumber(); nmPerSlice = gd.getNextNumber(); squareOutput = gd.getNextBoolean(); border = (int) gd.getNextNumber(); is2D = gd.getNextBoolean(); if (radius < 0.5) radius = 0.5; if (length < 0) length = 0; if (nmPerPixel < 1) nmPerPixel = 1; if (nmPerSlice < 1) nmPerSlice = 1; return true; } private void createMask() { // Create the dimensions final int hw = (int) Math.ceil(radius * 1000 / nmPerPixel); final int hd = (int) Math.ceil(radius * 1000 / nmPerSlice); final int width = 2 * hw + 1; final int depth = 2 * hd + 1; ImageStack stack = createHemiSphere(width, depth); // Extend the centre circle of the sphere into a tube of the required length final int h = (int) Math.ceil(length * 1000 / nmPerPixel); if (h > 0) { ImageStack newStack = new ImageStack(width, stack.getHeight() + h, stack.getSize()); for (int slice = 1; slice <= stack.getSize(); slice++) { byte[] pixels = (byte[]) stack.getPixels(slice); byte[] newPixels = new byte[width * newStack.getHeight()]; newStack.setPixels(newPixels, slice); System.arraycopy(pixels, 0, newPixels, 0, pixels.length); // Get the final strip to be extended final int offset = pixels.length - width; int target = pixels.length; for (int i = 0; i < h; i++) { System.arraycopy(pixels, offset, newPixels, target, width); target += width; } } stack = newStack; } // Copy the hemi-sphere onto the end ImageStack newStack = new ImageStack(width, stack.getHeight() + hw, stack.getSize()); for (int slice = 1; slice <= stack.getSize(); slice++) { byte[] pixels = (byte[]) stack.getPixels(slice); byte[] newPixels = new byte[width * newStack.getHeight()]; newStack.setPixels(newPixels, slice); System.arraycopy(pixels, 0, newPixels, 0, pixels.length); // Copy the hemi-sphere int source = 0; int target = newPixels.length - width; for (int i = 0; i < hw; i++) { System.arraycopy(pixels, source, newPixels, target, width); target -= width; source += width; } } stack = newStack; if (excludeNucleus) { ImageStack stack2 = createNucleusSphere(width, depth); int xloc = (stack.getWidth() - stack2.getWidth()) / 2; int yloc = (stack.getHeight() - stack2.getHeight()) / 2; int offset = (stack.getSize() - stack2.getSize()) / 2; for (int slice = 1; slice <= stack2.getSize(); slice++) { ImageProcessor ip = stack.getProcessor(slice + offset); ImageProcessor ip2 = stack2.getProcessor(slice); ip.copyBits(ip2, xloc, yloc, Blitter.SUBTRACT); } } if (squareOutput && stack.getWidth() != stack.getHeight()) { ImageStack stack2 = new ImageStack(stack.getHeight(), stack.getHeight()); int end = stack.getHeight() - stack.getWidth(); for (int slice = 1; slice <= stack.getSize(); slice++) { ImageProcessor ip = stack.getProcessor(slice); ImageProcessor ip2 = new ByteProcessor(stack2.getWidth(), stack2.getHeight()); stack2.addSlice(ip2); for (int xloc = 0; xloc <= end; xloc += stack.getWidth()) { ip2.insert(ip, xloc, 0); } } stack = stack2; } if (border > 0) { ImageStack stack2 = new ImageStack(stack.getWidth() + 2 * border, stack.getHeight() + 2 * border); for (int slice = 1; slice <= stack.getSize(); slice++) { ImageProcessor ip = stack.getProcessor(slice); ImageProcessor ip2 = new ByteProcessor(stack2.getWidth(), stack2.getHeight()); stack2.addSlice(ip2); ip2.insert(ip, border, border); } stack = stack2; } ImagePlus imp; if (is2D) { // TODO - Remove this laziness since we should really just do a 2D image int centre = stack.getSize() / 2; imp = Utils.display(TITLE, stack.getProcessor(centre)); } else { imp = Utils.display(TITLE, stack); } // Calibrate Calibration cal = new Calibration(); cal.setUnit("um"); cal.pixelWidth = cal.pixelHeight = nmPerPixel / 1000; cal.pixelDepth = nmPerSlice / 1000; imp.setCalibration(cal); } /** * Create a sphere using the given pixel width and stack depth using a fraction of the original cell radius * * @param width * @param depth * @return A sphere */ private ImageStack createNucleusSphere(int width, int depth) { // Create a sphere. This could be done exploiting symmetry to be more efficient // but is left as a simple implementation final double centreX = width * 0.5; final double centreZ = depth * 0.5; // Precompute squares for the width double[] s = new double[width]; for (int iy = 0; iy < width; iy++) { final double y = (centreX - (iy + 0.5)) * nmPerPixel; s[iy] = y * y; } ImageStack stack = new ImageStack(width, width, depth); final byte on = (byte) 255; final double r = radius * 1000 * nucleus; final double r2 = r * r; for (int iz = 0; iz < depth; iz++) { final double z = (centreZ - (iz + 0.5)) * nmPerSlice; final double z2 = z * z; byte[] mask = new byte[width * width]; for (int iy = 0, i = 0; iy < width; iy++) { final double y2z2 = s[iy] + z2; for (int ix = 0; ix < width; ix++, i++) { final double d2 = s[ix] + y2z2; if (d2 < r2) mask[i] = on; } } stack.setPixels(mask, iz + 1); } return stack; } /** * Create a hemi-sphere using the given pixel width and stack depth using the original cell radius * * @param width * @param depth * @return A hemi-sphere */ private ImageStack createHemiSphere(int width, int depth) { // Create a sphere. This could be done exploiting symmetry to be more efficient // but is left as a simple implementation final double centreX = width * 0.5; final double centreZ = depth * 0.5; // Precompute squares for the width double[] s = new double[width]; for (int iy = 0; iy < width; iy++) { final double y = (centreX - (iy + 0.5)) * nmPerPixel; s[iy] = y * y; } final int halfHeight = 1 + width / 2; ImageStack stack = new ImageStack(width, halfHeight, depth); final byte on = (byte) 255; final double r = radius * 1000; final double r2 = r * r; for (int iz = 0; iz < depth; iz++) { final double z = (centreZ - (iz + 0.5)) * nmPerSlice; final double z2 = z * z; byte[] mask = new byte[width * halfHeight]; for (int iy = 0, i = 0; iy < halfHeight; iy++) { final double y2z2 = s[iy] + z2; for (int ix = 0; ix < width; ix++, i++) { final double d2 = s[ix] + y2z2; if (d2 < r2) mask[i] = on; } } stack.setPixels(mask, iz + 1); } return stack; } }