ch.epfl.leb.sass.models.Microscope.java Source code

Java tutorial

Introduction

Here is the source code for ch.epfl.leb.sass.models.Microscope.java

Source

/*
 * Copyright (C) 2017 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.models;

import cern.jet.random.Gamma;
import cern.jet.random.Normal;
import cern.jet.random.Poisson;

import ch.epfl.leb.sass.utils.RNG;
import ch.epfl.leb.sass.utils.images.ImageS;
import ch.epfl.leb.sass.utils.images.internal.DefaultImageS;
import ch.epfl.leb.sass.models.psfs.PSFBuilder;
import ch.epfl.leb.sass.models.components.Laser;
import ch.epfl.leb.sass.models.components.Stage;
import ch.epfl.leb.sass.models.components.Camera;
import ch.epfl.leb.sass.models.components.Objective;
import ch.epfl.leb.sass.models.components.internal.DefaultStage;
import ch.epfl.leb.sass.models.components.internal.DefaultLaser;
import ch.epfl.leb.sass.models.components.internal.DefaultCamera;
import ch.epfl.leb.sass.models.components.internal.DefaultObjective;
import ch.epfl.leb.sass.models.obstructors.Obstructor;
import ch.epfl.leb.sass.models.fluorophores.Fluorophore;
import ch.epfl.leb.sass.models.illuminations.Illumination;
import ch.epfl.leb.sass.models.illuminations.IlluminationBuilder;
import ch.epfl.leb.sass.models.backgrounds.BackgroundCommand;
import ch.epfl.leb.sass.models.backgrounds.BackgroundCommandBuilder;
import ch.epfl.leb.sass.models.obstructors.internal.commands.ObstructorCommand;
import ch.epfl.leb.sass.models.obstructors.internal.commands.ObstructorCommandBuilder;
import ch.epfl.leb.sass.models.fluorophores.commands.FluorophoreCommand;
import ch.epfl.leb.sass.models.photophysics.FluorophoreDynamics;
import ch.epfl.leb.sass.models.fluorophores.commands.FluorophoreCommandBuilder;
import ch.epfl.leb.sass.models.photophysics.FluorophoreDynamicsBuilder;

import java.util.List;
import java.util.Arrays;
import java.io.Serializable;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;

/**
 * Integrates all the components into one microscope.
 */
public class Microscope implements Serializable {

    // Assigned in the constructor
    private final Camera camera;
    private final Laser laser;
    private final Objective objective;
    private final Stage stage;
    private final FluorophoreDynamics fluorDynamics;
    private final List<Fluorophore> fluorophores;
    private final List<Obstructor> obstructors;
    private final BackgroundCommand background;
    private final Illumination illumination;

    // Random number generators
    private final Poisson poisson = RNG.getPoissonGenerator();
    private final Gamma gamma = RNG.getGammaGenerator();
    private final Normal gaussian = RNG.getGaussianGenerator();

    /** 
     * Initializes the microscope for simulations.
     * 
     * @param cameraBuilder
     * @param laserBuilder
     * @param objectiveBuilder
     * @param psfBuilder
     * @param stageBuilder
     * @param fluorBuilder Positions fluorophore's within the field of view.
     * @param fluorDynamicsBuilder
     * @param obstructorBuilder Creates the obstructors, e.g. fiducials.
     * @param backgroundBuilder Creates the background signal on the image.
     * @param illuminationBuilder Creates the illumination profile.
     */
    public Microscope(DefaultCamera.Builder cameraBuilder, DefaultLaser.Builder laserBuilder,
            DefaultObjective.Builder objectiveBuilder, PSFBuilder psfBuilder, DefaultStage.Builder stageBuilder,
            FluorophoreCommandBuilder fluorBuilder, FluorophoreDynamicsBuilder fluorDynamicsBuilder,
            ObstructorCommandBuilder obstructorBuilder, BackgroundCommandBuilder backgroundBuilder,
            IlluminationBuilder illuminationBuilder) {

        // Build objects that do not require further setup
        this.camera = cameraBuilder.build();
        this.laser = laserBuilder.build();
        this.objective = objectiveBuilder.build();
        this.stage = stageBuilder.build();
        this.fluorDynamics = fluorDynamicsBuilder.build();

        // Setup the illumination
        illuminationBuilder.power(this.laser.getPower());
        illuminationBuilder.wavelength(this.laser.getWavelength());
        this.illumination = illuminationBuilder.build();

        // Extract the wavelength from the fluorophores
        double wavelength;
        wavelength = this.fluorDynamics.getWavelength();

        // Set the stage displacement for axially-dependent PSFs, the NA, and
        // the Gaussian FWHM for those PSFs that use a Gaussian approximation
        double fwhm = objective.airyFWHM(wavelength) / camera.getPixelSize();
        psfBuilder.stageDisplacement(stage.getZ()).NA(objective.getNA()).FWHM(fwhm).wavelength(wavelength)
                .resLateral(camera.getPixelSize() / objective.getMag());
        // Create the set of fluorophores.
        fluorBuilder.camera(camera).psfBuilder(psfBuilder).fluorDynamics(fluorDynamics).illumination(illumination);
        FluorophoreCommand fluorCommand = fluorBuilder.build();
        this.fluorophores = fluorCommand.generateFluorophores();

        // Build the obstructors
        obstructorBuilder.camera(camera).stage(stage).psfBuilder(psfBuilder);
        ObstructorCommand obstrCommand = obstructorBuilder.build();
        this.obstructors = obstrCommand.generateObstructors();

        // Set the size of the background image and build it
        backgroundBuilder.nX(camera.getNX()).nY(camera.getNY());
        this.background = backgroundBuilder.build();

        // Determine the lifetimes for each fluorophore's current state
        for (Fluorophore f : fluorophores) {
            f.recalculateLifetimes(laser.getPower());
        }

        // Connects the laser to the illumination profile for messaging.
        this.laser.addListener(this.illumination);
    }

    /**
     * Adds the background signal to the image.
     * @param image The image to which a background will be added.
     */
    private void addBackground(float[][] image) {
        float[][] backgroundSignal = this.background.generateBackground();

        for (int row = 0; row < image.length; row++) {
            for (int col = 0; col < image[row].length; col++) {
                image[row][col] += backgroundSignal[row][col];
            }
        }
    }

    /**
     * Simulates noise sources.
     * @param image image to be noised up.
     */
    private void addNoise(float[][] image) {

        // Poisson noise
        addPoissonNoise(image);

        for (int row = 0; row < image.length; row++) {
            // Multiplication noise from the EM gain register
            for (int col = 0; col < image[row].length; col++) {
                if (this.camera.getEmGain() != 0) {
                    // lambda parameter of nextDouble() is inverse of EM_gain.
                    image[row][col] = (float) gamma.nextDouble(image[row][col] + 0.01f,
                            1.0 / ((double) this.camera.getEmGain()));
                }

                // Dark noises (readout and thermal)
                image[row][col] += this.camera.getReadoutNoise() * gaussian.nextDouble()
                        + this.camera.getThermalNoise() * gaussian.nextDouble();
            }
        }
    }

    /**
     * Adds Poisson noise to the image.
     * @param image input image
     */
    private void addPoissonNoise(float[][] image) {
        for (int x = 0; x < image.length; x++) {
            for (int y = 0; y < image[0].length; y++) {
                image[x][y] = (float) poisson.nextInt(image[x][y]);
            }
        }
    }

    /**
     * Returns references to the fluorophores in the sample.
     * 
     * @return The sample's Fluorophore objects.
     */
    public List<Fluorophore> getFluorophores() {
        return this.fluorophores;
    }

    /**
     * Returns the size of the field-of-view in object space units.
     * 
     * @return size of current FOV in object space units.
     */
    public double getFovSize() {
        return (getObjectSpacePixelSize() * getObjectSpacePixelSize()) * camera.getNX() * camera.getNY();
    }

    /**
     * The size of a pixel after division by the objective magnification.
     * 
     * @return Length of one pixel side in object space units
     */
    public double getObjectSpacePixelSize() {
        return this.camera.getPixelSize() / this.objective.getMag();
    }

    /**
     * Modifies the laser power to desired value.
     * 
     * @param laserPower new laser power
     */
    public void setLaserPower(double laserPower) {
        laser.setPower(laserPower);
    }

    /**
     * Return current power of the laser.
     * @return laser power
     */
    public double getLaserPower() {
        return laser.getPower();
    }

    /**
     * Returns the number of currently active emitters.
     * @return number of shining emitters
     */
    public double getOnEmitterCount() {
        int count = 0;
        for (Fluorophore e : fluorophores) {
            if (e.isOn()) {
                count++;
            }
        }
        return count;
    }

    /**
     * Return the number of camera pixels in x and y.
     * @return 2D array with number of pixels in x and y.
     */
    public int[] getResolution() {
        int[] result = new int[2];
        result[0] = camera.getNX();
        result[1] = camera.getNY();
        return result;
    }

    /**
     * Generates a new frame and moves the device state forward.
     * 
     * First the obstructors are drawn on the frame, then the fluorophores,
     * and finally noise.
     * 
     * @return A simulated image of the next camera frame.
     */
    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);

        // Add obstructions
        if (obstructors != null) {
            for (Obstructor o : obstructors) {
                o.applyTo(pixels);
            }
        }
        // Add fluorophores
        // The applyTo method also handles fluorophore state changes by calling
        // the simulateBrightness() method of an emitter.
        for (Fluorophore f : fluorophores) {
            f.applyTo(pixels);
        }

        addBackground(pixels);
        addNoise(pixels);

        // Convert signal to ADU and add baseline.
        for (int x = 0; x < pixels.length; x++) {
            for (int y = 0; y < pixels[0].length; y++) {
                pixels[x][y] *= this.camera.getAduPerElectron();
                pixels[x][y] += this.camera.getBaseline();
            }
        }

        // Convert to image
        return new DefaultImageS(pixels);
    }

    /**
     * Returns information about the camera.
     * 
     * @return A JsonElement containing information about the camera.
     */
    public JsonElement toJsonCamera() {
        return this.camera.toJson();
    }

    /**
     * Returns information about the laser.
     * 
     * @return A JsonElement containing information about the laser.
     */
    public JsonElement toJsonLaser() {
        return this.laser.toJson();
    }

    /**
     * Returns information about the sample fluorophores.
     * 
     * @return A JsonObject containing information about the fluorophores.
     */
    public JsonElement toJsonFluorescence() {
        JsonArray jsonArray = new JsonArray();
        for (Fluorophore f : fluorophores) {
            jsonArray.add(f.toJson());
        }

        return jsonArray;
    }

    /**
     * Returns information about the objective.
     * 
     * @return A JsonElement containing information about the stage.
     */
    public JsonElement toJsonObjective() {
        return this.objective.toJson();
    }

    /**
     * Returns information about the stage.
     * 
     * @return A JsonElement containing information about the stage.
     */
    public JsonElement toJsonStage() {
        return this.stage.toJson();
    }
}