From 6d364b9e375bb4ee7c64d784b0e7d596e345b17c Mon Sep 17 00:00:00 2001 From: kmdouglass Date: Thu, 8 Feb 2018 09:38:12 +0100 Subject: [PATCH 1/3] First draft of the working image data abstraction layer. --- CHANGELOG.md | 2 + src/ch/epfl/leb/sass/ijplugin/App.java | 31 +- src/ch/epfl/leb/sass/models/Microscope.java | 12 +- .../epfl/leb/sass/models/legacy/Device.java | 9 +- .../epfl/leb/sass/models/legacy/STORMsim.java | 19 +- .../RemoteSimulationServiceHandler.java | 22 +- src/ch/epfl/leb/sass/simulator/Simulator.java | 7 +- .../simulator/internal/AbstractSimulator.java | 12 +- .../simulator/internal/DefaultSimulator.java | 16 +- .../simulator/internal/ImageJSimulator.java | 14 +- src/ch/epfl/leb/sass/utils/Runner.java | 63 ---- src/ch/epfl/leb/sass/utils/TiffGenerator.java | 104 ------- src/ch/epfl/leb/sass/utils/images/ImageS.java | 172 +++++++++++ .../utils/images/internal/DefaultImageS.java | 276 ++++++++++++++++++ .../RemoteSimulationServiceHandlerTest.java | 12 +- 15 files changed, 514 insertions(+), 257 deletions(-) delete mode 100644 src/ch/epfl/leb/sass/utils/Runner.java delete mode 100644 src/ch/epfl/leb/sass/utils/TiffGenerator.java create mode 100644 src/ch/epfl/leb/sass/utils/images/ImageS.java create mode 100644 src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e9762..fb30797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file. ### Added - An RPC server was created that allows for remote control of the simulation over the network. +- A new image abstraction layer called **ImageS** that separates image + data logic and display from the SASS core code. ### Changed - The package layout was restructured and simplified to make it easier diff --git a/src/ch/epfl/leb/sass/ijplugin/App.java b/src/ch/epfl/leb/sass/ijplugin/App.java index c768374..2d07f7b 100644 --- a/src/ch/epfl/leb/sass/ijplugin/App.java +++ b/src/ch/epfl/leb/sass/ijplugin/App.java @@ -23,19 +23,17 @@ import ch.epfl.leb.sass.models.Microscope; import ch.epfl.leb.alica.Analyzer; import ch.epfl.leb.alica.Controller; -import ij.ImagePlus; -import ij.process.ImageProcessor; +import ch.epfl.leb.sass.utils.images.ImageS; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; -import ch.epfl.leb.sass.simulator.Simulator; /** * Backend for the FIJI plugin GUI * @author Marcel Stefko */ public class App extends ImageJSimulator { - private final ImagePlus imp; + private final ImageS imp; private final SimulatorStatusFrame statusFrame; private final int controller_tickrate; private Worker worker; @@ -70,8 +68,8 @@ public App( this.getNextImage(); this.getNextImage(); interaction_window = new InteractionWindow(analyzer, controller); - imp = new ImagePlus("Simulation Window", this.getStack()); - imp.show(); + imp = this.getStack(); + imp.view(); // The units of the manual controller setpoint are the same as the // as the laser, not the analyzer output like the other controllers. @@ -157,9 +155,9 @@ class Worker extends Thread { private final App app; private final Controller controller; private final Analyzer analyzer; - private final ImagePlus imp; + private final ImageS imp; - public Worker(App app, Controller controller, Analyzer active_analyzer, ImagePlus imp) { + public Worker(App app, Controller controller, Analyzer active_analyzer, ImageS imp) { this.app = app; this.controller = controller; this.analyzer = active_analyzer; @@ -169,17 +167,20 @@ public Worker(App app, Controller controller, Analyzer active_analyzer, ImagePlu @Override public void run() { - ImageProcessor ip; SimulatorStatusFrame statusFrame = app.getStatusFrame(); while (!stop) { app.incrementCounter(); - ip = app.getNextImage(); + + // TODO: Remove the explicit type in the future when cameras models + // with different bit depths are implemented! + ImageS is = app.getNextImage(); + long time_start, time_end; time_start = System.nanoTime(); analyzer.processImage( - ip.getPixelsCopy(), - ip.getWidth(), - ip.getHeight(), + is.getPixelDataPrimitive(0), + is.getWidth(), + is.getHeight(), app.getObjectSpacePixelSize(), time_start ); @@ -198,8 +199,8 @@ public void run() { app.getGeneratorTrueSignal().add(app.getTrueSignal(app.getImageCount())); - imp.setSlice(imp.getNSlices()); - imp.updateAndRepaintWindow(); + imp.setSlice(imp.getSize() - 1); + imp.updateView(); try { sleep(20); } catch (InterruptedException ex) { diff --git a/src/ch/epfl/leb/sass/models/Microscope.java b/src/ch/epfl/leb/sass/models/Microscope.java index 84198ad..980de1a 100644 --- a/src/ch/epfl/leb/sass/models/Microscope.java +++ b/src/ch/epfl/leb/sass/models/Microscope.java @@ -17,6 +17,8 @@ */ package ch.epfl.leb.sass.models; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import ch.epfl.leb.sass.models.fluorophores.internal.DefaultFluorophore; import ch.epfl.leb.sass.utils.RNG; import ch.epfl.leb.sass.models.obstructors.Obstructor; @@ -35,12 +37,9 @@ import ch.epfl.leb.sass.models.obstructors.internal.commands.ObstructorCommand; import ch.epfl.leb.sass.models.backgrounds.BackgroundCommandBuilder; import ch.epfl.leb.sass.models.backgrounds.BackgroundCommand; -import ij.process.FloatProcessor; -import ij.process.ShortProcessor; import java.util.Arrays; import java.util.List; import cern.jet.random.Poisson; -import ij.IJ; /** * Integrates all the components into one microscope. @@ -197,7 +196,7 @@ public double getOnEmitterCount() { * * @return simulated frame */ - public ShortProcessor simulateFrame() { + public ImageS simulateFrame() { float[][] pixels = new float[this.camera.getNX()][this.camera.getNY()]; for (int row = 0; row < pixels.length; row++) Arrays.fill(pixels[row], 0.0f); @@ -226,9 +225,8 @@ public ShortProcessor simulateFrame() { } } - // Convert to short array - FloatProcessor fp = new FloatProcessor(pixels); - return fp.convertToShortProcessor(false); + // Convert to image + return new DefaultImageS(pixels); } /** diff --git a/src/ch/epfl/leb/sass/models/legacy/Device.java b/src/ch/epfl/leb/sass/models/legacy/Device.java index 666ef0e..91deec9 100644 --- a/src/ch/epfl/leb/sass/models/legacy/Device.java +++ b/src/ch/epfl/leb/sass/models/legacy/Device.java @@ -20,6 +20,8 @@ package ch.epfl.leb.sass.models.legacy; import ch.epfl.leb.sass.models.obstructors.Obstructor; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import cern.jet.random.Gamma; import cern.jet.random.Normal; import ij.process.FloatProcessor; @@ -184,7 +186,7 @@ public double getOnEmitterCount() { * and afterwards noise is added. * @return simulated frame */ - public ShortProcessor simulateFrame() { + public ImageS simulateFrame() { float[][] pixels = new float[camera.res_x][camera.res_y]; for (int row = 0; row < pixels.length; row++) Arrays.fill(pixels[row], 0.0f); @@ -213,9 +215,8 @@ public ShortProcessor simulateFrame() { } } - // Convert to short array - FloatProcessor fp = new FloatProcessor(pixels); - return fp.convertToShortProcessor(false); + // Convert to image + return new DefaultImageS(pixels); } /** diff --git a/src/ch/epfl/leb/sass/models/legacy/STORMsim.java b/src/ch/epfl/leb/sass/models/legacy/STORMsim.java index 70a16d3..fa1f4b0 100644 --- a/src/ch/epfl/leb/sass/models/legacy/STORMsim.java +++ b/src/ch/epfl/leb/sass/models/legacy/STORMsim.java @@ -25,13 +25,9 @@ import ch.epfl.leb.sass.utils.RNG; import ch.epfl.leb.sass.simulator.internal.AbstractSimulator; import ch.epfl.leb.sass.models.obstructors.internal.ConstantBackground; -import ij.IJ; -import ij.ImagePlus; -import ij.ImageStack; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import ij.gui.GenericDialog; -import ij.io.FileSaver; -import ij.process.ImageProcessor; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -62,7 +58,7 @@ public STORMsim(Device device) { this.device = device; } int[] res = this.device.getResolution(); - stack = new ImageStack(res[0],res[1]); + stack = new DefaultImageS(res[0], res[1]); emitter_history = new ArrayList(); emitter_history.add(0.0); @@ -74,13 +70,14 @@ public double getTrueSignal(int image_no) { } @Override - public ImageProcessor getNextImage() { + public ImageS getNextImage() { // we calculate emitter count first so it corresponds with the beginning // of the frame rather than end of the frame emitter_history.add(device.getOnEmitterCount()); - ImageProcessor ip = device.simulateFrame(); - stack.addSlice(ip); - return ip; + ImageS is = device.simulateFrame(); + + stack.concatenate(is); + return is; } /** diff --git a/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java b/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java index 91ee13e..6110e31 100644 --- a/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java +++ b/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java @@ -18,9 +18,7 @@ package ch.epfl.leb.sass.server; import ch.epfl.leb.sass.simulator.Simulator; - -import ij.ImagePlus; -import ij.io.FileSaver; +import ch.epfl.leb.sass.utils.images.ImageS; import java.nio.ByteBuffer; @@ -37,11 +35,6 @@ public class RemoteSimulationServiceHandler implements RemoteSimulationService.I */ private Simulator simulator; - /** - * The title of the image returned by the RPC server. - */ - public static final String TITLE = "Current image"; - public RemoteSimulationServiceHandler(Simulator simulator) { this.simulator = simulator; } @@ -54,16 +47,9 @@ public RemoteSimulationServiceHandler(Simulator simulator) { */ @Override public ByteBuffer getNextImage() { - - // Advance the simulation one time step and retrieve the image. - ImagePlus imp; - imp = new ImagePlus(this.TITLE, simulator.getNextImage()); - - // Serialize the image to a byte array. - FileSaver saver = new FileSaver(imp); - byte[] pixels = saver.serialize(); - - return ByteBuffer.wrap(pixels); + // Advance the simulation one time step and retrieve the image. + ImageS is = simulator.getNextImage(); + return is.serializeToBuffer(); } diff --git a/src/ch/epfl/leb/sass/simulator/Simulator.java b/src/ch/epfl/leb/sass/simulator/Simulator.java index 13d44fa..f8bf400 100644 --- a/src/ch/epfl/leb/sass/simulator/Simulator.java +++ b/src/ch/epfl/leb/sass/simulator/Simulator.java @@ -19,6 +19,8 @@ */ package ch.epfl.leb.sass.simulator; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import ij.ImageStack; import ij.process.ImageProcessor; import java.io.File; @@ -48,7 +50,7 @@ public interface Simulator { * Generates a new image and adds it to the internal stack. * @return newly generated image */ - public ImageProcessor getNextImage(); + public ImageS getNextImage(); /** * Increments the simulation by one time step without creating an image. @@ -96,9 +98,10 @@ public interface Simulator { /** * Returns internal stack with all generated images. + * * @return internal stack */ - public ImageStack getStack(); + public ImageS getStack(); /** * diff --git a/src/ch/epfl/leb/sass/simulator/internal/AbstractSimulator.java b/src/ch/epfl/leb/sass/simulator/internal/AbstractSimulator.java index 640cc0b..fda9c8e 100644 --- a/src/ch/epfl/leb/sass/simulator/internal/AbstractSimulator.java +++ b/src/ch/epfl/leb/sass/simulator/internal/AbstractSimulator.java @@ -19,12 +19,10 @@ */ package ch.epfl.leb.sass.simulator.internal; -import ij.ImagePlus; -import ij.ImageStack; -import ij.io.FileSaver; import java.io.File; import java.util.HashMap; import ch.epfl.leb.sass.simulator.Simulator; +import ch.epfl.leb.sass.utils.images.ImageS; /** * @@ -40,7 +38,7 @@ public abstract class AbstractSimulator implements Simulator { /** * Stack to which the generated images are appended. */ - protected ImageStack stack; + protected ImageS stack; /** * Initializes the empty parameters map. @@ -51,13 +49,11 @@ public AbstractSimulator() { @Override public void saveStack(File file) { - ImagePlus imp = new ImagePlus("stack", stack); - FileSaver fs = new FileSaver(imp); - fs.saveAsTiffStack(file.getAbsolutePath()); + stack.saveAsTiffStack(file); } @Override - public ImageStack getStack() { + public ImageS getStack() { return stack; } diff --git a/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java b/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java index ddc20ba..ad34452 100644 --- a/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java +++ b/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java @@ -19,10 +19,9 @@ */ package ch.epfl.leb.sass.simulator.internal; - import ch.epfl.leb.sass.models.Microscope; -import ij.ImageStack; -import ij.process.ImageProcessor; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import java.util.ArrayList; import java.util.HashMap; @@ -52,7 +51,7 @@ public DefaultSimulator(Microscope microscope) { this.microscope = microscope; int[] res = this.microscope.getResolution(); - stack = new ImageStack(res[0],res[1]); + stack = new DefaultImageS(res[0], res[1]); emitterHistory = new ArrayList(); emitterHistory.add(0.0); @@ -65,13 +64,14 @@ public double getTrueSignal(int image_no) { } @Override - public ImageProcessor getNextImage() { + public ImageS getNextImage() { // we calculate emitter count first so it corresponds with the beginning // of the frame rather than end of the frame emitterHistory.add(microscope.getOnEmitterCount()); - ImageProcessor ip = microscope.simulateFrame(); - stack.addSlice(ip); - return ip; + ImageS pixels = microscope.simulateFrame(); + stack.concatenate(pixels); + + return pixels; } /** diff --git a/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java b/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java index b46b708..0678a2f 100644 --- a/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java +++ b/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java @@ -26,21 +26,15 @@ import ch.epfl.leb.alica.controllers.manual.ManualController; import ch.epfl.leb.sass.loggers.StateLogger; import ch.epfl.leb.sass.loggers.PositionLogger; -import ij.ImageStack; -import ij.process.ImageProcessor; +import ch.epfl.leb.sass.utils.images.ImageS; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.JFileChooser; -import javax.swing.filechooser.FileNameExtensionFilter; import org.json.JSONException; import org.json.JSONObject; -import ch.epfl.leb.sass.utils.RNG; -import ij.IJ; -import ch.epfl.leb.sass.simulator.Simulator; /** * The default simulator that is run as, for example, the ImageJ plugin. @@ -130,19 +124,19 @@ public ImageJSimulator( * @param tiff_save_path * @return */ - public ImageStack execute(int no_of_images, int controller_refresh_rate, String csv_save_path, String tiff_save_path) { + public ImageS execute(int no_of_images, int controller_refresh_rate, String csv_save_path, String tiff_save_path) { if (no_of_images < 1 || controller_refresh_rate < 1) { throw new IllegalArgumentException("Wrong simulation parameters!"); } double pixelSize = this.getObjectSpacePixelSize(); - ImageProcessor ip; + ImageS ip; for (image_count = 1; image_count <= no_of_images; image_count++) { JSONObject history_entry = new JSONObject(); ip = this.getNextImage(); analyzer.processImage( - ip.getPixelsCopy(), + ip.getPixelDataPrimitive(0), ip.getWidth(), ip.getHeight(), pixelSize, diff --git a/src/ch/epfl/leb/sass/utils/Runner.java b/src/ch/epfl/leb/sass/utils/Runner.java deleted file mode 100644 index 93c6ab6..0000000 --- a/src/ch/epfl/leb/sass/utils/Runner.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 Laboratory of Experimental Biophysics - * Ecole Polytechnique Federale de Lausanne - * - * Author: Marcel Stefko - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ch.epfl.leb.sass.utils; - -import ch.epfl.leb.sass.models.legacy.Device; -import ij.ImagePlus; -import ij.ImageStack; -import ij.io.FileSaver; -import ij.process.ShortProcessor; - -/** - * Test class for RealTimeGenerator - * @author Marcel Stefko - */ -public class Runner { - - /** - * Generates some images and saves them to a .tif file. - * @param args command line arguments - */ - public static void main(String[] args) { - Device device = new Device(); - ImageStack stack = new ImageStack(400,400); - - for (int i=0; i<100; i++) { - System.out.println(i); - ShortProcessor ip = device.simulateFrame(); - stack.addSlice(ip); - } - device.setLaserPower(0.3); - for (int i=0; i<100; i++) { - System.out.println(i); - ShortProcessor ip = device.simulateFrame(); - stack.addSlice(ip); - } - device.setLaserPower(5); - for (int i=0; i<100; i++) { - System.out.println(i); - ShortProcessor ip = device.simulateFrame(); - stack.addSlice(ip); - } - ImagePlus imp = new ImagePlus("stack", stack); - FileSaver fs = new FileSaver(imp); - fs.saveAsTiffStack("C:\\Users\\stefko\\Desktop\\stackd.tif"); - } -} diff --git a/src/ch/epfl/leb/sass/utils/TiffGenerator.java b/src/ch/epfl/leb/sass/utils/TiffGenerator.java deleted file mode 100644 index f1a82b8..0000000 --- a/src/ch/epfl/leb/sass/utils/TiffGenerator.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2017 Laboratory of Experimental Biophysics - * Ecole Polytechnique Federale de Lausanne - * - * Author: Marcel Stefko - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ch.epfl.leb.sass.utils; - -import ch.epfl.leb.sass.simulator.internal.AbstractSimulator; -import ij.ImagePlus; -import ij.ImageStack; -import ij.io.FileSaver; -import ij.process.ImageProcessor; -import java.io.File; -import java.util.HashMap; -import ch.epfl.leb.sass.simulator.Simulator; - -/** - * Generates images from a .tiff stack image file. - * @author Marcel Stefko - */ -public class TiffGenerator extends AbstractSimulator { - private int count; - - /** - * Initializes the TiffParser and loads up the image stack from a file. - * @param file tiff file to be loaded - */ - public TiffGenerator(File file) { - TiffParser parser = new TiffParser(); - System.out.print("Loading selected .tif file."); - stack = parser.loadGeneralTiff(file); - System.out.format("Tif file loaded: %s\n",file.getAbsolutePath()); - count = 0; - } - - @Override - public ImageProcessor getNextImage() { - count++; - if (count > stack.getSize()) - return null; - return stack.getProcessor(count); - } - - @Override - public void incrementTimeStep(){ - return; - } - - @Override - public void setCustomParameters(HashMap map) { - return; - } - - @Override - public HashMap getCustomParameters() { - return new HashMap(); - } - - @Override - public void setControlSignal(double value) { - return; - } - - @Override - public double getControlSignal() { - return 0.0; - } - - @Override - public double getTrueSignal(int image_no) { - return 0.0; - } - - @Override - public double getObjectSpacePixelSize() { - return 0.1; - } - - - @Override - public double getFOVSize() { - ImageProcessor ip = stack.getProcessor(count); - return getObjectSpacePixelSize()*getObjectSpacePixelSize()*ip.getWidth()*ip.getHeight(); - } - - @Override - public String getShortTrueSignalDescription() { - return ""; - } -} diff --git a/src/ch/epfl/leb/sass/utils/images/ImageS.java b/src/ch/epfl/leb/sass/utils/images/ImageS.java new file mode 100644 index 0000000..f18f3ba --- /dev/null +++ b/src/ch/epfl/leb/sass/utils/images/ImageS.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2017-2018 Laboratory of Experimental Biophysics + * Ecole Polytechnique Fédérale de Lausanne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ch.epfl.leb.sass.utils.images; + +import java.io.File; +import java.nio.ByteBuffer; + +/** + * An abstraction layer for an 3-dimensional image stack in SASS. + * + * This interface allows developers to more easily substitute other backends for + * image data into SASS. For example, one could write an implementation for + * ImgLib2 datatypes to replace ImageJ's original ImageStack. + * + * This interface should be used everywhere image data is passed between SASS + * components. It represents a general purpose, 3-dimensional image stack. + * + * @param The datatype of the pixels. + * @author Kyle M. Douglass + */ +public interface ImageS{ + /** + * Returns the title (or, equivalently, the name) of the image dataset. + * + * @return The title of the dataset. + */ + public String getTitle(); + + /** + * Sets the title (or, equivalently, the name) of the dataset. + * + * @param title The title to give to the image dataset. + */ + public void setTitle(String title); + + /** + * Returns the bit depth of the pixels. + * + * @return The bit depth of the pixels. + */ + public int getBitDepth(); + + /** + * Serializes the dataset into a TIFF-encoded byte array. + * + * @return The image data encoded as a TIFF-file byte string. + */ + public byte[] serializeToArray(); + + /** + * Returns a buffer containing the dataset in a TIFF-encoded byte array. + * + * @return A ByteBuffer containing the TIFF-encoded dataset. + */ + public ByteBuffer serializeToBuffer(); + + /** + * Adds a single image to the dataset. + * + * This method accepts a 2D array of pixels where the indexes are in XY + * order. The length of the array in X and Y must equal the lengths of the + * existing images. + * + * @param image The image data to add to the dataset. + */ + public void addImage(int[][] image); + + /** + * Adds a single image to the dataset. + * + * This method accepts a 2D array of pixels where the indexes are in XY + * order. The length of the array in X and Y must equal the lengths of the + * existing images. + * + * @param image The image data to add to the dataset. + */ + public void addImage(float[][] image); + + /** + * Appends another ImageS dataset to the end of this one. + * + * @param dataset The images to add to the dataset. + */ + public void concatenate(ImageS dataset); + + /** + * Returns the image data at the slice corresponding to index. + * + * @param index + * @return + */ + public T[] getPixelData(int index); + + /** + * Returns the pixel data at the given index as a 1D array of primitives. + * + * This method will break the abstraction because the program will need to + * know the underlying datatype of the image data. It is provided primarily + * for backwards compatability and it is not recommended. + * + * @param index The index of the corresponding slice. + * @return The pixel data at the provided index. + */ + public Object getPixelDataPrimitive(int index); + + /** + * Displays the images. + * + */ + public void view(); + + /** + * Updates the dataset viewer to show the currently active slice. + * + */ + public void updateView(); + + /** + * Sets the active slice of the dataset (0-indexed). + * + * * This is the image that will be displayed in the viewer. + * + * @param index The index of the slice to activate. + */ + public void setSlice(int index); + + /** + * Saves the images to a TIFF file. + * + * @param file The TIFF file where the dataset will be saved. + */ + public void saveAsTiffStack(File file); + + /** + * + * Returns the width of the images in the dataset. + * + * @return The width of the images in the dataset. + */ + public int getWidth(); + + /** + * + * Returns the height of the images in the dataset. + * + * @return The height of the images in the dataset. + */ + public int getHeight(); + + /** + * Returns the number of images in the dataset. + * + * @return The number of images in the dataset. + */ + public int getSize(); + +} \ No newline at end of file diff --git a/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java new file mode 100644 index 0000000..36cfc86 --- /dev/null +++ b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2017-2018 Laboratory of Experimental Biophysics + * Ecole Polytechnique Fédérale de Lausanne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ch.epfl.leb.sass.utils.images.internal; + +import ij.ImageStack; +import ij.ImagePlus; +import ij.process.FloatProcessor; +import ij.io.FileSaver; +import java.io.File; +import java.nio.ByteBuffer; +import ch.epfl.leb.sass.utils.images.ImageS; + +/** + * The default implementation of the ImageS interface. + * + * The default implementation currently wraps ImageJ1's ImageStack class. See + * https://imagej.nih.gov/ij/developer/api/ij/ImagePlus.html for more + * information. + * + * @author Kyle M. Douglass + */ +public class DefaultImageS implements ImageS { + + private ImagePlus imp; + private ImageStack images; + private String title = "SASS Image Dataset"; + + /** + * Creates a new and empty DefaultImageS. + */ + public DefaultImageS(int width, int height) { + images = new ImageStack(width, height); + } + + /** + * Creates a new DefaultImageS object from a 2D array of ints. + * + * The first index of the input array should correspond to x; the second + * corresponds to y. + * + * @param pixels The 2D array of pixel values. + */ + public DefaultImageS(int[][] pixels) { + images = new ImageStack(pixels.length, pixels[0].length); + + FloatProcessor fp = new FloatProcessor(pixels); + images.addSlice(fp.convertToShortProcessor(false)); + imp = new ImagePlus(title, images); + } + + /** + * Creates a new DefaultImageS object from a 2D array of floats. + * + * The first index of the input array should correspond to x; the second + * corresponds to y. + * + * @param pixels The 2D array of pixel values. + */ + public DefaultImageS(float[][] pixels) { + images = new ImageStack(pixels.length, pixels[0].length); + + FloatProcessor fp = new FloatProcessor(pixels); + images.addSlice(fp.convertToShortProcessor(false)); + imp = new ImagePlus(title, images); + } + + @Override + public int getBitDepth() { + return images.getBitDepth(); + } + + /** + * Returns the title of the image stack. + * + * @return The title of the image stack. + */ + @Override + public String getTitle() { + return title; + } + + /** + * Sets the title of the image stack. + * + * @param title The title of the image stack. + */ + @Override + public void setTitle(String title) { + this.title = title; + } + + /** + * Serializes the image stack to a TIFF-encoded byte array. + * + * @return A TIFF-encoded byte array. + */ + @Override + public byte[] serializeToArray() { + ImagePlus imp = new ImagePlus(title, images); + FileSaver fs = new FileSaver(imp); + return fs.serialize(); + } + + /** + * Returns a buffer containing the dataset in a TIFF-encoded byte array. + * + * @return A buffer containing the dataset in a TIFF-encoded byte array. + */ + @Override + public ByteBuffer serializeToBuffer() { + return ByteBuffer.wrap(serializeToArray()); + } + + /** + * Converts a 2D array of ints to 16-bit ints and adds it to the dataset. + * + * @param image A 2D array of ints indexed by xy. + */ + @Override + public void addImage(int[][] image) { + images.addSlice(( new FloatProcessor(image) ).convertToShortProcessor(false)); + + } + + /** + * Converts a 2D array of floats to 16-bit ints and adds it to the dataset. + * + * @param image A 2D array of floats indexed by xy. + */ + @Override + public void addImage(float[][] image) { + images.addSlice(( new FloatProcessor(image) ).convertToShortProcessor(false)); + } + + /** + * Appends another ImageS dataset to the end of this one. + * + * @param dataset The images to add to the dataset. + */ + @Override + public void concatenate(ImageS dataset) { + short[] prim = new short[dataset.getWidth() * dataset.getHeight()]; + + // ImageJ1 *requires* a short array and Java 8 streams do not support + // Short. + for (int i = 0; i < dataset.getSize(); i++) { + Short[] pixels = dataset.getPixelData(i); + for (int j = 0; j < pixels.length; j++) { + prim[j] = (short)pixels[j]; + } + images.addSlice("", prim); + } + } + + /** + * Returns the pixel data at the given index as a 1D array. + * + * @param index The index of the corresponding slice. + * @return The pixel data at the provided index. + */ + @Override + public Short[] getPixelData(int index) { + // ImageJ1 is 1-indexed and follows xy order, where x changes quickest. + short[] temp = (short[])images.getPixels(index + 1); + Short[] pixels = new Short[temp.length]; + for (int i = 0; i < temp.length; i++) { + pixels[i] = new Short(temp[i]); + } + + return pixels; + //return Arrays.stream(temp).boxed().toArray(Short[]::new); + } + + /** + * Returns the pixel data at the given index as a 1D array of primitives. + * + * @param index The index of the corresponding slice. + * @return The pixel data at the provided index as a primitive data type. + */ + @Override + public Object getPixelDataPrimitive(int index) { + // ImageJ1 is 1-indexed and follows xy order, where x changes quickest. + return (short[])images.getPixels(index + 1); + + } + + /** + * Displays the images in a ImagePlus window. + * + */ + @Override + public void view() { + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } + imp.show(); + } + + /** + * Updates the dataset viewer to show the currently active slice. + * + */ + public void updateView() { + imp.updateAndRepaintWindow(); + } + + /** + * Sets the active slice of the dataset (0-indexed). + * + * This is the image that will be displayed in the viewer. + * + * @param index The index of the slice to activate. + */ + public void setSlice(int index) { + imp.setSlice(index); + } + + /** + * Saves the images to a TIFF file. + * + */ + @Override + public void saveAsTiffStack(File file) { + FileSaver fs = new FileSaver(imp); + fs.saveAsTiffStack(file.getAbsolutePath()); + } + + /** + * + * Returns the width of the images in the dataset. + * + * @return The width of the images in the dataset. + */ + @Override + public int getWidth() { + return images.getWidth(); + } + + /** + * + * Returns the height of the images in the dataset. + * + * @return The height of the images in the dataset. + */ + @Override + public int getHeight() { + return images.getHeight(); + } + + /** + * Returns the number of images in the dataset. + * + * @return The number of images in the dataset. + */ + @Override + public int getSize() { + return images.getSize(); + } + +} diff --git a/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java b/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java index a1ec63a..1901ad1 100644 --- a/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java +++ b/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java @@ -19,6 +19,8 @@ import ch.epfl.leb.sass.simulator.internal.RPCSimulator; import ch.epfl.leb.sass.loggers.FrameInfo; +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import ij.IJ; import ij.ImagePlus; @@ -42,11 +44,7 @@ public class RemoteSimulationServiceHandlerTest { private RPCSimulator mockSimulator; private RemoteSimulationServiceHandler handler; - // NOTE: The string argument to createImage() MUST match that used in the - // class implementation. - private ImagePlus imp = IJ.createImage( - RemoteSimulationServiceHandler.TITLE, 1, 1, 1, 16 - ); + private ImageS is = new DefaultImageS( new int[1][1] ); public RemoteSimulationServiceHandlerTest() { this.mockSimulator = mock(RPCSimulator.class); @@ -62,10 +60,10 @@ public void testGetNextImage() { // Instructs the wrapped simulator to return the ImageJ test image. // Instructs the wrapped simulator to return the ImageJ test image. - when(this.mockSimulator.getNextImage()).thenReturn(imp.getProcessor()); + when(this.mockSimulator.getNextImage()).thenReturn(is); ByteBuffer bufferedImage = this.handler.getNextImage(); - byte[] expResult = (new FileSaver(this.imp)).serialize(); + byte[] expResult = is.serializeToArray(); byte[] result = new byte[bufferedImage.remaining()]; bufferedImage.get(result); From 9573cb23dc2c592f51ed0f5a140e4490cd2e9fd6 Mon Sep 17 00:00:00 2001 From: kmdouglass Date: Sun, 11 Feb 2018 13:52:54 +0100 Subject: [PATCH 2/3] Second draft of ImageS abstraction layer along with tests. --- src/ch/epfl/leb/sass/ijplugin/App.java | 65 ++-- .../sass/ijplugin/InitializeSimulation.java | 26 +- .../epfl/leb/sass/models/legacy/STORMsim.java | 3 +- .../sass/server/ImageGenerationException.java | 260 ++++++++++++++ .../sass/server/RemoteSimulationService.java | 139 ++++++- .../RemoteSimulationServiceHandler.java | 17 +- src/ch/epfl/leb/sass/simulator/Simulator.java | 6 +- .../simulator/internal/DefaultSimulator.java | 3 +- .../simulator/internal/ImageJSimulator.java | 10 +- src/ch/epfl/leb/sass/utils/images/ImageS.java | 63 ++-- .../utils/images/ImageShapeException.java | 35 ++ .../utils/images/internal/DefaultImageS.java | 131 +++++-- .../RemoteSimulationServiceHandlerTest.java | 10 +- .../images/internal/DefaultImageSTest.java | 339 ++++++++++++++++++ thrift/RPCServer.thrift | 5 +- thrift/compile.sh | 2 + 16 files changed, 984 insertions(+), 130 deletions(-) create mode 100644 src/ch/epfl/leb/sass/server/ImageGenerationException.java create mode 100644 src/ch/epfl/leb/sass/utils/images/ImageShapeException.java create mode 100644 test/ch/epfl/leb/sass/utils/images/internal/DefaultImageSTest.java create mode 100755 thrift/compile.sh diff --git a/src/ch/epfl/leb/sass/ijplugin/App.java b/src/ch/epfl/leb/sass/ijplugin/App.java index 2d07f7b..a75aeb8 100644 --- a/src/ch/epfl/leb/sass/ijplugin/App.java +++ b/src/ch/epfl/leb/sass/ijplugin/App.java @@ -24,6 +24,8 @@ import ch.epfl.leb.alica.Analyzer; import ch.epfl.leb.alica.Controller; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; +import ij.IJ; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,7 +60,7 @@ public App( Analyzer analyzer, Controller controller, int controller_tickrate - ) { + ) throws ImageShapeException { super(microscope, analyzer, controller); if (controller_tickrate<1) { throw new IllegalArgumentException("Wrong controller tickrate!"); @@ -171,33 +173,44 @@ public void run() { while (!stop) { app.incrementCounter(); - // TODO: Remove the explicit type in the future when cameras models - // with different bit depths are implemented! - ImageS is = app.getNextImage(); - - long time_start, time_end; - time_start = System.nanoTime(); - analyzer.processImage( - is.getPixelDataPrimitive(0), - is.getWidth(), - is.getHeight(), - app.getObjectSpacePixelSize(), - time_start - ); - time_end = System.nanoTime(); - System.out.format("%s: Image analyzed in %d microseconds.\n", analyzer.getName(), (time_end - time_start)/1000); + try { + ImageS is = app.getNextImage(); + + long time_start, time_end; + time_start = System.nanoTime(); + analyzer.processImage( + is.getPixelData(0), + is.getWidth(), + is.getHeight(), + app.getObjectSpacePixelSize(), + time_start + ); + time_end = System.nanoTime(); + System.out.format( + "%s: Image analyzed in %d microseconds.\n", + analyzer.getName(), + (time_end - time_start)/1000 + ); - if (app.getImageCount() % app.getControllerTickrate() == 0) { - //System.out.println(image_count); - controller.nextValue(analyzer.getBatchOutput()); - } - app.setControlSignal(controller.getCurrentOutput()); - - app.getAnalyzerOutput().add(analyzer.getIntermittentOutput()); - app.getControllerOutput().add(controller.getCurrentOutput()); - app.getControllerSetpoint().add(controller.getSetpoint()); - app.getGeneratorTrueSignal().add(app.getTrueSignal(app.getImageCount())); + if (app.getImageCount() % app.getControllerTickrate() == 0) { + //System.out.println(image_count); + controller.nextValue(analyzer.getBatchOutput()); + } + app.setControlSignal(controller.getCurrentOutput()); + + app.getAnalyzerOutput().add(analyzer.getIntermittentOutput()); + app.getControllerOutput().add(controller.getCurrentOutput()); + app.getControllerSetpoint().add(controller.getSetpoint()); + app.getGeneratorTrueSignal().add(app.getTrueSignal(app.getImageCount())); + } catch (ImageShapeException ex) { + IJ.showMessage( + "Critical error: simulated images don't match the " + + "shape of the existing dataset" + ); + ex.printStackTrace(); + stop = true; + } imp.setSlice(imp.getSize() - 1); imp.updateView(); diff --git a/src/ch/epfl/leb/sass/ijplugin/InitializeSimulation.java b/src/ch/epfl/leb/sass/ijplugin/InitializeSimulation.java index 6f5677a..d8feb22 100644 --- a/src/ch/epfl/leb/sass/ijplugin/InitializeSimulation.java +++ b/src/ch/epfl/leb/sass/ijplugin/InitializeSimulation.java @@ -27,6 +27,7 @@ import ch.epfl.leb.alica.analyzers.AnalyzerFactory; import ch.epfl.leb.alica.analyzers.AnalyzerSetupPanel; import ch.epfl.leb.alica.controllers.ControllerFactory; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import ij.IJ; import java.awt.GridLayout; import java.io.File; @@ -2416,15 +2417,22 @@ private void initializeSimulationActionPerformed(java.awt.event.ActionEvent evt) } controller_factory.setMaxControllerOutput(max_controller_output); - // The simulation engine - App app = new App( - microscope, - analyzer_factory.build(), - controller_factory.build(), - controller_tickrate - ); - main.setApp(app); - this.dispose(); + try { + // The simulation engine + App app = new App( + microscope, + analyzer_factory.build(), + controller_factory.build(), + controller_tickrate + ); + main.setApp(app); + this.dispose(); + } catch (ImageShapeException ex) { + IJ.showMessage( + "Shape error encountered when generating the ImageS " + + "dataset." + ); + } }//GEN-LAST:event_initializeSimulationActionPerformed /** * Opens a SASS settings file and updates the GUI accordingly. diff --git a/src/ch/epfl/leb/sass/models/legacy/STORMsim.java b/src/ch/epfl/leb/sass/models/legacy/STORMsim.java index fa1f4b0..bdfccb5 100644 --- a/src/ch/epfl/leb/sass/models/legacy/STORMsim.java +++ b/src/ch/epfl/leb/sass/models/legacy/STORMsim.java @@ -26,6 +26,7 @@ import ch.epfl.leb.sass.simulator.internal.AbstractSimulator; import ch.epfl.leb.sass.models.obstructors.internal.ConstantBackground; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import ij.gui.GenericDialog; import java.io.IOException; @@ -70,7 +71,7 @@ public double getTrueSignal(int image_no) { } @Override - public ImageS getNextImage() { + public ImageS getNextImage() throws ImageShapeException { // we calculate emitter count first so it corresponds with the beginning // of the frame rather than end of the frame emitter_history.add(device.getOnEmitterCount()); diff --git a/src/ch/epfl/leb/sass/server/ImageGenerationException.java b/src/ch/epfl/leb/sass/server/ImageGenerationException.java new file mode 100644 index 0000000..92f2083 --- /dev/null +++ b/src/ch/epfl/leb/sass/server/ImageGenerationException.java @@ -0,0 +1,260 @@ +/** + * Autogenerated by Thrift Compiler (0.11.0) + * + * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + * @generated + */ +package ch.epfl.leb.sass.server; + +@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-02-11") +public class ImageGenerationException extends org.apache.thrift.TException implements org.apache.thrift.TBase, java.io.Serializable, Cloneable, Comparable { + private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("ImageGenerationException"); + + + private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new ImageGenerationExceptionStandardSchemeFactory(); + private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new ImageGenerationExceptionTupleSchemeFactory(); + + + /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ + public enum _Fields implements org.apache.thrift.TFieldIdEnum { +; + + private static final java.util.Map byName = new java.util.HashMap(); + + static { + for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) { + byName.put(field.getFieldName(), field); + } + } + + /** + * Find the _Fields constant that matches fieldId, or null if its not found. + */ + public static _Fields findByThriftId(int fieldId) { + switch(fieldId) { + default: + return null; + } + } + + /** + * Find the _Fields constant that matches fieldId, throwing an exception + * if it is not found. + */ + public static _Fields findByThriftIdOrThrow(int fieldId) { + _Fields fields = findByThriftId(fieldId); + if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!"); + return fields; + } + + /** + * Find the _Fields constant that matches name, or null if its not found. + */ + public static _Fields findByName(java.lang.String name) { + return byName.get(name); + } + + private final short _thriftId; + private final java.lang.String _fieldName; + + _Fields(short thriftId, java.lang.String fieldName) { + _thriftId = thriftId; + _fieldName = fieldName; + } + + public short getThriftFieldId() { + return _thriftId; + } + + public java.lang.String getFieldName() { + return _fieldName; + } + } + public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap; + static { + java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); + metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); + org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(ImageGenerationException.class, metaDataMap); + } + + public ImageGenerationException() { + } + + /** + * Performs a deep copy on other. + */ + public ImageGenerationException(ImageGenerationException other) { + } + + public ImageGenerationException deepCopy() { + return new ImageGenerationException(this); + } + + @Override + public void clear() { + } + + public void setFieldValue(_Fields field, java.lang.Object value) { + switch (field) { + } + } + + public java.lang.Object getFieldValue(_Fields field) { + switch (field) { + } + throw new java.lang.IllegalStateException(); + } + + /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */ + public boolean isSet(_Fields field) { + if (field == null) { + throw new java.lang.IllegalArgumentException(); + } + + switch (field) { + } + throw new java.lang.IllegalStateException(); + } + + @Override + public boolean equals(java.lang.Object that) { + if (that == null) + return false; + if (that instanceof ImageGenerationException) + return this.equals((ImageGenerationException)that); + return false; + } + + public boolean equals(ImageGenerationException that) { + if (that == null) + return false; + if (this == that) + return true; + + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + + return hashCode; + } + + @Override + public int compareTo(ImageGenerationException other) { + if (!getClass().equals(other.getClass())) { + return getClass().getName().compareTo(other.getClass().getName()); + } + + int lastComparison = 0; + + return 0; + } + + public _Fields fieldForId(int fieldId) { + return _Fields.findByThriftId(fieldId); + } + + public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException { + scheme(iprot).read(iprot, this); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException { + scheme(oprot).write(oprot, this); + } + + @Override + public java.lang.String toString() { + java.lang.StringBuilder sb = new java.lang.StringBuilder("ImageGenerationException("); + boolean first = true; + + sb.append(")"); + return sb.toString(); + } + + public void validate() throws org.apache.thrift.TException { + // check for required fields + // check for sub-struct validity + } + + private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { + try { + write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { + try { + read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in))); + } catch (org.apache.thrift.TException te) { + throw new java.io.IOException(te); + } + } + + private static class ImageGenerationExceptionStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public ImageGenerationExceptionStandardScheme getScheme() { + return new ImageGenerationExceptionStandardScheme(); + } + } + + private static class ImageGenerationExceptionStandardScheme extends org.apache.thrift.scheme.StandardScheme { + + public void read(org.apache.thrift.protocol.TProtocol iprot, ImageGenerationException struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TField schemeField; + iprot.readStructBegin(); + while (true) + { + schemeField = iprot.readFieldBegin(); + if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { + break; + } + switch (schemeField.id) { + default: + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + + // check for required fields of primitive type, which can't be checked in the validate method + struct.validate(); + } + + public void write(org.apache.thrift.protocol.TProtocol oprot, ImageGenerationException struct) throws org.apache.thrift.TException { + struct.validate(); + + oprot.writeStructBegin(STRUCT_DESC); + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + + } + + private static class ImageGenerationExceptionTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory { + public ImageGenerationExceptionTupleScheme getScheme() { + return new ImageGenerationExceptionTupleScheme(); + } + } + + private static class ImageGenerationExceptionTupleScheme extends org.apache.thrift.scheme.TupleScheme { + + @Override + public void write(org.apache.thrift.protocol.TProtocol prot, ImageGenerationException struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + } + + @Override + public void read(org.apache.thrift.protocol.TProtocol prot, ImageGenerationException struct) throws org.apache.thrift.TException { + org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; + } + } + + private static S scheme(org.apache.thrift.protocol.TProtocol proto) { + return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme(); + } +} + diff --git a/src/ch/epfl/leb/sass/server/RemoteSimulationService.java b/src/ch/epfl/leb/sass/server/RemoteSimulationService.java index 92760c9..80756b2 100644 --- a/src/ch/epfl/leb/sass/server/RemoteSimulationService.java +++ b/src/ch/epfl/leb/sass/server/RemoteSimulationService.java @@ -7,7 +7,7 @@ package ch.epfl.leb.sass.server; @SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"}) -@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-01-24") +@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.11.0)", date = "2018-02-11") public class RemoteSimulationService { public interface Iface { @@ -20,7 +20,7 @@ public interface Iface { /** * Increments the simulation by one time step and returns an image. */ - public java.nio.ByteBuffer getNextImage() throws org.apache.thrift.TException; + public java.nio.ByteBuffer getNextImage() throws ImageGenerationException, org.apache.thrift.TException; /** * Changes the simulation'ss fluorescence activation laser power. @@ -91,7 +91,7 @@ public java.lang.String recv_getServerStatus() throws org.apache.thrift.TExcepti throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "getServerStatus failed: unknown result"); } - public java.nio.ByteBuffer getNextImage() throws org.apache.thrift.TException + public java.nio.ByteBuffer getNextImage() throws ImageGenerationException, org.apache.thrift.TException { send_getNextImage(); return recv_getNextImage(); @@ -103,13 +103,16 @@ public void send_getNextImage() throws org.apache.thrift.TException sendBase("getNextImage", args); } - public java.nio.ByteBuffer recv_getNextImage() throws org.apache.thrift.TException + public java.nio.ByteBuffer recv_getNextImage() throws ImageGenerationException, org.apache.thrift.TException { getNextImage_result result = new getNextImage_result(); receiveBase(result, "getNextImage"); if (result.isSetSuccess()) { return result.success; } + if (result.ex != null) { + throw result.ex; + } throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "getNextImage failed: unknown result"); } @@ -221,7 +224,7 @@ public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apa prot.writeMessageEnd(); } - public java.nio.ByteBuffer getResult() throws org.apache.thrift.TException { + public java.nio.ByteBuffer getResult() throws ImageGenerationException, org.apache.thrift.TException { if (getState() != org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) { throw new java.lang.IllegalStateException("Method call not finished!"); } @@ -357,7 +360,11 @@ protected boolean handleRuntimeExceptions() { public getNextImage_result getResult(I iface, getNextImage_args args) throws org.apache.thrift.TException { getNextImage_result result = new getNextImage_result(); - result.success = iface.getNextImage(); + try { + result.success = iface.getNextImage(); + } catch (ImageGenerationException ex) { + result.ex = ex; + } return result; } } @@ -522,7 +529,11 @@ public void onError(java.lang.Exception e) { byte msgType = org.apache.thrift.protocol.TMessageType.REPLY; org.apache.thrift.TSerializable msg; getNextImage_result result = new getNextImage_result(); - if (e instanceof org.apache.thrift.transport.TTransportException) { + if (e instanceof ImageGenerationException) { + result.ex = (ImageGenerationException) e; + result.setExIsSet(true); + msg = result; + } else if (e instanceof org.apache.thrift.transport.TTransportException) { _LOGGER.error("TTransportException inside handler", e); fb.close(); return; @@ -1543,15 +1554,18 @@ public static class getNextImage_result implements org.apache.thrift.TBase byName = new java.util.HashMap(); @@ -1568,6 +1582,8 @@ public static _Fields findByThriftId(int fieldId) { switch(fieldId) { case 0: // SUCCESS return SUCCESS; + case 1: // EX + return EX; default: return null; } @@ -1613,6 +1629,8 @@ public java.lang.String getFieldName() { java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class); tmpMap.put(_Fields.SUCCESS, new org.apache.thrift.meta_data.FieldMetaData("success", org.apache.thrift.TFieldRequirementType.DEFAULT, new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING , true))); + tmpMap.put(_Fields.EX, new org.apache.thrift.meta_data.FieldMetaData("ex", org.apache.thrift.TFieldRequirementType.DEFAULT, + new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, ImageGenerationException.class))); metaDataMap = java.util.Collections.unmodifiableMap(tmpMap); org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(getNextImage_result.class, metaDataMap); } @@ -1621,10 +1639,12 @@ public getNextImage_result() { } public getNextImage_result( - java.nio.ByteBuffer success) + java.nio.ByteBuffer success, + ImageGenerationException ex) { this(); this.success = org.apache.thrift.TBaseHelper.copyBinary(success); + this.ex = ex; } /** @@ -1634,6 +1654,9 @@ public getNextImage_result(getNextImage_result other) { if (other.isSetSuccess()) { this.success = org.apache.thrift.TBaseHelper.copyBinary(other.success); } + if (other.isSetEx()) { + this.ex = new ImageGenerationException(other.ex); + } } public getNextImage_result deepCopy() { @@ -1643,6 +1666,7 @@ public getNextImage_result deepCopy() { @Override public void clear() { this.success = null; + this.ex = null; } public byte[] getSuccess() { @@ -1679,6 +1703,30 @@ public void setSuccessIsSet(boolean value) { } } + public ImageGenerationException getEx() { + return this.ex; + } + + public getNextImage_result setEx(ImageGenerationException ex) { + this.ex = ex; + return this; + } + + public void unsetEx() { + this.ex = null; + } + + /** Returns true if field ex is set (has been assigned a value) and false otherwise */ + public boolean isSetEx() { + return this.ex != null; + } + + public void setExIsSet(boolean value) { + if (!value) { + this.ex = null; + } + } + public void setFieldValue(_Fields field, java.lang.Object value) { switch (field) { case SUCCESS: @@ -1693,6 +1741,14 @@ public void setFieldValue(_Fields field, java.lang.Object value) { } break; + case EX: + if (value == null) { + unsetEx(); + } else { + setEx((ImageGenerationException)value); + } + break; + } } @@ -1701,6 +1757,9 @@ public java.lang.Object getFieldValue(_Fields field) { case SUCCESS: return getSuccess(); + case EX: + return getEx(); + } throw new java.lang.IllegalStateException(); } @@ -1714,6 +1773,8 @@ public boolean isSet(_Fields field) { switch (field) { case SUCCESS: return isSetSuccess(); + case EX: + return isSetEx(); } throw new java.lang.IllegalStateException(); } @@ -1742,6 +1803,15 @@ public boolean equals(getNextImage_result that) { return false; } + boolean this_present_ex = true && this.isSetEx(); + boolean that_present_ex = true && that.isSetEx(); + if (this_present_ex || that_present_ex) { + if (!(this_present_ex && that_present_ex)) + return false; + if (!this.ex.equals(that.ex)) + return false; + } + return true; } @@ -1753,6 +1823,10 @@ public int hashCode() { if (isSetSuccess()) hashCode = hashCode * 8191 + success.hashCode(); + hashCode = hashCode * 8191 + ((isSetEx()) ? 131071 : 524287); + if (isSetEx()) + hashCode = hashCode * 8191 + ex.hashCode(); + return hashCode; } @@ -1774,6 +1848,16 @@ public int compareTo(getNextImage_result other) { return lastComparison; } } + lastComparison = java.lang.Boolean.valueOf(isSetEx()).compareTo(other.isSetEx()); + if (lastComparison != 0) { + return lastComparison; + } + if (isSetEx()) { + lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.ex, other.ex); + if (lastComparison != 0) { + return lastComparison; + } + } return 0; } @@ -1801,6 +1885,14 @@ public java.lang.String toString() { org.apache.thrift.TBaseHelper.toString(this.success, sb); } first = false; + if (!first) sb.append(", "); + sb.append("ex:"); + if (this.ex == null) { + sb.append("null"); + } else { + sb.append(this.ex); + } + first = false; sb.append(")"); return sb.toString(); } @@ -1852,6 +1944,15 @@ public void read(org.apache.thrift.protocol.TProtocol iprot, getNextImage_result org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } break; + case 1: // EX + if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) { + struct.ex = new ImageGenerationException(); + struct.ex.read(iprot); + struct.setExIsSet(true); + } else { + org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); + } + break; default: org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type); } @@ -1872,6 +1973,11 @@ public void write(org.apache.thrift.protocol.TProtocol oprot, getNextImage_resul oprot.writeBinary(struct.success); oprot.writeFieldEnd(); } + if (struct.ex != null) { + oprot.writeFieldBegin(EX_FIELD_DESC); + struct.ex.write(oprot); + oprot.writeFieldEnd(); + } oprot.writeFieldStop(); oprot.writeStructEnd(); } @@ -1893,20 +1999,31 @@ public void write(org.apache.thrift.protocol.TProtocol prot, getNextImage_result if (struct.isSetSuccess()) { optionals.set(0); } - oprot.writeBitSet(optionals, 1); + if (struct.isSetEx()) { + optionals.set(1); + } + oprot.writeBitSet(optionals, 2); if (struct.isSetSuccess()) { oprot.writeBinary(struct.success); } + if (struct.isSetEx()) { + struct.ex.write(oprot); + } } @Override public void read(org.apache.thrift.protocol.TProtocol prot, getNextImage_result struct) throws org.apache.thrift.TException { org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot; - java.util.BitSet incoming = iprot.readBitSet(1); + java.util.BitSet incoming = iprot.readBitSet(2); if (incoming.get(0)) { struct.success = iprot.readBinary(); struct.setSuccessIsSet(true); } + if (incoming.get(1)) { + struct.ex = new ImageGenerationException(); + struct.ex.read(iprot); + struct.setExIsSet(true); + } } } diff --git a/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java b/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java index 6110e31..069f4ae 100644 --- a/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java +++ b/src/ch/epfl/leb/sass/server/RemoteSimulationServiceHandler.java @@ -19,6 +19,8 @@ import ch.epfl.leb.sass.simulator.Simulator; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; +import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import java.nio.ByteBuffer; @@ -44,13 +46,18 @@ public RemoteSimulationServiceHandler(Simulator simulator) { * * @return A buffer containing the TIFF-encoded byte string of the * simulator's next image. + * @throws ch.epfl.leb.sass.server.ImageGenerationException */ @Override - public ByteBuffer getNextImage() { - // Advance the simulation one time step and retrieve the image. - ImageS is = simulator.getNextImage(); - return is.serializeToBuffer(); - + public ByteBuffer getNextImage() throws ImageGenerationException { + // Advance the simulation one time step and retrieve the image. + try { + ImageS is = simulator.getNextImage(); + return is.serializeToBuffer(); + } catch (ImageShapeException ex) { + ex.printStackTrace(); + throw new ImageGenerationException(); + } } /** diff --git a/src/ch/epfl/leb/sass/simulator/Simulator.java b/src/ch/epfl/leb/sass/simulator/Simulator.java index f8bf400..3770489 100644 --- a/src/ch/epfl/leb/sass/simulator/Simulator.java +++ b/src/ch/epfl/leb/sass/simulator/Simulator.java @@ -20,9 +20,7 @@ package ch.epfl.leb.sass.simulator; import ch.epfl.leb.sass.utils.images.ImageS; -import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; -import ij.ImageStack; -import ij.process.ImageProcessor; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import java.io.File; import java.util.HashMap; @@ -50,7 +48,7 @@ public interface Simulator { * Generates a new image and adds it to the internal stack. * @return newly generated image */ - public ImageS getNextImage(); + public ImageS getNextImage() throws ImageShapeException; /** * Increments the simulation by one time step without creating an image. diff --git a/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java b/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java index ad34452..56c6635 100644 --- a/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java +++ b/src/ch/epfl/leb/sass/simulator/internal/DefaultSimulator.java @@ -21,6 +21,7 @@ import ch.epfl.leb.sass.models.Microscope; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; import java.util.ArrayList; import java.util.HashMap; @@ -64,7 +65,7 @@ public double getTrueSignal(int image_no) { } @Override - public ImageS getNextImage() { + public ImageS getNextImage() throws ImageShapeException { // we calculate emitter count first so it corresponds with the beginning // of the frame rather than end of the frame emitterHistory.add(microscope.getOnEmitterCount()); diff --git a/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java b/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java index 0678a2f..d1d88f4 100644 --- a/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java +++ b/src/ch/epfl/leb/sass/simulator/internal/ImageJSimulator.java @@ -27,6 +27,7 @@ import ch.epfl.leb.sass.loggers.StateLogger; import ch.epfl.leb.sass.loggers.PositionLogger; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; @@ -124,7 +125,12 @@ public ImageJSimulator( * @param tiff_save_path * @return */ - public ImageS execute(int no_of_images, int controller_refresh_rate, String csv_save_path, String tiff_save_path) { + public ImageS execute( + int no_of_images, + int controller_refresh_rate, + String csv_save_path, + String tiff_save_path + ) throws ImageShapeException { if (no_of_images < 1 || controller_refresh_rate < 1) { throw new IllegalArgumentException("Wrong simulation parameters!"); } @@ -136,7 +142,7 @@ public ImageS execute(int no_of_images, int controller_refresh_rate, String csv_ ip = this.getNextImage(); analyzer.processImage( - ip.getPixelDataPrimitive(0), + ip.getPixelData(0), ip.getWidth(), ip.getHeight(), pixelSize, diff --git a/src/ch/epfl/leb/sass/utils/images/ImageS.java b/src/ch/epfl/leb/sass/utils/images/ImageS.java index f18f3ba..4135b9a 100644 --- a/src/ch/epfl/leb/sass/utils/images/ImageS.java +++ b/src/ch/epfl/leb/sass/utils/images/ImageS.java @@ -21,19 +21,18 @@ import java.nio.ByteBuffer; /** - * An abstraction layer for an 3-dimensional image stack in SASS. + * An abstraction layer for a 3-dimensional, 16-bit image stack in SASS. * * This interface allows developers to more easily substitute other backends for * image data into SASS. For example, one could write an implementation for * ImgLib2 datatypes to replace ImageJ's original ImageStack. * * This interface should be used everywhere image data is passed between SASS - * components. It represents a general purpose, 3-dimensional image stack. + * components. * - * @param The datatype of the pixels. * @author Kyle M. Douglass */ -public interface ImageS{ +public interface ImageS{ /** * Returns the title (or, equivalently, the name) of the image dataset. * @@ -69,34 +68,53 @@ public interface ImageS{ */ public ByteBuffer serializeToBuffer(); + /** + * Adds a single image to the dataset. + * + * This method accepts a 2D array of pixels and adds it to the end of the + * dataset. The size of the image in X and Y must be the same as the + * existing images. + * + * @param image The image data to add to the dataset. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException + */ + public void addImage(short[][] image) throws ImageShapeException; + /** * Adds a single image to the dataset. * - * This method accepts a 2D array of pixels where the indexes are in XY - * order. The length of the array in X and Y must equal the lengths of the + * This method accepts a 2D array of pixels and adds it to the end of the + * dataset. The size of the image in X and Y must be the same as the * existing images. * + * Integer data will be truncated into shorts. + * * @param image The image data to add to the dataset. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException */ - public void addImage(int[][] image); + public void addImage(int[][] image) throws ImageShapeException; /** * Adds a single image to the dataset. * - * This method accepts a 2D array of pixels where the indexes are in XY - * order. The length of the array in X and Y must equal the lengths of the + * This method accepts a 2D array of pixels and adds it to the end of the + * dataset. The size of the image in X and Y must be the same as the * existing images. * + * Float data will be truncated into shorts. + * * @param image The image data to add to the dataset. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException */ - public void addImage(float[][] image); + public void addImage(float[][] image) throws ImageShapeException; /** * Appends another ImageS dataset to the end of this one. * * @param dataset The images to add to the dataset. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException */ - public void concatenate(ImageS dataset); + public void concatenate(ImageS dataset) throws ImageShapeException; /** * Returns the image data at the slice corresponding to index. @@ -104,19 +122,7 @@ public interface ImageS{ * @param index * @return */ - public T[] getPixelData(int index); - - /** - * Returns the pixel data at the given index as a 1D array of primitives. - * - * This method will break the abstraction because the program will need to - * know the underlying datatype of the image data. It is provided primarily - * for backwards compatability and it is not recommended. - * - * @param index The index of the corresponding slice. - * @return The pixel data at the provided index. - */ - public Object getPixelDataPrimitive(int index); + public short[] getPixelData(int index); /** * Displays the images. @@ -130,6 +136,15 @@ public interface ImageS{ */ public void updateView(); + /** + * Gets the active slice of the dataset (0-indexed). + * + * This is the image that will be displayed in the viewer. + * + * @return The index of the current slice. + */ + public int getSlice(); + /** * Sets the active slice of the dataset (0-indexed). * diff --git a/src/ch/epfl/leb/sass/utils/images/ImageShapeException.java b/src/ch/epfl/leb/sass/utils/images/ImageShapeException.java new file mode 100644 index 0000000..bccf4ab --- /dev/null +++ b/src/ch/epfl/leb/sass/utils/images/ImageShapeException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017-2018 Laboratory of Experimental Biophysics + * Ecole Polytechnique Fédérale de Lausanne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ch.epfl.leb.sass.utils.images; + +/** + * Raised when trying to add data to ImageS datasets of the wrong XY shape. + * + * @author Kyle M. Douglass + */ +public class ImageShapeException extends Exception { + + public ImageShapeException(){ + super(); + } + + public ImageShapeException(String message){ + super(message); + } + +} diff --git a/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java index 36cfc86..61d962d 100644 --- a/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java +++ b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java @@ -24,6 +24,7 @@ import java.io.File; import java.nio.ByteBuffer; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; /** * The default implementation of the ImageS interface. @@ -34,11 +35,12 @@ * * @author Kyle M. Douglass */ -public class DefaultImageS implements ImageS { +public class DefaultImageS implements ImageS { private ImagePlus imp; private ImageStack images; private String title = "SASS Image Dataset"; + private final int BITDEPTH = 16; /** * Creates a new and empty DefaultImageS. @@ -81,6 +83,9 @@ public DefaultImageS(float[][] pixels) { @Override public int getBitDepth() { + if (images.getSize() == 0) { + return BITDEPTH; + } return images.getBitDepth(); } @@ -111,7 +116,11 @@ public void setTitle(String title) { */ @Override public byte[] serializeToArray() { - ImagePlus imp = new ImagePlus(title, images); + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } + FileSaver fs = new FileSaver(imp); return fs.serialize(); } @@ -127,23 +136,50 @@ public ByteBuffer serializeToBuffer() { } /** - * Converts a 2D array of ints to 16-bit ints and adds it to the dataset. + * Adds a 2D array of shorts to the dataset. * - * @param image A 2D array of ints indexed by xy. + * @param image A 2D array of shorts. */ @Override - public void addImage(int[][] image) { - images.addSlice(( new FloatProcessor(image) ).convertToShortProcessor(false)); + public void addImage(short[][] image) throws ImageShapeException { + checkSize(image.length, image[0].length); + short[] flattened = new short[image.length * image[0].length]; + int s = 0; + + // Note that ImageJ1 flattens arrays according to column-row order, i.e. + // the slowest changing variable is the column, not the row! + for (int i = 0; i < image.length; i++) { + for (int j = 0; j < image[0].length; j++) { + flattened[s] = image[j][i]; + s++; + } + } + + images.addSlice("", flattened); + } + + /** + * Converts a 2D array of ints to 16-bit shorts and adds it to the dataset. + * + * @param image A 2D array of ints indexed by xy. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException + */ + @Override + public void addImage(int[][] image) throws ImageShapeException { + checkSize(image.length, image[0].length); + images.addSlice(( new FloatProcessor(image) ).convertToShortProcessor(false)); } /** - * Converts a 2D array of floats to 16-bit ints and adds it to the dataset. + * Converts a 2D array of floats to 16-bit shorts and adds it to the dataset. * * @param image A 2D array of floats indexed by xy. + * @throws ch.epfl.leb.sass.utils.images.ImageShapeException */ @Override - public void addImage(float[][] image) { + public void addImage(float[][] image) throws ImageShapeException { + checkSize(image.length, image[0].length); images.addSlice(( new FloatProcessor(image) ).convertToShortProcessor(false)); } @@ -153,17 +189,12 @@ public void addImage(float[][] image) { * @param dataset The images to add to the dataset. */ @Override - public void concatenate(ImageS dataset) { - short[] prim = new short[dataset.getWidth() * dataset.getHeight()]; + public void concatenate(ImageS dataset) throws ImageShapeException { + checkSize(dataset.getWidth(), dataset.getHeight()); - // ImageJ1 *requires* a short array and Java 8 streams do not support - // Short. for (int i = 0; i < dataset.getSize(); i++) { - Short[] pixels = dataset.getPixelData(i); - for (int j = 0; j < pixels.length; j++) { - prim[j] = (short)pixels[j]; - } - images.addSlice("", prim); + short[] pixels = dataset.getPixelData(i); + images.addSlice("", pixels); } } @@ -174,29 +205,9 @@ public void concatenate(ImageS dataset) { * @return The pixel data at the provided index. */ @Override - public Short[] getPixelData(int index) { - // ImageJ1 is 1-indexed and follows xy order, where x changes quickest. - short[] temp = (short[])images.getPixels(index + 1); - Short[] pixels = new Short[temp.length]; - for (int i = 0; i < temp.length; i++) { - pixels[i] = new Short(temp[i]); - } - - return pixels; - //return Arrays.stream(temp).boxed().toArray(Short[]::new); - } - - /** - * Returns the pixel data at the given index as a 1D array of primitives. - * - * @param index The index of the corresponding slice. - * @return The pixel data at the provided index as a primitive data type. - */ - @Override - public Object getPixelDataPrimitive(int index) { + public short[] getPixelData(int index) { // ImageJ1 is 1-indexed and follows xy order, where x changes quickest. return (short[])images.getPixels(index + 1); - } /** @@ -216,10 +227,27 @@ public void view() { * Updates the dataset viewer to show the currently active slice. * */ + @Override public void updateView() { imp.updateAndRepaintWindow(); } + /** + * Gets the active slice of the dataset (0-indexed). + * + * This is the image that will be displayed in the viewer. + * + * @return The index of the active slice. + */ + @Override + public int getSlice() { + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } + return imp.getSlice(); + } + /** * Sets the active slice of the dataset (0-indexed). * @@ -227,7 +255,12 @@ public void updateView() { * * @param index The index of the slice to activate. */ + @Override public void setSlice(int index) { + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } imp.setSlice(index); } @@ -236,7 +269,12 @@ public void setSlice(int index) { * */ @Override - public void saveAsTiffStack(File file) { + public void saveAsTiffStack(File file) throws IllegalArgumentException { + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } + FileSaver fs = new FileSaver(imp); fs.saveAsTiffStack(file.getAbsolutePath()); } @@ -273,4 +311,19 @@ public int getSize() { return images.getSize(); } + /** + * Verify that the size of the array matches the size of the dataset. + * + * @param width The width of the input array. + * @param height The height of the input array. + */ + private void checkSize(int width, int height) throws ImageShapeException { + if ( (width != this.getWidth()) || (height != this.getHeight()) ) { + throw new ImageShapeException( + "Error: trying to add two ImageS datasets with different" + + " widths and/or heights." + ); + } + } + } diff --git a/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java b/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java index 1901ad1..b49d048 100644 --- a/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java +++ b/test/ch/epfl/leb/sass/server/RemoteSimulationServiceHandlerTest.java @@ -20,12 +20,9 @@ import ch.epfl.leb.sass.simulator.internal.RPCSimulator; import ch.epfl.leb.sass.loggers.FrameInfo; import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; import ch.epfl.leb.sass.utils.images.internal.DefaultImageS; -import ij.IJ; -import ij.ImagePlus; -import ij.io.FileSaver; - import com.google.gson.Gson; import java.nio.ByteBuffer; @@ -44,7 +41,7 @@ public class RemoteSimulationServiceHandlerTest { private RPCSimulator mockSimulator; private RemoteSimulationServiceHandler handler; - private ImageS is = new DefaultImageS( new int[1][1] ); + private ImageS is = new DefaultImageS( new int[1][1] ); public RemoteSimulationServiceHandlerTest() { this.mockSimulator = mock(RPCSimulator.class); @@ -55,9 +52,8 @@ public RemoteSimulationServiceHandlerTest() { * Test of getNextImage method, of class RemoteSimulationServiceHandler. */ @Test - public void testGetNextImage() { + public void testGetNextImage() throws ImageShapeException, ImageGenerationException { System.out.println("getNextImage"); - // Instructs the wrapped simulator to return the ImageJ test image. // Instructs the wrapped simulator to return the ImageJ test image. when(this.mockSimulator.getNextImage()).thenReturn(is); diff --git a/test/ch/epfl/leb/sass/utils/images/internal/DefaultImageSTest.java b/test/ch/epfl/leb/sass/utils/images/internal/DefaultImageSTest.java new file mode 100644 index 0000000..4f71df8 --- /dev/null +++ b/test/ch/epfl/leb/sass/utils/images/internal/DefaultImageSTest.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2017-2018 Laboratory of Experimental Biophysics + * Ecole Polytechnique Fédérale de Lausanne + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ch.epfl.leb.sass.utils.images.internal; + +import ch.epfl.leb.sass.utils.images.ImageS; +import ch.epfl.leb.sass.utils.images.ImageShapeException; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.junit.Test; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import static org.junit.Assert.*; + +/** + * Test suite for DefaultImageS. + * + * @author Kyle M. Douglass + */ +public class DefaultImageSTest { + + private final int WIDTH = 2; + private final int HEIGHT = 2; + private final int BITDEPTH = 16; + private final String TITLE = "SASS Image Dataset"; + + DefaultImageS instance = null; + + @Rule + public TemporaryFolder tempDir = new TemporaryFolder(); + + @Before + public void setUp() { + instance = new DefaultImageS(WIDTH, HEIGHT); + } + + /** + * Test of getBitDepth method, of class DefaultImageS. + */ + @Test + public void testGetBitDepth() throws ImageShapeException { + System.out.println("getBitDepth"); + int expResult = BITDEPTH; + int result = instance.getBitDepth(); + assertEquals(expResult, result); + + instance.addImage(new int[HEIGHT][WIDTH]); + result = instance.getBitDepth(); + assertEquals(expResult, result); + } + + /** + * Test of getTitle method, of class DefaultImageS. + */ + @Test + public void testGetTitle() { + System.out.println("getTitle"); + String expResult = TITLE; + String result = instance.getTitle(); + assertEquals(expResult, result); + } + + /** + * Test of setTitle method, of class DefaultImageS. + */ + @Test + public void testSetTitle() { + System.out.println("setTitle"); + String title = "new title"; + instance.setTitle(title); + assertEquals(title, instance.getTitle()); + } + + /** + * Test of serializeToArray method, of class DefaultImageS. + */ + @Test + public void testSerializeToArray() throws ImageShapeException { + System.out.println("serializeToArray"); + + // This only verifies that the method runs, not that the implementation + // correctly serializes the data. + instance.addImage(new short [2][2]); + byte[] expResult = instance.serializeToArray(); + + } + + /** + * Test of serializeToBuffer method, of class DefaultImageS. + */ + @Test + public void testSerializeToBuffer() throws ImageShapeException { + System.out.println("serializeToBuffer"); + + // This isn't so much a test that the image is correctly serialized, + // but more of a check that SerializeToBuffer is working without + // exception. + instance.addImage(new short [2][2]); + ByteBuffer expResult = ByteBuffer.wrap(instance.serializeToArray()); + ByteBuffer result = instance.serializeToBuffer(); + assertEquals(expResult, result); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test + public void testAddImage_shortArrArr() throws ImageShapeException { + System.out.println("addImage"); + + // The current DefaultImageS implementation uses column-row order. + // I'd like to change this back to row-order in the future if possible. + short[][] image = {{1, 3}, {2, 4}}; + instance.addImage(image); + + short[] expResult = {1, 2, 3, 4}; + short[] result = instance.getPixelData(0); + assertArrayEquals(expResult, result); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test(expected = ImageShapeException.class) + public void testAddImage_shortArrArr_wrongSize() throws ImageShapeException { + System.out.println("addImage_wrongSize"); + + // The current DefaultImageS implementation uses column-row order. + // I'd like to change this back to row-order in the future if possible. + short[][] image = {{1, 3, 7}, {2, 4, 8}}; + instance.addImage(image); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test(expected = ImageShapeException.class) + public void testAddImage_intArrArr_wrongSize() throws ImageShapeException { + System.out.println("addImage_wrongSize"); + + // The current DefaultImageS implementation uses column-row order. + // I'd like to change this back to row-order in the future if possible. + int[][] image = {{1, 3, 7}, {2, 4, 8}}; + instance.addImage(image); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test(expected = ImageShapeException.class) + public void testAddImage_floatArrArr_wrongSize() throws ImageShapeException { + System.out.println("addImage_wrongSize"); + + // The current DefaultImageS implementation uses column-row order. + // I'd like to change this back to row-order in the future if possible. + float[][] image = {{1, 3, 7}, {2, 4, 8}}; + instance.addImage(image); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test + public void testAddImage_intArrArr() throws ImageShapeException { + System.out.println("addImage"); + + int[][] image = {{2, 6}, {4, 8}}; + instance.addImage(image); + + short[] expResult = {2, 4, 6, 8}; + short[] result = instance.getPixelData(0); + assertArrayEquals(expResult, result); + } + + /** + * Test of addImage method, of class DefaultImageS. + */ + @Test + public void testAddImage_floatArrArr() throws ImageShapeException { + System.out.println("addImage"); + + float[][] image = {{2, 7}, {3, 8}}; + instance.addImage(image); + + short[] expResult = {2, 3, 7, 8}; + short[] result = instance.getPixelData(0); + assertArrayEquals(expResult, result); + } + + /** + * Test of concatenate method, of class DefaultImageS. + */ + @Test + public void testConcatenate() throws ImageShapeException { + System.out.println("concatenate"); + instance.addImage(new short[][]{{1, 3}, {2, 4}}); + + ImageS newImage = new DefaultImageS(WIDTH, HEIGHT); + newImage.addImage(new short[][]{{2, 6}, {4, 8}}); + instance.concatenate(newImage); + + assertArrayEquals(new short[]{1, 2, 3, 4}, instance.getPixelData(0)); + assertArrayEquals(new short[]{2, 4, 6, 8}, instance.getPixelData(1)); + } + + /** + * Test of concatenate method, of class DefaultImageS. + */ + @Test(expected = ImageShapeException.class) + public void testConcatenate_wrongSize() throws ImageShapeException { + System.out.println("concatenate"); + instance.addImage(new short[][]{{1, 3}, {2, 4}}); + + ImageS newImage = new DefaultImageS(3, 2); + newImage.addImage(new short[][]{{1, 2, 6}, {1, 4, 8}}); + instance.concatenate(newImage); + } + + /** + * Test of getPixelData method, of class DefaultImageS. + */ + @Test + public void testGetPixelData() throws ImageShapeException { + System.out.println("getPixelData"); + + instance.addImage(new short[][]{{1, 3}, {2, 4}}); + + + short[] expResult = {1, 2, 3, 4}; + short[] result = instance.getPixelData(0); + assertArrayEquals(expResult, result); + } + + /** + * Test of getSlice method, of class DefaultImageS. + */ + @Test + public void testGetSlice() throws ImageShapeException { + System.out.println("getSlice"); + + instance.addImage( new short[][]{{1, 3}, {2, 4}} ); + instance.addImage( new short[][]{{2, 6}, {4, 8}} ); + + int expResult = 1; + instance.setSlice(1); + assertEquals(expResult, instance.getSlice()); + } + + /** + * Test of setSlice method, of class DefaultImageS. + */ + @Test + public void testSetSlice() throws ImageShapeException { + System.out.println("setSlice"); + testGetSlice(); + } + + /** + * Test of saveAsTiffStack method, of class DefaultImageS. + */ + @Test + public void testSaveAsTiffStack() throws IOException, ImageShapeException { + System.out.println("saveAsTiffStack"); + File file = tempDir.newFile("testFile.tif"); + + // This only tests that the method runs without error, not that it's + // saved correctly. + instance.addImage(new short[][]{{1, 3}, {2, 4}}); + instance.saveAsTiffStack(file); + } + + /** + * Test of saveAsTiffStack method, of class DefaultImageS. + */ + @Test(expected = IllegalArgumentException.class) + public void testSaveAsTiffStackEmpty() throws IOException { + System.out.println("saveAsTiffStackEmpty"); + File file = tempDir.newFile("testFile.txt"); + + // No data in the dataset yet + instance.saveAsTiffStack(file); + + } + + /** + * Test of getWidth method, of class DefaultImageS. + */ + @Test + public void testGetWidth() { + System.out.println("getWidth"); + int expResult = WIDTH; + int result = instance.getWidth(); + assertEquals(expResult, result); + } + + /** + * Test of getHeight method, of class DefaultImageS. + */ + @Test + public void testGetHeight() { + System.out.println("getHeight"); + int expResult = HEIGHT; + int result = instance.getHeight(); + assertEquals(expResult, result); + } + + /** + * Test of getSize method, of class DefaultImageS. + */ + @Test + public void testGetSize() throws ImageShapeException { + System.out.println("getSize"); + int expResult = 0; + int result = instance.getSize(); + assertEquals(expResult, result); + + instance.addImage( new short[][]{{1, 3}, {2, 4}} ); + expResult = 1; + result = instance.getSize(); + assertEquals(expResult, result); + } + +} diff --git a/thrift/RPCServer.thrift b/thrift/RPCServer.thrift index 848a4d8..e86b65b 100644 --- a/thrift/RPCServer.thrift +++ b/thrift/RPCServer.thrift @@ -20,6 +20,9 @@ namespace java ch.epfl.leb.sass.server namespace py remotesim +exception ImageGenerationException { +} + service RemoteSimulationService { /** @@ -30,7 +33,7 @@ service RemoteSimulationService { /** * Increments the simulation by one time step and returns an image. */ - binary getNextImage(), + binary getNextImage() throws(1: ImageGenerationException ex), /** * Changes the simulation'ss fluorescence activation laser power. diff --git a/thrift/compile.sh b/thrift/compile.sh new file mode 100755 index 0000000..3c746a9 --- /dev/null +++ b/thrift/compile.sh @@ -0,0 +1,2 @@ +thrift -r --gen java RPCServer.thrift + From 91a3dd1adf0d565f0ffe8426bb301411a654115c Mon Sep 17 00:00:00 2001 From: kmdouglass Date: Sun, 11 Feb 2018 14:09:14 +0100 Subject: [PATCH 3/3] Updated scripts. --- build.xml | 2 +- nbproject/build-impl.xml | 31 +++++++++---------- nbproject/genfiles.properties | 6 ++-- nbproject/project.properties | 1 + nbproject/project.xml | 3 +- scripts/example_Gibson_Lanni_PSF.bsh | 5 --- scripts/example_dSTORM.bsh | 7 ----- scripts/example_grid_2d_fluorophores.bsh | 6 +--- scripts/example_grid_3d_fluorophores.bsh | 7 ----- scripts/example_loggers.bsh | 10 +++--- scripts/example_random_2d_fluorophores.bsh | 9 ------ scripts/example_random_3d_fluorophores.bsh | 6 ---- scripts/example_run_generator_moving.bsh | 5 --- .../utils/images/internal/DefaultImageS.java | 4 +++ 14 files changed, 31 insertions(+), 71 deletions(-) diff --git a/build.xml b/build.xml index a7812f6..3503245 100644 --- a/build.xml +++ b/build.xml @@ -3,7 +3,7 @@ Builds, tests, and runs the project SASS. - + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml index db14df7..7bd7aa4 100644 --- a/nbproject/build-impl.xml +++ b/nbproject/build-impl.xml @@ -19,7 +19,7 @@ is divided into following sections: - cleanup --> - + @@ -46,8 +46,8 @@ is divided into following sections: - - + + @@ -76,7 +76,7 @@ is divided into following sections: - + @@ -156,7 +156,6 @@ is divided into following sections: - @@ -450,7 +449,7 @@ is divided into following sections: - + @@ -601,7 +600,7 @@ is divided into following sections: - + @@ -841,7 +840,7 @@ is divided into following sections: - + @@ -853,7 +852,7 @@ is divided into following sections: - + @@ -893,7 +892,7 @@ is divided into following sections: - + @@ -976,15 +975,15 @@ is divided into following sections: - + - + - + @@ -992,7 +991,7 @@ is divided into following sections: - + @@ -1187,7 +1186,7 @@ is divided into following sections: Must select one file in the IDE or set run.class - + Must select one file in the IDE or set applet.url @@ -1378,7 +1377,7 @@ is divided into following sections: - + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties index fce66b5..7bae94b 100644 --- a/nbproject/genfiles.properties +++ b/nbproject/genfiles.properties @@ -3,7 +3,7 @@ build.xml.script.CRC32=1c9a5f06 build.xml.stylesheet.CRC32=8064a381@1.75.2.48 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=e8261fd9 -nbproject/build-impl.xml.script.CRC32=a5d77285 -nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48 +nbproject/build-impl.xml.data.CRC32=dd76ab7f +nbproject/build-impl.xml.script.CRC32=ca3d274f +nbproject/build-impl.xml.stylesheet.CRC32=05530350@1.79.1.48 diff --git a/nbproject/project.properties b/nbproject/project.properties index 4137bb5..9ce1169 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -92,6 +92,7 @@ javadoc.nonavbar=false javadoc.notree=false javadoc.private=false javadoc.reference.gson-2.8.2.jar=src/lib/javadocs/gson-2.8.2-javadoc.jar +javadoc.reference.ij-1.50e.jar=/home/kmdouglass/apps/Fiji.app/javadocs/ij javadoc.reference.jfreechart-1.0.19.jar=/home/douglass/src/jfreechart-1.0.19/javadoc javadoc.reference.libthrift-0.11.0.jar=src/lib/libthrift-0.11.0-javadoc.jar javadoc.splitindex=true diff --git a/nbproject/project.xml b/nbproject/project.xml index e650440..c3e839d 100644 --- a/nbproject/project.xml +++ b/nbproject/project.xml @@ -3,7 +3,7 @@ org.netbeans.modules.java.j2seproject - SASS-dev + SASS @@ -13,4 +13,3 @@ - diff --git a/scripts/example_Gibson_Lanni_PSF.bsh b/scripts/example_Gibson_Lanni_PSF.bsh index ae8a196..524a479 100644 --- a/scripts/example_Gibson_Lanni_PSF.bsh +++ b/scripts/example_Gibson_Lanni_PSF.bsh @@ -152,12 +152,7 @@ for (i=0;i<1000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); -//System.exit(0); // uncomment if you want termination immediately diff --git a/scripts/example_dSTORM.bsh b/scripts/example_dSTORM.bsh index e7df0d5..6210ab3 100644 --- a/scripts/example_dSTORM.bsh +++ b/scripts/example_dSTORM.bsh @@ -115,12 +115,5 @@ for (i=0;i<100;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); - -//System.exit(0); // uncomment if you want termination immediately - diff --git a/scripts/example_grid_2d_fluorophores.bsh b/scripts/example_grid_2d_fluorophores.bsh index cd71ed0..86b9f41 100644 --- a/scripts/example_grid_2d_fluorophores.bsh +++ b/scripts/example_grid_2d_fluorophores.bsh @@ -17,6 +17,7 @@ import ch.epfl.leb.sass.models.components.*; import ch.epfl.leb.sass.models.psfs.internal.Gaussian2D; import ch.epfl.leb.sass.models.fluorophores.internal.commands.GenerateFluorophoresGrid2D; import ch.epfl.leb.sass.utils.RNG; +import ch.epfl.leb.sass.utils.images.ImageS; // The seed determines the outputs of the random number generator. RNG.setSeed(42); @@ -114,9 +115,4 @@ for (i=0;i<10000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); -//System.exit(0); // uncomment if you want termination immediately diff --git a/scripts/example_grid_3d_fluorophores.bsh b/scripts/example_grid_3d_fluorophores.bsh index 6b0c1fb..82c45c0 100644 --- a/scripts/example_grid_3d_fluorophores.bsh +++ b/scripts/example_grid_3d_fluorophores.bsh @@ -128,10 +128,3 @@ for (i=0;i<10000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); - -//System.exit(0); // uncomment if you want termination immediately - diff --git a/scripts/example_loggers.bsh b/scripts/example_loggers.bsh index 0e380d4..6efdfbb 100644 --- a/scripts/example_loggers.bsh +++ b/scripts/example_loggers.bsh @@ -122,13 +122,13 @@ for (i=0;i<10000;i++) { } // Save the stack -generator.saveStack(new File("generated_stack.tif")); +//generator.saveStack(new File("generated_stack.tif")); // Save the fluorophore positions. -positionLogger.setFilename("fluorophore_positions.csv"); -positionLogger.saveLogFile(); +//positionLogger.setFilename("fluorophore_positions.csv"); +//positionLogger.saveLogFile(); // Save the state transitions -stateLogger.setFilename("state_transitions.csv"); -stateLogger.saveLogFile(); +//stateLogger.setFilename("state_transitions.csv"); +//stateLogger.saveLogFile(); diff --git a/scripts/example_random_2d_fluorophores.bsh b/scripts/example_random_2d_fluorophores.bsh index cc0aebc..bca979f 100644 --- a/scripts/example_random_2d_fluorophores.bsh +++ b/scripts/example_random_2d_fluorophores.bsh @@ -116,12 +116,3 @@ for (i=0;i<10000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); - -//System.exit(0); // uncomment if you want termination immediately - - - diff --git a/scripts/example_random_3d_fluorophores.bsh b/scripts/example_random_3d_fluorophores.bsh index 0cd788b..5d887c4 100644 --- a/scripts/example_random_3d_fluorophores.bsh +++ b/scripts/example_random_3d_fluorophores.bsh @@ -127,12 +127,6 @@ for (i=0;i<10000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); - -//System.exit(0); // uncomment if you want termination immediately diff --git a/scripts/example_run_generator_moving.bsh b/scripts/example_run_generator_moving.bsh index 658b6a4..f38ec6a 100644 --- a/scripts/example_run_generator_moving.bsh +++ b/scripts/example_run_generator_moving.bsh @@ -83,12 +83,7 @@ for (i=0;i<10000;i++) { // save and show; uncomment these lines to save and display stack //generator.saveStack(new File("generated_stack.tif")); -//import ij.ImagePlus; -//ImagePlus ip = new ImagePlus("Simulation output", generator.getStack()); -//ip.show(); -//ip.updateAndRepaintWindow(); -//System.exit(0); // uncomment if you want termination immediately diff --git a/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java index 61d962d..56b4469 100644 --- a/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java +++ b/src/ch/epfl/leb/sass/utils/images/internal/DefaultImageS.java @@ -229,6 +229,10 @@ public void view() { */ @Override public void updateView() { + if (imp == null) { + // The ImagePlus was not created in the constructor. + imp = new ImagePlus(title, images); + } imp.updateAndRepaintWindow(); }