ch.epfl.leb.sass.simulator.internal.DefaultSimulator.java Source code

Java tutorial

Introduction

Here is the source code for ch.epfl.leb.sass.simulator.internal.DefaultSimulator.java

Source

/* 
 * Copyright (C) 2017-2018 Laboratory of Experimental Biophysics
 * Ecole Polytechnique Federale 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 <http://www.gnu.org/licenses/>.
 */
package ch.epfl.leb.sass.simulator.internal;

import ch.epfl.leb.sass.utils.DeepCopy;
import ch.epfl.leb.sass.logging.Message;
import ch.epfl.leb.sass.logging.Listener;
import ch.epfl.leb.sass.models.Microscope;
import ch.epfl.leb.sass.models.fluorophores.Fluorophore;
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 com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonElement;
import com.google.gson.GsonBuilder;

import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The basic simulation engine from which others may be derived.
 * 
 * @author Marcel Stefko
 * @author Kyle M. Douglass
 */
public class DefaultSimulator extends AbstractSimulator {

    public final static Logger LOGGER = Logger.getLogger(DefaultSimulator.class.getName());

    /**
     * The STATE_LISTENER records changes in the fluorophore's state.
     */
    private final DefaultSimulator.StateListener STATE_LISTENER;

    /**
     * Scaling factor for the density.
     * 
     * Multiplying the estimated density by a factor of 100 means that the
     * output returns spots per 10 um x 10 um = 100 um^2 area.
     */
    private final double SCALEFACTOR = 100;

    // Member names for JSON serialization
    private final String CAMERA_MEMBER_NAME = "Camera";
    private final String FLUOR_MEMBER_NAME = "Fluorophores";
    private final String LASER_MEMBER_NAME = "Laser";
    private final String OBJECTIVE_MEMBER_NAME = "Objective";
    private final String STAGE_MEMBER_NAME = "Stage";

    private Microscope microscope;
    private ArrayList<Double> emitterHistory;

    /**
     * Initialize the generator.
     * @param microscope
     */
    public DefaultSimulator(Microscope microscope) {
        super();
        this.microscope = microscope;

        int[] res = this.microscope.getResolution();
        stack = new DefaultImageS(res[0], res[1]);

        emitterHistory = new ArrayList<>();
        emitterHistory.add(0.0);

        // Create the fluorescence state STATE_LISTENER and attach it.
        STATE_LISTENER = this.new StateListener();
        List<Fluorophore> fluorophores = this.microscope.getFluorophores();
        for (Fluorophore f : fluorophores) {
            f.addListener(STATE_LISTENER);
        }

    }

    /**
     * Returns the JSON member name assigned to the camera.
     * 
     * @return The JSON member name for the Camera field.
     */
    @Override
    public String getCameraJsonName() {
        return CAMERA_MEMBER_NAME;
    }

    @Override
    public double getControlSignal() {
        return microscope.getLaserPower();
    }

    @Override
    public HashMap<String, Double> getCustomParameters() {
        parameters.put("real_laser_power", microscope.getLaserPower());
        return parameters;
    }

    /**
     * 
     * @return The size of the FOV in square object-space units.
     */
    @Override
    public double getFOVSize() {
        return microscope.getFovSize();
    }

    /**
     * Returns the name of the JSON key for the fluorescence info.
     * 
     * @return The name of the key indicating the fluorescence information.
     */
    public String getFluorescenceJsonName() {
        return FLUOR_MEMBER_NAME;
    }

    /**
     * Returns the name of the JSON key for the laser info.
     * 
     * @return The name of the key indicating the laser information.
     */
    public String getLaserJsonName() {
        return LASER_MEMBER_NAME;
    }

    /**
     * Returns messages about changes in the simulation state.
     * 
     * Unlike {@link #getSimulationState() getSimulationState()}, which returns
     * information about the *current* state of the simulation, this method
     * returns the messages from individual components that contain information
     * about changes in their state that have occurred since the last time this
     * method was called.
     * 
     * @return A list containing the state change messages.
     */
    @Override
    public List<Message> getMessages() {
        List<Message> messages = this.STATE_LISTENER.dumpMessageCache();
        if (messages == null) {
            return new ArrayList<Message>();
        } else {
            return messages;
        }
    }

    /**
     * Returns a copy of the Microscope that is controlled by this simulation.
     * 
     * The copy that is returned is a deep copy of the
     * {@link ch.epfl.leb.sass.models.Microscope Microscope} that the simulation
     * was initialized with.
     * 
     * @return A copy of the Microscope object controlled by this simulation.
     */
    @Override
    public Microscope getMicroscope() {
        return microscope;
    }

    /**
     * Generates a new image and adds it to the internal stack.
     * @return newly generated image
     */
    @Override
    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());
        ImageS pixels = microscope.simulateFrame();
        stack.concatenate(pixels);

        return pixels;
    }

    /**
     * Returns the name of the JSON key for the objective state info.
     * 
     * @return The name of the key indicating the objective information.
     * @see #toJsonState()
     */
    @Override
    public String getObjectiveJsonName() {
        return OBJECTIVE_MEMBER_NAME;
    }

    /**
     *
     * @return Length of one pixel side in object-space units.
     */
    @Override
    public double getObjectSpacePixelSize() {
        return microscope.getObjectSpacePixelSize();
    }

    @Override
    public String getShortTrueSignalDescription() {
        String descr = "counts/" + String.valueOf((int) SCALEFACTOR) + " m^2";
        return descr;
    }

    /**
     * Returns the name of the JSON key for the stage info.
     * 
     * @return The name of the key indicating the stage information.
     */
    public String getStageJsonName() {
        return STAGE_MEMBER_NAME;
    }

    /**
     * Returns this instance's StateListener.
     * 
     * This method is primarily for testing purposes and is not exposed in the
     * Simulator interface.
     * 
     * @return A reference to this instance's StateListener.
     */
    public DefaultSimulator.StateListener getStateListener() {
        return STATE_LISTENER;
    }

    @Override
    public double getTrueSignal(int image_no) {
        return emitterHistory.get(image_no) / microscope.getFovSize() * SCALEFACTOR;
    }

    /**
     * Advance the simulation by one time step (i.e. one frame).
     * 
     * Simulates a frame but does not create an image.
     */
    @Override
    public void incrementTimeStep() {
        emitterHistory.add(microscope.getOnEmitterCount());
        microscope.simulateFrame();
    }

    /**
     * Saves the messages in the cache to a select file.
     * 
     * @param file The file to save to.
     */
    public void saveMessages(File file) {
        JsonElement json = toJsonMessages();
        try (Writer writer = new FileWriter(file)) {
            Gson gson = new GsonBuilder().create();
            gson.toJson(json, writer);
        } catch (IOException ex) {
            String err = "Could not write simulation Messages to the file.";
            LOGGER.log(Level.WARNING, err);
            ex.printStackTrace();
        } catch (Exception ex) {
            String err = "An unknown error occurred while saving messages to " + "the file.";
            LOGGER.log(Level.WARNING, err);
            ex.printStackTrace();
        }
    }

    /**
     * Saves the current state of the simulation.
     * 
     * @param file The file to save to.
     */
    public void saveState(File file) {
        JsonElement json = toJsonState();
        try (Writer writer = new FileWriter(file)) {
            Gson gson = new GsonBuilder().create();
            gson.toJson(json, writer);
        } catch (IOException ex) {
            String err = "Could not write simulation state to the file.";
            LOGGER.log(Level.WARNING, err);
            ex.printStackTrace();
        } catch (Exception ex) {
            String err = "An unknown error occurred while saving the " + "simulation state to the file.";
            LOGGER.log(Level.WARNING, err);
            ex.printStackTrace();
        }
    }

    @Override
    public void setControlSignal(double value) {
        parameters.put("target_laser_power", value);
        microscope.setLaserPower(value);
    }

    @Override
    public void setCustomParameters(HashMap<String, Double> map) {
        parameters = map;
    }

    /**
     * Returns messages about changes in the simulation state as a JSON object.
     * 
     * Unlike {@link #toJsonState() toJsonState()}, which returns
     * information about the *current* state of the simulation, this method
     * returns the messages from individual simulation components that contain
     * information about changes in their state that have occurred since the
     * last time this method was called.
     * 
     * @return A JSON object containing the simulation messages.
     */
    @Override
    public JsonElement toJsonMessages() {
        JsonArray json = new JsonArray();
        for (Message msg : this.getMessages()) {
            json.add(msg.toJson().getAsJsonObject());
        }

        Gson gson = new Gson();
        return gson.fromJson(json, JsonArray.class);
    }

    /**
     * Returns information on the simulation's current state as a JSON object.
     * 
     * Unlike {@link #toJsonMessages() toJsonMessages()}, which returns
     * information about previous changes in the simulation's state, this method
     * reports on the current state of the simulation.
     * 
     * @return A JSON object containing information on the simulation state.
     */
    @Override
    public JsonElement toJsonState() {
        JsonObject json = new JsonObject();
        json.add(CAMERA_MEMBER_NAME, this.microscope.toJsonCamera());
        json.add(FLUOR_MEMBER_NAME, this.microscope.toJsonFluorescence());
        json.add(LASER_MEMBER_NAME, this.microscope.toJsonLaser());
        json.add(OBJECTIVE_MEMBER_NAME, this.microscope.toJsonObjective());
        json.add(STAGE_MEMBER_NAME, this.microscope.toJsonStage());
        return json;
    }

    /**
     * The StateListener listens for changes in the simulation's state.
     * 
     * These changes can occur at any time on a continuous interval between
     * the simulation time steps.
     */
    class StateListener implements Listener {

        /**
         * A cache containing the state transition messages.
         */
        ArrayList<Message> transitions = new ArrayList();

        /**
         * This method is called by an Observable when its state has changed.
         * 
         * @param data The data object that is passed from the Observable.
         */
        @Override
        public void update(Object data) {
            if (data == null) {
                // No data reported by the Observable.
                return;
            }

            try {
                Message msg = (Message) data;
                transitions.add(msg);
            } catch (Exception ex) {
                String err = "Could not coerce the Listener's message into a " + "known message type.";
                LOGGER.log(Level.WARNING, err);
                LOGGER.log(Level.WARNING, ex.getMessage());
            }
        }

        /**
         * Dumps the contents of the cache to a JSON string.
         * 
         * Calling this method will irreversibly clear the cache. This method
         * will return null if the cache is empty.
         * 
         * @return The contents of the cache as JSON string or null.
         */
        public List<Message> dumpMessageCache() {
            if (transitions.isEmpty()) {
                return null;
            }

            // Copy the messages before clearing the cache.
            ArrayList<Message> messages = new ArrayList<>();
            for (Message msg : transitions) {
                messages.add((Message) DeepCopy.deepCopy(msg));
            }

            transitions.clear();
            return messages;
        }
    }
}