Skip to content
Snippets Groups Projects
WellPlateBatch.java 7.85 KiB
Newer Older
package plugins.adufour.hcs.blocks;

import java.awt.Point;
import java.io.File;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import danyfel80.wells.data.IField;
import danyfel80.wells.data.IPlate;
import danyfel80.wells.data.IWell;
import icy.gui.frame.progress.CancelableProgressFrame;
import icy.sequence.Sequence;
import icy.system.IcyHandledException;
import icy.system.SystemUtil;
import icy.util.StringUtil;
import plugins.adufour.blocks.lang.Batch;
import plugins.adufour.blocks.lang.FileBatch;
import plugins.adufour.blocks.util.VarList;
import plugins.adufour.hcs.WellPlateImporter;
import plugins.adufour.hcs.io.AbstractWellPlateReader;
import plugins.adufour.vars.gui.FileMode;
import plugins.adufour.vars.gui.model.FileTypeModel;
import plugins.adufour.vars.lang.Var;
import plugins.adufour.vars.lang.VarFile;
import plugins.adufour.vars.lang.VarInteger;
import plugins.adufour.vars.lang.VarSequence;
import plugins.adufour.vars.lang.VarString;
import plugins.adufour.vars.util.VarListener;
 * A well plate batch is a work-flow that will process every image of every well
 * in a given well plate (or a selection thereof). Supported plate formats are
 * the same as those supported by the {@link OldWellPlateImporter} plug-in.
 * 
 * @author Daniel Felipe Gonzalez Obando
public class WellPlateBatch extends Batch
{
    private VarString wellFilter;

    private VarString plateID;

    private VarString wellID;

    private VarInteger fieldID;

    @Override
    public void declareInput(VarList inputMap)
    {
        super.declareInput(inputMap);

        wellFilter = new VarString("Well filter", "");
        inputMap.add(wellFilter.getName(), wellFilter);
    }

    @Override
    public void declareOutput(VarList outputMap)
    {
        super.declareOutput(outputMap);

        plateID = new VarString("Plate", "--");
        plateID.setEnabled(false);

        wellID = new VarString("Well", "--");
        wellID.setEnabled(false);

        fieldID = new VarInteger("Field", 0);
        fieldID.setEnabled(false);

        outputMap.add(plateID.getName(), plateID);
        outputMap.add(wellID.getName(), wellID);
        outputMap.add(fieldID.getName(), fieldID);
    }

    @Override
    public void declareLoopVariables(List<Var<?>> loopVariables)
    {
        super.declareLoopVariables(loopVariables);

        loopVariables.add(wellFilter);
        loopVariables.add(plateID);
        loopVariables.add(wellID);
        loopVariables.add(fieldID);
    }

    /**
     * The folder containing the well plate. Although we could have extended
     * {@link FileBatch}, we choose to extend just {@link Batch} since we don't
     * need extension filters or recursive sub-folder analysis
     */
    private VarFile plateFolder;
    /**
     * Indicates whether the current well plate is loaded (avoids reloading it
     * every time the protocol is run)
     */
    private boolean wellPlateLoaded = false;

    @Override
    public Var<?> getBatchSource()
    {
        if (plateFolder == null)
        {
            // Make sure the well plate is reloaded only when this parameter changes
            plateFolder = new VarFile("Plate folder", null, new VarListener<File>()
            {
                @Override
                public void valueChanged(Var<File> source, File oldValue, File newValue)
                {
                    wellPlateLoaded = false;
                }

                @Override
                public void referenceChanged(Var<File> source, Var<? extends File> oldReference,
                        Var<? extends File> newReference)
                {
                    wellPlateLoaded = false;
                }
            });
            plateFolder.setDefaultEditorModel(new FileTypeModel(null, FileMode.FOLDERS, null, false));
        }
        return plateFolder;
    }

    private VarSequence element;

    @Override
    public VarSequence getBatchElement()
    {
        if (element == null)
        {
            // WARNING: do *not* change the name of this variable
            // why? see declareInput() and VarList.add()
            element = new VarSequence("Sequence", null);
        }
        return element;
    }

    String[] wellFilterValues;
    AbstractWellPlateReader reader;
    IPlate currentWellPlate;
    Iterator<? extends IWell> wellIterator;
    Iterator<? extends IField> fieldIterator;

    @Override
    public void initializeLoop()
    {
        wellFilterValues = wellFilter.getValue(true).split("\\s+");
        if (!wellPlateLoaded) // Fetch a valid reader and load the well plate
        {
            File folder = plateFolder.getValue(true);

            reader = WellPlateImporter.getReaderFor(folder);
            if (reader == null)
                throw new IcyHandledException("No plate reader found for folder " + folder.getPath());

            @SuppressWarnings("unchecked")
            Optional<CancelableProgressFrame> progress = (Optional<CancelableProgressFrame>) (SystemUtil.isHeadLess()
                    ? Optional.empty()
                    : Optional.of(new CancelableProgressFrame("Loading well plate...")));

            try
            {
                Future<? extends IPlate> plateFuture = reader.loadPlateFromFolder(folder,
                        (p, m) -> progress.ifPresent(pr -> {
                            pr.setPosition(p);
                            pr.setMessage(m);
                        }));
                currentWellPlate = plateFuture.get();
                plateID.setValue(currentWellPlate.getId());
                wellPlateLoaded = true;
            }
            catch (ExecutionException | InterruptedException e)
            {
                throw new RuntimeException(e);
            }
            finally
            {
                progress.ifPresent(pr -> pr.close());
            }
        }

        // Prepare the iterator'
        wellIterator = currentWellPlate.getWells().values().stream().filter(this::shouldLoadWell).iterator();
        fieldIterator = Collections.<IField> emptyList().iterator();
    }

    private boolean shouldLoadWell(IWell well)
    {
        if (wellFilterValues.length != 0)
        {
            Point wellPos = well.getPositionInPlate();
            char col = ((char) ('A' + wellPos.x - 1));
            String row = StringUtil.toString(wellPos.y, 2);
            String wellAlphanumeric = "" + col + row;
            for (String filter : wellFilterValues)
            {
                if (wellAlphanumeric.contains(filter))
                    return true;
            }
            return false;
        }
        return true;
    }

    private IWell currentWell;

    @Override
    public void beforeIteration()
    {
        try
        {
            Sequence sequence = new Sequence();
            if (!fieldIterator.hasNext())
            {
                if (wellIterator.hasNext())
                {
                    currentWell = wellIterator.next();
                    fieldIterator = currentWell.getFields().values().iterator();
                }
            }
            if (fieldIterator.hasNext())
            {
                IField currentField = fieldIterator.next();
                Point pos = currentWell.getPositionInPlate();
                wellID.setValue("" + ((char) ('A' + pos.x - 1)) + StringUtil.toString(pos.y, 2));
                fieldID.setValue((int) currentField.getId());
                reader.loadField(currentWellPlate, currentWell, currentField, sequence, null);
                element.setValue(sequence);
            }
            Thread.yield();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public boolean isStopConditionReached()
    {
        return !fieldIterator.hasNext() && !wellIterator.hasNext();
    }