package org.signalml.plugin.newstager.logic.mgr; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.signalml.domain.signal.samplesource.MultichannelSampleSource; import org.signalml.method.ComputationException; import org.signalml.plugin.data.logic.PluginComputationMgrStepResult; import org.signalml.plugin.exception.PluginToolAbortException; import org.signalml.plugin.exception.PluginToolInterruptedException; import org.signalml.plugin.method.helper.AbstractPluginTrackerUpdaterWithTimer; import org.signalml.plugin.method.logic.AbstractPluginComputationMgrStep; import org.signalml.plugin.method.logic.IPluginComputationMgrStepTrackerProxy; import org.signalml.plugin.method.logic.PluginWorkerSet; import org.signalml.plugin.newstager.data.NewStagerConstants; import org.signalml.plugin.newstager.data.NewStagerFASPThreshold; import org.signalml.plugin.newstager.data.NewStagerParameterThresholds; import org.signalml.plugin.newstager.data.NewStagerParameters; import org.signalml.plugin.newstager.data.NewStagerSignalReaderWorkerData; import org.signalml.plugin.newstager.data.NewStagerSignalStatsResult; import org.signalml.plugin.newstager.data.NewStagerSleepStats; import org.signalml.plugin.newstager.data.NewStagerStatWorkerData; import org.signalml.plugin.newstager.data.logic.INewStagerWorkerCompletion; import org.signalml.plugin.newstager.data.logic.NewStagerComputationProgressPhase; import org.signalml.plugin.newstager.data.logic.NewStagerMgrStepData; import org.signalml.plugin.newstager.data.logic.NewStagerStatAlgorithmResult; import org.signalml.plugin.newstager.io.INewStagerStatsSynchronizer; import org.signalml.plugin.newstager.io.NewStagerSignalReaderWorker; import org.signalml.plugin.newstager.logic.artifact.NewStagerStatWorker; import org.signalml.plugin.signal.PluginSignalHelper; import org.signalml.util.MinMaxRange; public class NewStagerSignalStatsStep extends AbstractPluginComputationMgrStep<NewStagerMgrStepData> { private class TrackerUpdater extends AbstractPluginTrackerUpdaterWithTimer { private final NewStagerSignalStatsStep step; private final IPluginComputationMgrStepTrackerProxy<NewStagerComputationProgressPhase> tracker; private final int blockCount; public TrackerUpdater( NewStagerSignalStatsStep step, IPluginComputationMgrStepTrackerProxy<NewStagerComputationProgressPhase> tracker, int blockCount) { this.step = step; this.tracker = tracker; this.blockCount = blockCount; } @Override public void update(int progress, int prevProgress) { if (progress < this.blockCount) { this.tracker.advance(this.step, Math.max(progress - prevProgress, 0)); this.tracker .setProgressPhase( NewStagerComputationProgressPhase.SIGNAL_STATS_BLOCK_COMPUTATION_PHASE, progress, this.blockCount); } } } private static final int BUFFER_QUEUE_SIZE = 20; private final PluginWorkerSet workers; private final TrackerUpdater trackerUpdater; private final AtomicInteger progressBlockCount; private final AtomicBoolean statResultReadyFlag; private final int blockLengthInSapmles; private Integer blockCount; private NewStagerStatAlgorithmResult statResult; public NewStagerSignalStatsStep(NewStagerMgrStepData data) { super(data); this.workers = new PluginWorkerSet(this.data.threadFactory); this.progressBlockCount = new AtomicInteger(0); this.statResultReadyFlag = new AtomicBoolean(false); this.trackerUpdater = new TrackerUpdater(this, this.data.tracker, this.getBlockCount()); this.statResult = null; this.blockLengthInSapmles = this.data.constants.getBlockLengthInSamples(); } @Override public int getStepNumberEstimate() { return this.getBlockCount() + 1; } @Override protected PluginComputationMgrStepResult prepareStepResult() { if (!this.statResultReadyFlag.get()) { return null; } NewStagerParameters parameters = this.computeNewParameters(); return new NewStagerSignalStatsResult( this.getSignalStatCoeffs(parameters), parameters, this.statResult.muscle, this.statResult.montage); } @Override protected PluginComputationMgrStepResult doRun( PluginComputationMgrStepResult prevStepResult) throws PluginToolAbortException, PluginToolInterruptedException, ComputationException { IPluginComputationMgrStepTrackerProxy<NewStagerComputationProgressPhase> tracker = this.data.tracker; tracker.setProgressPhase(NewStagerComputationProgressPhase.SIGNAL_STATS_PREPARE_PHASE); this.createWorkers(); this.workers.startAll(); tracker.setProgressPhase(NewStagerComputationProgressPhase.SIGNAL_STATS_SOURCE_FILE_INITIAL_READ_PHASE); this.trackerUpdater.start(1500); while (!this.statResultReadyFlag.get()) { try { Thread.sleep(500); this.trackerUpdater.setProgress(this.progressBlockCount.get()); } catch (InterruptedException e) { throw new PluginToolInterruptedException(e); } this.checkAbortState(); } return this.prepareStepResult(); } @Override protected void cleanup() { super.cleanup(); this.trackerUpdater.stop(); this.stopWorkers(); } private void createWorkers() { MultichannelSampleSource source = this.data.stagerData .getSampleSource(); final BlockingQueue<double[][]> freeBufferQueue = new ArrayBlockingQueue<double[][]>( BUFFER_QUEUE_SIZE); final BlockingQueue<double[][]> readyBufferQueue = new ArrayBlockingQueue<double[][]>( BUFFER_QUEUE_SIZE); for (int i = 0; i < BUFFER_QUEUE_SIZE; ++i) { freeBufferQueue .add(new double[source.getChannelCount()][this.data.constants .getBlockLengthInSamples()]); } INewStagerStatsSynchronizer synchronizer = new INewStagerStatsSynchronizer() { AtomicBoolean shutdownFlag = new AtomicBoolean(); @Override public void markBufferAsReady(double[][] buffer) throws InterruptedException { readyBufferQueue.add(buffer); } @Override public double[][] getWritableBuffer() throws InterruptedException { return freeBufferQueue.take(); } @Override public double[][] getReadyBuffer() throws InterruptedException { double result[][] = null; do { result = readyBufferQueue.poll(1000, TimeUnit.MILLISECONDS); if (result == null && this.shutdownFlag.get()) { return null; } } while (result == null); return result; } @Override public void markBufferAsProcessed(double buffer[][]) throws InterruptedException { freeBufferQueue.add(buffer); } @Override public void finalizeBuffers() throws InterruptedException { this.shutdownFlag.set(true); } @Override public int getBufferLength() { return blockLengthInSapmles; } }; final AtomicBoolean resultReadyFlag = this.statResultReadyFlag; INewStagerWorkerCompletion<NewStagerStatAlgorithmResult> completion = new INewStagerWorkerCompletion<NewStagerStatAlgorithmResult>() { @Override public void signalProgress(int i) { progressBlockCount.incrementAndGet(); } @Override public void completeWork(NewStagerStatAlgorithmResult result) { resultReadyFlag.set(true); statResult = result; } }; Runnable readerWorker = new NewStagerSignalReaderWorker( new NewStagerSignalReaderWorkerData(source, synchronizer)); Runnable statWorker = new NewStagerStatWorker( new NewStagerStatWorkerData(synchronizer, completion, this.data.constants, this.data.stagerData.getParameters(), this.data.stagerData.getChannelMap())); this.workers.add(readerWorker); this.workers.add(statWorker); } private void stopWorkers() { this.workers.terminateAll(); } private NewStagerParameters computeNewParameters() { NewStagerParameters oldParameters = this.data.stagerData .getParameters(); if (!this.statResultReadyFlag.get()) { return oldParameters; } NewStagerParameterThresholds thresholds = oldParameters.thresholds; NewStagerConstants constants = this.data.constants; double coeff = constants.amplitudeA * this.statResult.deviation + constants.amplitudeB; return new NewStagerParameters(oldParameters.bookFilePath, oldParameters.rules, oldParameters.analyseEMGChannelFlag, oldParameters.analyseEEGChannelsFlag, oldParameters.primaryHypnogramFlag, new NewStagerParameterThresholds( this.computeMontageToneEMGThreshold(), thresholds.montageEEGThreshold, thresholds.montageEMGThreshold, thresholds.montageToneEMGThreshold, thresholds.remEogDeflectionThreshold, thresholds.semEogDeflectionThreshold, this.convertThreshold(thresholds.alphaThreshold, coeff, constants.alphaOffset), this.convertThreshold( thresholds.deltaThreshold, coeff, constants.deltaOffset), this.convertThreshold( thresholds.spindleThreshold, coeff, constants.spindleOffset), thresholds.thetaThreshold, thresholds.kCThreshold)); } private NewStagerFASPThreshold convertThreshold( NewStagerFASPThreshold threshold, double coeff, double offset) { if (threshold.amplitude.getMin() == MinMaxRange.AUTO) { MinMaxRange amplitude = threshold.amplitude; return new NewStagerFASPThreshold(new MinMaxRange( amplitude.getUnlimitedValue(), coeff - offset, amplitude.getMax(), amplitude.isMinUnlimited(), amplitude.isMaxUnlimited()), threshold.frequency, threshold.scale, threshold.phase); } return threshold; } private double computeMontageToneEMGThreshold() { Double oldThreshold = this.data.stagerData.getParameters().thresholds.toneEMG; if (oldThreshold.isNaN()) { double t[] = this.statResult.muscle; double mean = 0.0d; int length = Math.max(t.length - 1, 0); //for some reason we discard the last element for (int i = 0; i < length; ++i) { mean += t[i]; } mean /= t.length; // this is not the actual mean return (mean > this.data.constants.muscleThreshold) ? this.data.constants.muscleThreshold : mean / this.data.constants.muscleThresholdRate; } else { return oldThreshold; } } private NewStagerSleepStats getSignalStatCoeffs( NewStagerParameters parameters) { NewStagerParameterThresholds thresholds = parameters.thresholds; return new NewStagerSleepStats( thresholds.alphaThreshold.amplitude.getMin(), thresholds.deltaThreshold.amplitude.getMin(), thresholds.spindleThreshold.amplitude.getMin(), thresholds.toneEMG); } private int getBlockCount() { if (this.blockCount == null) { this.blockCount = new Integer(PluginSignalHelper.GetBlockCount( this.data.stagerData.getSampleSource(), this.data.constants.getBlockLengthInSamples())); } return this.blockCount; } }