net.sf.maltcms.chromaui.project.spi.descriptors.CachingChromatogram2D.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.maltcms.chromaui.project.spi.descriptors.CachingChromatogram2D.java

Source

/* 
 * Maui, Maltcms User Interface. 
 * Copyright (C) 2008-2014, The authors of Maui. All rights reserved.
 *
 * Project website: http://maltcms.sf.net
 *
 * Maui may be used under the terms of either the
 *
 * GNU Lesser General Public License (LGPL)
 * http://www.gnu.org/licenses/lgpl.html
 *
 * or the
 *
 * Eclipse Public License (EPL)
 * http://www.eclipse.org/org/documents/epl-v10.php
 *
 * As a user/recipient of Maui, you may choose which license to receive the code 
 * under. Certain files or entire directories may not be covered by this 
 * dual license, but are subject to licenses compatible to both LGPL and EPL.
 * License exceptions are explicitly declared in all relevant files or in a 
 * LICENSE file in the relevant directories.
 *
 * Maui 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. Please consult the relevant license documentation
 * for details.
 */
package net.sf.maltcms.chromaui.project.spi.descriptors;

import cross.Factory;
import cross.annotations.Configurable;
import cross.cache.ICacheDelegate;
import cross.cache.ICacheElementProvider;
import cross.datastructures.fragments.IFileFragment;
import cross.datastructures.fragments.IVariableFragment;
import cross.datastructures.fragments.ImmutableVariableFragment2;
import cross.datastructures.fragments.VariableFragment;
import cross.datastructures.tuple.Tuple2D;
import cross.exception.NotImplementedException;
import cross.exception.ResourceNotAvailableException;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import lombok.extern.java.Log;
import maltcms.datastructures.caches.IScanLine;
import maltcms.datastructures.ms.IChromatogram2D;
import maltcms.datastructures.ms.IExperiment2D;
import maltcms.datastructures.ms.IScan2D;
import maltcms.datastructures.ms.Scan2D;
import maltcms.tools.MaltcmsTools;
import net.sf.maltcms.chromaui.project.spi.caching.ChromatogramScanCache;
import org.apache.commons.configuration.Configuration;
import ucar.ma2.Array;
import ucar.ma2.ArrayShort;
import ucar.ma2.MAMath;

/**
 *
 * Implementation of 2D chromatogram backed by a cache for mass spectra.
 *
 * @author Nils Hoffmann
 */
@Log
public class CachingChromatogram2D implements IChromatogram2D, ICacheElementProvider<Integer, SerializableScan2D> {

    private IFileFragment parent;
    private final String scanAcquisitionTimeUnit = "seconds";
    @Configurable(name = "var.scan_acquisition_time")
    private String scan_acquisition_time_var = "scan_acquisition_time";
    private List<Array> massValues;
    private List<Array> intensityValues;
    private IVariableFragment scanAcquisitionTimeVariable;
    private ICacheDelegate<Integer, SerializableScan2D> whm;
    private int scans;
    private boolean initialized = false;
    private double modulationTime = -1;
    private double scanRate = 0.0;
    private RtProvider rtProvider = null;
    private int prefetchSize = 1000;
    private int spm = -1;
    private int modulations = -1;
    private AtomicBoolean loading = new AtomicBoolean(false);
    private static ExecutorService prefetchLoader = Executors.newFixedThreadPool(1);
    private SoftReference<double[]> satArrayReference;
    private SoftReference<Array> satReference;
    private Tuple2D<Double, Double> massRange;
    private Tuple2D<Double, Double> timeRange;
    private Array msLevel;
    private Map<Short, List<Integer>> msScanMap;

    public CachingChromatogram2D(final IFileFragment e) {
        this.parent = e;
        String id = e.getUri().toString() + "-2D";
        whm = ChromatogramScanCache
                .createVolatileAutoRetrievalCache(UUID.nameUUIDFromBytes(id.getBytes()).toString(), this);
    }

    public void setPrefetchSize(int numberOfScansToLoad) {
        this.prefetchSize = numberOfScansToLoad;
    }

    private void init() {
        if (!initialized) {
            final String mz = Factory.getInstance().getConfiguration().getString("var.mass_values", "mass_values");
            final String intens = Factory.getInstance().getConfiguration().getString("var.intensity_values",
                    "intensity_values");
            final String scan_index = Factory.getInstance().getConfiguration().getString("var.scan_index",
                    "scan_index");
            final IVariableFragment index = this.parent.getChild(scan_index);
            this.scans = MaltcmsTools.getNumberOfScans(this.parent);
            final IVariableFragment mzV = this.parent.getChild(mz);
            mzV.setIndex(index);
            activateCache(mzV);
            massValues = mzV.getIndexedArray();
            final IVariableFragment iV = this.parent.getChild(intens);
            iV.setIndex(index);
            activateCache(iV);
            intensityValues = iV.getIndexedArray();
            final String modulation_time = Factory.getInstance().getConfiguration().getString("var.modulation_time",
                    "modulation_time");
            modulationTime = parent.getChild(modulation_time).getArray().getDouble(0);
            try {
                final String scan_rate = Factory.getInstance().getConfiguration().getString("var.scan_rate",
                        "scan_rate");
                scanRate = parent.getChild(scan_rate).getArray().getDouble(0);
                if (scanRate == 0) {
                    Array satA = this.parent.getChild(scan_acquisition_time_var).getArray();
                    double s0 = satA.getDouble(0);
                    double s1 = satA.getDouble(1);
                    scanRate = 1.0d / (s1 - s0);
                }
                spm = (int) (Math.ceil(modulationTime * scanRate));
                modulations = (int) (scans / spm);
            } catch (ResourceNotAvailableException rnae) {
            }
            final String first_column_elution_time = Factory.getInstance().getConfiguration()
                    .getString("var.first_column_elution_time", "first_column_elution_time");
            final String second_column_elution_time = Factory.getInstance().getConfiguration()
                    .getString("var.second_column_elution_time", "second_column_elution_time");
            scanAcquisitionTimeVariable = this.parent.getChild(scan_acquisition_time_var);
            try {
                this.parent.getChild(first_column_elution_time);
                this.parent.getChild(second_column_elution_time);
                rtProvider = new ArbitraryModulationRtProvider(first_column_elution_time,
                        second_column_elution_time, parent);
            } catch (ResourceNotAvailableException rnae) {
                rtProvider = new UniformModulationRtProvider(scan_acquisition_time_var, parent);
                //                IScanLine scanLine = ScanLineCacheFactory.getDefaultScanLineCache(parent);
                Logger.getLogger(getClass().getName()).info("Generating first and second column elution time!");
                IVariableFragment fcmt = new VariableFragment(this.parent, first_column_elution_time);
                IVariableFragment scmt = new VariableFragment(this.parent, second_column_elution_time);
                Array satA = scanAcquisitionTimeVariable.getArray();
                Array fceta = Array.factory(satA.getElementType(), satA.getShape());
                Array sceta = Array.factory(satA.getElementType(), satA.getShape());
                for (int i = 0; i < this.scans; i++) {
                    double[] rts = rtProvider.getRts(i);
                    fceta.setDouble(i, rts[0]);
                    sceta.setDouble(i, rts[1]);
                }
                fcmt.setArray(fceta);
                scmt.setArray(sceta);
            }
            try {
                final String ms_level = Factory.getInstance().getConfiguration().getString("var.ms_level",
                        "ms_level");
                IVariableFragment msLevelVar = this.parent.getChild(ms_level);
                msLevel = msLevelVar.getArray();
                msScanMap = new TreeMap<>();
                for (int i = 0; i < msLevel.getShape()[0]; i++) {
                    Short msLevelValue = msLevel.getShort(i);
                    if (msLevelValue == 0) {
                        Logger.getLogger(getClass().getName()).info("Correcting msLevelValue of 0 to 1");
                        msLevelValue = 1;
                        msLevel.setShort(i, msLevelValue);
                    }
                    if (msScanMap.containsKey(msLevelValue)) {
                        List<Integer> scanToScan = msScanMap.get(msLevelValue);
                        scanToScan.add(i);
                    } else {
                        List<Integer> scanToScan = new ArrayList<>();
                        scanToScan.add(scanToScan.size(), i);
                        msScanMap.put(msLevelValue, scanToScan);
                    }
                }
            } catch (ResourceNotAvailableException rnae) {
                Logger.getLogger(getClass().getName())
                        .info("Chromatogram has no ms_level variable, assuming all scans are MS1!");
                msScanMap = new TreeMap<>();
                msLevel = new ArrayShort.D1(this.scans);
                List<Integer> scanToScan = new ArrayList<>();
                for (int i = 0; i < this.scans; i++) {
                    scanToScan.add(i);
                    msLevel.setShort(i, (short) 1);
                }
                msScanMap.put((short) 1, scanToScan);
            }
            initialized = true;
        }
    }

    protected void activateCache(IVariableFragment ivf) {
        if (ivf.getParent().getName().toLowerCase().endsWith(".mzml")
                || ivf.getParent().getName().toLowerCase().endsWith(".mzml.xml")) {
            Logger.getLogger(CachingChromatogram1D.class.getName())
                    .info("Not activating cached list on mzml file!");
        } else {
            if (ivf instanceof ImmutableVariableFragment2) {
                Logger.getLogger(getClass().getName()).log(Level.INFO, "Using cached access on variable: {0}", ivf);
                ((ImmutableVariableFragment2) ivf).setUseCachedList(true);
            }
            if (ivf instanceof VariableFragment) {
                Logger.getLogger(getClass().getName()).log(Level.INFO, "Using cached access on variable: {0}", ivf);
                ((VariableFragment) ivf).setUseCachedList(true);
            }
        }
    }

    //    protected Scan2D acquireFromCache(final int i) {
    //        try {
    //            init();
    //            SerializableScan2D scan = whm.get(i);
    //            if (scan == null) {
    //                scan = provide(i);
    //                whm.put(i, scan);
    //                if (!loading.get()) {
    //                    Runnable r = new Runnable() {
    //                        @Override
    //                        public void run() {
    //                            int minBound = Math.max(0, i - prefetchSize);
    //                            int maxBound = Math.min(getNumberOfScans(), i + prefetchSize);
    //                            for (int j = minBound; j <= maxBound; j++) {
    //                                whm.put(Integer.valueOf(j), provide(j));
    //                            }
    //                            loading.compareAndSet(true, false);
    //                        }
    //                    };
    //                    prefetchLoader.submit(r);
    //                }
    //            }
    //            return scan.getScan();
    //        } catch (java.lang.IndexOutOfBoundsException ex) {
    //            System.err.println("Warning: Could not access scan at index " + i);
    //            return null;
    //        }
    //    }
    protected Scan2D acquireFromCache(final int i) {
        try {
            SerializableScan2D scan = whm.get(i);
            if (scan == null) {
                System.err.println("Retrieving scan " + i);
                if (loading.compareAndSet(false, true)) {
                    System.err.println("Scheduling batched prefetch!");
                    Runnable r = new Runnable() {
                        @Override
                        public void run() {
                            int minBound = Math.max(0, i - prefetchSize);
                            int maxBound = Math.min(getNumberOfScans(), i + prefetchSize);
                            System.err.println(
                                    "Prefetching scans from " + minBound + " to " + maxBound + " into cache!");
                            for (int j = minBound; j <= maxBound; j++) {
                                whm.put(j, provide(j));
                            }
                            loading.compareAndSet(true, false);
                        }
                    };
                    prefetchLoader.submit(r);
                }
                scan = whm.get(i);
                if (scan == null) {
                    scan = provide(i);
                    System.err.println("Putting scan " + i + " into cache!");
                    whm.put(i, scan);
                }
            } else {
                System.err.println("Retrieved scan " + i + " from cache!");
            }
            return scan.getScan();
        } catch (java.lang.IndexOutOfBoundsException ex) {
            Logger.getLogger(getClass().getName()).log(Level.WARNING, "Warning: Could not access scan at index {0}",
                    i);
            return null;
        }
    }

    protected Scan2D buildScan(int i) {
        return acquireFromCache(i);
    }

    @Override
    public void configure(final Configuration cfg) {
        this.scan_acquisition_time_var = cfg.getString("var.scan_acquisition_time", "scan_acquisition_time");
        //        this.first_column_elution_time_var = cfg.getString("var.first_column_elution_time");
        //        this.second_column_elution_time_var = cfg.getString("var.second_column_elution_time");
    }

    @Override
    public Tuple2D<Double, Double> getTimeRange() {
        init();
        if (timeRange == null) {
            MAMath.MinMax satMM = MAMath.getMinMax(getScanAcquisitionTime());
            timeRange = new Tuple2D<>(satMM.min, satMM.max);
        }
        return timeRange;
    }

    @Override
    public Tuple2D<Double, Double> getMassRange() {
        init();
        if (massRange == null) {
            massRange = MaltcmsTools.getMinMaxMassRange(parent);
        }
        return massRange;
    }

    @Override
    public List<Array> getIntensities() {
        init();
        return intensityValues;
    }

    @Override
    public List<Array> getMasses() {
        init();
        return massValues;
    }

    /**
     * @param scan scan index to load
     */
    @Override
    public Scan2D getScan(final int scan) {
        init();
        return buildScan(scan);
    }

    @Override
    public String getScanAcquisitionTimeUnit() {
        return this.scanAcquisitionTimeUnit;
    }

    public List<Scan2D> getScans() {
        init();
        ArrayList<Scan2D> al = new ArrayList<>();
        for (int i = 0; i < getNumberOfScans(); i++) {
            al.add(buildScan(i));
        }
        return al;
    }

    /**
     * This iterator acts on the underlying collection of scans in
     * Chromatogram1D, so be careful with concurrent access / modification!
     */
    @Override
    public Iterator<IScan2D> iterator() {

        final Iterator<IScan2D> iter = new Iterator<IScan2D>() {
            private int currentPos = 0;

            @Override
            public boolean hasNext() {
                //                init();
                if (this.currentPos < getScans().size() - 1) {
                    return true;
                }
                return false;
            }

            @Override
            public IScan2D next() {
                return getScan(this.currentPos++);
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Can not remove scans with iterator!");
            }
        };
        return iter;
    }

    public void setExperiment(final IExperiment2D e) {
        this.parent = e;
    }

    /*
     * (non-Javadoc)
     *
     * @see maltcms.datastructures.ms.IChromatogram#getScanAcquisitionTime()
     */
    @Override
    public Array getScanAcquisitionTime() {
        init();
        Array sat = null;
        if (satReference == null || satReference.get() == null) {
            sat = scanAcquisitionTimeVariable.getArray();
            satReference = new SoftReference<>(sat);
        } else {
            sat = satReference.get();
            if (sat == null) {
                sat = scanAcquisitionTimeVariable.getArray();
                satReference = new SoftReference<>(sat);
            }
        }
        return sat;
    }

    /*
     * (non-Javadoc)
     *
     * @see maltcms.datastructures.ms.IChromatogram#getNumberOfScans()
     */
    @Override
    public int getNumberOfScans() {
        init();
        return scans;
        //      return MaltcmsTools.getNumberOfScans(this.parent);
    }

    protected double[] getSatArray() {
        double[] satArray = null;
        if (satArrayReference == null || satArrayReference.get() == null) {
            satArray = (double[]) getScanAcquisitionTime().get1DJavaArray(double.class);
            satArrayReference = new SoftReference<>(satArray);
        } else {
            satArray = satArrayReference.get();
        }
        return satArray;
    }

    @Override
    public int getIndexFor(double scan_acquisition_time) {
        init();
        double[] satArray = getSatArray();
        int idx = Arrays.binarySearch(satArray, scan_acquisition_time);
        if (idx >= 0) {// exact hit
            log.log(Level.FINE, "sat {0}, scan_index {1}", new Object[] { scan_acquisition_time, idx });
            return idx;
        } else {// imprecise hit, find closest element
            int insertionPosition = (-idx) - 1;
            if (insertionPosition <= 0) {
                log.log(Level.WARNING, "Insertion position was {0}, setting to index 0", insertionPosition);
            }
            if (insertionPosition >= satArray.length) {
                log.log(Level.WARNING, "Insertion position was {0}, setting to index {1}",
                        new Object[] { insertionPosition, satArray.length - 1 });
            }
            double current = satArray[Math.min(satArray.length - 1, insertionPosition)];
            double previous = satArray[Math.max(0, insertionPosition - 1)];
            if (Math.abs(scan_acquisition_time - previous) <= Math.abs(scan_acquisition_time - current)) {
                int index = Math.max(0, insertionPosition - 1);
                return index;
            } else {
                return insertionPosition;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see maltcms.datastructures.ms.IChromatogram#getParent()
     */
    @Override
    public IFileFragment getParent() {
        //        init();
        return this.parent;
    }

    @Override
    public SerializableScan2D provide(Integer k) {
        init();
        final Array masses = massValues.get(k);
        final Array intens = intensityValues.get(k);
        final double[] rts = rtProvider.getRts(k);
        short scanMsLevel = 1;
        if (msLevel != null) {
            scanMsLevel = msLevel.getByte(k);
        }
        Scan2D s = new Scan2D(masses, intens, k,
                this.parent.getChild(scan_acquisition_time_var).getArray().getDouble(k), k, k, rts[0], rts[1],
                scanMsLevel);
        return new SerializableScan2D(s);
    }

    @Override
    public IScan2D getScan2D(int i, int i1) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int getNumberOfModulations() {
        init();
        return modulations;
    }

    @Override
    public int getNumberOfScansPerModulation() {
        init();
        return spm;
    }

    @Override
    public int getNumberOf2DScans() {
        init();
        return scans;
        //return this.parent.getChild(this.scan_acquisition_time_var, true).getDimensions()[0].getLength();
    }

    @Override
    public double getModulationDuration() {
        init();
        return this.modulationTime;
    }

    @Override
    public String getSecondColumnScanAcquisitionTimeUnit() {
        return "seconds";
    }

    @Override
    public Point getPointFor(int i) {
        init();
        IScan2D scan2d = getScan(i);
        return new Point(scan2d.getFirstColumnScanIndex(), scan2d.getSecondColumnScanIndex());
    }

    @Override
    public Point getPointFor(double d) {
        init();
        IScan2D scan2d = getScan(getIndexFor(d));
        return new Point(scan2d.getFirstColumnScanIndex(), scan2d.getSecondColumnScanIndex());
    }

    public RtProvider getRtProvider() {
        init();
        return this.rtProvider;
    }

    @Override
    public Rectangle2D getTimeRange2D() {
        IScan2D startScan = getScanForMsLevel(0, (short) 1);
        IScan2D endScan = getScanForMsLevel(getNumberOfScansForMsLevel((short) 1) - 1, (short) 1);
        double[] startRts = getRtProvider().getRts(0);
        double[] stopRts = getRtProvider().getRts(getNumberOfScans() - 1);
        return new Rectangle2D.Double(startScan.getFirstColumnScanAcquisitionTime(), 0,
                endScan.getFirstColumnScanAcquisitionTime() - startScan.getFirstColumnScanAcquisitionTime(),
                getModulationDuration());
    }

    @Override
    public IScanLine getScanLineImpl() {
        throw new NotImplementedException();
    }

    public abstract class RtProvider {

        abstract double[] getRts(int idx);
    }

    public class UniformModulationRtProvider extends RtProvider {

        private final double satOffset;
        private final double modulationTime;
        private final double scanRate;
        private final double scanDuration;

        public UniformModulationRtProvider(String rtVariable, IFileFragment resource) {
            this.satOffset = resource.getChild(rtVariable).getArray().getDouble(0);
            modulationTime = resource.getChild("modulation_time").getArray().getDouble(0);
            this.scanRate = resource.getChild("scan_rate").getArray().getDouble(0);
            this.scanDuration = 1.0d / this.scanRate;
        }

        @Override
        double[] getRts(int idx) {
            final int scanspermodulation = (int) (this.scanRate * this.modulationTime);
            final int scanLineIdx = ((int) idx) / scanspermodulation;
            double sat1 = satOffset + (scanLineIdx * this.modulationTime);
            double sat2 = (((((float) idx) % ((float) scanspermodulation))) * this.scanDuration);
            return new double[] { sat1, sat2 };
        }
    }

    public class ArbitraryModulationRtProvider extends RtProvider {

        private final Array rt1, rt2;

        public ArbitraryModulationRtProvider(String rt1Variable, String rt2Variable, IFileFragment resource) {
            this.rt1 = resource.getChild(rt1Variable).getArray();
            this.rt2 = resource.getChild(rt2Variable).getArray();
        }

        @Override
        double[] getRts(int idx) {
            return new double[] { rt1.getDouble(idx), rt2.getDouble(idx) };
        }
    }

    @Override
    public int getNumberOfScansForMsLevel(short msLevelValue) {
        init();
        if (msScanMap.containsKey(msLevelValue)) {
            return msScanMap.get(msLevelValue).size();
        }
        return 0;
    }

    @Override
    public Iterable<IScan2D> subsetByMsLevel(final short msLevel) {
        Iterable<IScan2D> iterable = new Iterable<IScan2D>() {
            @Override
            public Iterator<IScan2D> iterator() {
                return new Scan2DIterator(msLevel);
            }
        };
        return iterable;
    }

    @Override
    public Collection<Short> getMsLevels() {
        init();
        List<Short> l = new ArrayList<>(msScanMap.keySet());
        Collections.sort(l);
        return l;
    }

    @Override
    public IScan2D getScanForMsLevel(int i, short level) {
        init();
        if (msScanMap.containsKey(level)) {
            return getScan(msScanMap.get(level).get(i));
        } else {
            throw new ResourceNotAvailableException("No mass spectra available for fragmentation level " + level
                    + " in chromatogram " + getParent().getUri());
        }
    }

    @Override
    public List<Integer> getIndicesOfScansForMsLevel(short level) {
        init();
        if (msScanMap.containsKey(level)) {
            return Collections.unmodifiableList(msScanMap.get(level));
        } else {
            throw new ResourceNotAvailableException("No mass spectra available for fragmentation level " + level
                    + " in chromatogram " + getParent().getUri());
        }
    }

    private class Scan2DIterator implements Iterator<IScan2D> {

        private final int maxScans;
        private int scan = 0;
        private short msLevel = 1;

        public Scan2DIterator(short msLevel) {
            maxScans = getNumberOfScansForMsLevel(msLevel);
            this.msLevel = msLevel;
        }

        @Override
        public boolean hasNext() {
            return scan < maxScans - 1;
        }

        @Override
        public IScan2D next() {
            return getScanForMsLevel(scan++, msLevel);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        }
    }
}