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

Java tutorial

Introduction

Here is the source code for net.sf.maltcms.chromaui.project.spi.descriptors.CachingChromatogram1D.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.tools.EvalTools;
import cross.datastructures.tuple.Tuple2D;
import cross.exception.ResourceNotAvailableException;
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 lombok.extern.slf4j.Slf4j;
import maltcms.datastructures.ms.IChromatogram1D;
import maltcms.datastructures.ms.IExperiment1D;
import maltcms.datastructures.ms.IScan1D;
import maltcms.datastructures.ms.Scan1D;
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 1D chromatogram backed by a cache for mass spectra.
 *
 * @author Nils Hoffmann
 */
@Log
public class CachingChromatogram1D implements IChromatogram1D, ICacheElementProvider<Integer, SerializableScan1D> {

    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 intensityValuesVariable;
    private IVariableFragment massValuesVariable;
    private IVariableFragment indexVariable;
    private IVariableFragment scanAcquisitionTimeVariable;
    private ICacheDelegate<Integer, SerializableScan1D> whm;
    private int scans;
    private int prefetchSize = 500;
    private boolean initialized = false;
    private AtomicBoolean loading = new AtomicBoolean(false);
    private static ExecutorService prefetchLoader = Executors.newFixedThreadPool(1);
    private SoftReference<Array> satReference;
    private SoftReference<double[]> satArrayReference;
    private Tuple2D<Double, Double> massRange;
    private Tuple2D<Double, Double> timeRange;
    private Array msLevel;
    private Map<Short, List<Integer>> msScanMap;

    public CachingChromatogram1D(final IFileFragment e) {
        this.parent = e;
        String id = e.getUri().toString() + "-1D";
        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");
            indexVariable = this.parent.getChild(scan_index);
            this.scans = MaltcmsTools.getNumberOfScans(this.parent);
            massValuesVariable = this.parent.getChild(mz);
            massValuesVariable.setIndex(indexVariable);
            activateCache(massValuesVariable);
            massValues = massValuesVariable.getIndexedArray();
            intensityValuesVariable = this.parent.getChild(intens);
            intensityValuesVariable.setIndex(indexVariable);
            activateCache(intensityValuesVariable);
            intensityValues = intensityValuesVariable.getIndexedArray();
            scanAcquisitionTimeVariable = this.parent.getChild(scan_acquisition_time_var);
            try {
                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 Scan1D acquireFromCache(final int i) {
        try {
            SerializableScan1D 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 Scan1D buildScan(int i) {
        init();
        return acquireFromCache(i);
    }

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

    @Override
    public Tuple2D<Double, Double> getTimeRange() {
        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 Scan1D getScan(final int scan) {
        init();
        return buildScan(scan);
    }

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

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

    @Override
    public Iterable<IScan1D> subsetByScanAcquisitionTime(double startSat, double stopSat) {
        final int startIndex = getIndexFor(startSat);
        if (startIndex < 0) {
            throw new ArrayIndexOutOfBoundsException(startIndex);
        }
        final int stopIndex = getIndexFor(stopSat);
        if (stopIndex > getNumberOfScans() - 1) {
            throw new ArrayIndexOutOfBoundsException(stopIndex);
        }
        final Iterator<IScan1D> iter = new Iterator<IScan1D>() {
            private int currentPos = startIndex;

            @Override
            public boolean hasNext() {
                return this.currentPos < stopIndex;
            }

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

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

    @Override
    public Iterable<IScan1D> subsetByScanIndex(final int startIndex, final int stopIndex) {
        if (startIndex < 0) {
            throw new ArrayIndexOutOfBoundsException(startIndex);
        }
        if (stopIndex > getNumberOfScans() - 1) {
            throw new ArrayIndexOutOfBoundsException(stopIndex);
        }
        final Iterator<IScan1D> iter = new Iterator<IScan1D>() {
            private int currentPos = startIndex;

            @Override
            public boolean hasNext() {
                return this.currentPos < stopIndex;
            }

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

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

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

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

            @Override
            public boolean hasNext() {
                return this.currentPos < getScans().size() - 1;
            }

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

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

    public void setExperiment(final IExperiment1D 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);
            }
        }
        EvalTools.notNull(sat, this);
        return sat;
    }

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

    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();
        }
        if (satArray == null) {
            throw new ResourceNotAvailableException("Could not retrieve scan acquisition time array!");
        }
        return satArray;
    }

    @Override
    public int getIndexFor(double scan_acquisition_time) {
        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() {
        return this.parent;
    }

    @Override
    public SerializableScan1D provide(Integer k) {
        init();
        if (Logger.getLogger(getClass().getName()).isLoggable(Level.FINE)) {
            Logger.getLogger(getClass().getName()).log(Level.FINE, "Retrieving mass value at index {0}", k);
        }
        final Array masses = massValues.get(k);
        if (Logger.getLogger(getClass().getName()).isLoggable(Level.FINE)) {
            Logger.getLogger(getClass().getName()).log(Level.FINE, "Retrieving intensity value at index {0}", k);
        }
        final Array intens = intensityValues.get(k);
        short scanMsLevel = 1;
        if (msLevel != null) {
            if (Logger.getLogger(getClass().getName()).isLoggable(Level.FINE)) {
                Logger.getLogger(getClass().getName()).log(Level.FINE, "Retrieving ms level at index {0}", k);
            }
            scanMsLevel = msLevel.getShort(k);
        }
        Scan1D s = new Scan1D(masses, intens, k, getSatArray()[k], scanMsLevel);
        return new SerializableScan1D(s);
    }

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

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

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

    @Override
    public IScan1D 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 Scan1DIterator implements Iterator<IScan1D> {

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

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

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

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

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