/* MultichannelSampleFilter.java created 2007-09-24 * */ package org.signalml.domain.signal.filter; import java.beans.PropertyChangeEvent; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import java.util.Vector; import java.util.concurrent.Semaphore; import java.util.logging.Level; import org.apache.log4j.Logger; import org.signalml.domain.montage.Montage; import org.signalml.domain.montage.MontageMismatchException; import org.signalml.domain.montage.filter.FFTSampleFilter; import org.signalml.domain.montage.filter.FFTSampleFilter.Range; import org.signalml.domain.montage.filter.SampleFilterDefinition; import org.signalml.domain.montage.filter.TimeDomainSampleFilter; import org.signalml.domain.signal.MultichannelSampleProcessor; import org.signalml.domain.signal.filter.fft.FFTSinglechannelSampleFilter; import org.signalml.domain.signal.filter.iir.AbstractIIRSinglechannelSampleFilter; import org.signalml.domain.signal.filter.iir.OfflineIIRSinglechannelSampleFilter; import org.signalml.domain.signal.filter.iir.OnlineIIRSinglechannelSampleFilter; import org.signalml.domain.signal.samplesource.ChangeableMultichannelSampleSource; import org.signalml.domain.signal.samplesource.ChannelSelectorSampleSource; import org.signalml.domain.signal.samplesource.MultichannelSampleSource; import org.signalml.domain.signal.samplesource.OriginalMultichannelSampleSource; import org.signalml.domain.signal.samplesource.SampleSource; import org.signalml.math.iirdesigner.BadFilterParametersException; import org.signalml.math.iirdesigner.FilterCoefficients; import org.signalml.math.iirdesigner.IIRDesigner; /** * This abstract class represents a filter of samples for multichannel signal. * It contains the list of {@link SinglechannelSampleFilterEngine engines} for every channel * and uses them to filter samples. * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. */ public class MultichannelSampleFilter extends MultichannelSampleProcessor { protected OriginalMultichannelSampleSource originalSource; protected static final Logger logger = Logger.getLogger(MultichannelSampleFilter.class); /** * a vector that at each index (being also the index of a channel) holds * a chain (list) of {@link SinglechannelSampleFilterEngine engines}. */ protected Vector<LinkedList<SinglechannelSampleFilterEngine>> chains; /** * the {@link Montage montage} with which this filter is associated */ protected Montage currentMontage = null; /** * A boolean indicating if montage has just changed. * For the use of {@link #updateTimeDomainSampleFilterEnginesCache()}. */ private boolean newMontage = false; /** * A semaphore to prevent using getSamples() and * applyMontage() simultaneously in a multithreading enviroment. */ private Semaphore semaphore = new Semaphore(1); private long processedSampleCount = 0; /** * Constructor. Creates an empty sample filter using a given source. * @param source the source of samples */ public MultichannelSampleFilter(MultichannelSampleSource source) { super(source); reinitFilterChains(); } /** * Constructor. Creates an empty sample filter using a given source and * a given original sample source. Original source of samples is used to * detect changes in original source of signal (i.e. new samples) which * should be used to update the cached filtered signal which is stored * in {@link AbstractIIRSinglechannelSampleFilter TimeDomainSampleFilterEngines}. * This is useful if the originalSource is an instance of * {@link ChangeableMultichannelSampleSource}. * @param source the source of samples * @param originalSource the original source of samples */ public MultichannelSampleFilter(MultichannelSampleSource source, OriginalMultichannelSampleSource originalSource) { this(source); this.originalSource = originalSource; } /** * Updates the filtered signal cache in all * {@link AbstractIIRSinglechannelSampleFilter TimeDomainSampleFilterEngines} * in the MultichannelSampleFilter. * * @param samplesAdded how many new samples were added to the originalSource * since the last time this method was evoked. */ private void updateTimeDomainSampleFilterEnginesCache(int samplesAdded) { SinglechannelSampleFilterEngine eng; Iterator<SinglechannelSampleFilterEngine> it; for (int i = 0; i < chains.size(); i++) { it = (chains.get(i)).iterator(); while (it.hasNext()) { eng = it.next(); if (eng instanceof OnlineIIRSinglechannelSampleFilter) { ((OnlineIIRSinglechannelSampleFilter)eng).updateCache(samplesAdded); } } } } /** * Updates the filtered signal cache in all * {@link AbstractIIRSinglechannelSampleFilter TimeDomainSampleFilterEngines} * in the MultichannelSampleFilter. Automatically detects the number of * new samples if originalSource is {@link ChangeableMultichannelSampleSource} * and runs the {@link #updateTimeDomainSampleFilterEnginesCache(int)}. * Has no effect if the originalSource is not a {@link ChangeableMultichannelSampleSource}. */ private void updateTimeDomainSampleFilterEnginesCache() { if (originalSource instanceof ChangeableMultichannelSampleSource) { ChangeableMultichannelSampleSource os = (ChangeableMultichannelSampleSource)originalSource; try { semaphore.acquire(); os.lock(); int samplesAdded; if (newMontage) { newMontage = false; samplesAdded = source.getSampleCount(0); this.processedSampleCount = os.getAddedSamplesCount(); } else { samplesAdded = (int) (os.getAddedSamplesCount() - processedSampleCount); processedSampleCount = os.getAddedSamplesCount(); } if (samplesAdded > 0) updateTimeDomainSampleFilterEnginesCache(samplesAdded); } catch (InterruptedException ex) { java.util.logging.Logger.getLogger(MultichannelSampleFilter.class.getName()).log(Level.SEVERE, null, ex); } finally { os.unlock(); semaphore.release(); } } } @Override public synchronized void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) { try { if (originalSource instanceof ChangeableMultichannelSampleSource) updateTimeDomainSampleFilterEnginesCache(); semaphore.acquire(); LinkedList<SinglechannelSampleFilterEngine> chain = chains.get(channel); if (chain.isEmpty()) { source.getSamples(channel, target, signalOffset, count, arrayOffset); } else { ListIterator<SinglechannelSampleFilterEngine> it = chain.listIterator(chain.size()); SinglechannelSampleFilterEngine last = it.previous(); last.getSamples(target, signalOffset, count, arrayOffset); } } catch (InterruptedException ex) { java.util.logging.Logger.getLogger(MultichannelSampleFilter.class.getName()).log(Level.SEVERE, null, ex); } finally { semaphore.release(); } } /** * Adds the filter {@link SinglechannelSampleFilterEngine engine} for all * channels. * @param filter the filter engine to be added */ public void addFilter(SinglechannelSampleFilterEngine filter) { int cnt = chains.size(); LinkedList<SinglechannelSampleFilterEngine> chain; for (int i=0; i<cnt; i++) { chain = chains.get(i); chain.add(filter); } } /** * Adds the filter {@link SinglechannelSampleFilterEngine engine} for specified * channels. * @param filter the filter engine to be added * @param channels array of indexes of channels for which filter engine * is to be added */ public void addFilter(SinglechannelSampleFilterEngine filter, int[] channels) { LinkedList<SinglechannelSampleFilterEngine> chain; for (int i=0; i<channels.length; i++) { chain = chains.get(channels[i]); chain.add(filter); } } /** * Adds the filter {@link SinglechannelSampleFilterEngine engine} for the * specified channel. * @param filter the filter engine to be added * @param channel the number of the channel for which filter engine * is to be added */ public void addFilter(SinglechannelSampleFilterEngine filter, int channel) { LinkedList<SinglechannelSampleFilterEngine> chain; chain = chains.get(channel); chain.add(filter); } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource() == source) { if (evt.getPropertyName().equals(CHANNEL_COUNT_PROPERTY)) { reinitFilterChains(); } } else if (evt.getPropertyName().equals(OriginalMultichannelSampleSource.CALIBRATION_PROPERTY)) { updateTimeDomainSampleFilterEnginesCache(source.getSampleCount(0)); } super.propertyChange(evt); } /** * Clears the list of filter {@link SinglechannelSampleFilterEngine engines}. * It is done by creating new and replacing. */ public void reinitFilterChains() { int cnt = source.getChannelCount(); chains = new Vector<LinkedList<SinglechannelSampleFilterEngine>>(cnt); LinkedList<SinglechannelSampleFilterEngine> chain; for (int i=0; i<cnt; i++) { chain = new LinkedList<SinglechannelSampleFilterEngine>(); chains.add(chain); } } public Montage getCurrentMontage() { return currentMontage; } public void setCurrentMontage(Montage currentMontage) throws MontageMismatchException { try { applyMontage(currentMontage); } catch (MontageMismatchException ex) { logger.error("Failed to apply montage", ex); this.currentMontage = null; reinitFilterChains(); throw ex; } this.currentMontage = currentMontage; } /** * Imports all {@link TimeDomainSampleFilter time domain filters} from the * given montage to the {@link MultichannelSampleFilter}. * * @param montage the montage from which the filters will be imported */ private void importTimeDomainFiltersFromMontage(Montage montage) { int channelCount = montage.getMontageChannelCount(); int filterCount = montage.getSampleFilterCount(); SampleFilterDefinition[] definitions = new SampleFilterDefinition[filterCount]; TimeDomainSampleFilter tdsFilter; LinkedList<SinglechannelSampleFilterEngine> chain; SampleSource input;//input for the next filter in the list int i, e; for (i = 0; i < filterCount; i++) { definitions[i] = montage.getSampleFilterAt(i); if (definitions[i] instanceof TimeDomainSampleFilter) { tdsFilter = (TimeDomainSampleFilter) definitions[i]; tdsFilter.setSamplingFrequency(source.getSamplingFrequency()); FilterCoefficients filterCoefficients = null; try { filterCoefficients = IIRDesigner.designDigitalFilter(tdsFilter); } catch (BadFilterParametersException ex) { java.util.logging.Logger.getLogger(AbstractIIRSinglechannelSampleFilter.class.getName()).log(Level.SEVERE, null, ex); continue; } for (e = 0; e < channelCount; e++) { if (!montage.isFilteringExcluded(i, e)) { chain = chains.get(e); if (chain.isEmpty()) input = new ChannelSelectorSampleSource(source, e); else input = chain.getLast(); AbstractIIRSinglechannelSampleFilter timeDomainSampleFilterEngine; if (originalSource instanceof ChangeableMultichannelSampleSource) { timeDomainSampleFilterEngine = new OnlineIIRSinglechannelSampleFilter(input, tdsFilter, filterCoefficients); } else { timeDomainSampleFilterEngine = new OfflineIIRSinglechannelSampleFilter(input, tdsFilter, filterCoefficients); ((OfflineIIRSinglechannelSampleFilter)timeDomainSampleFilterEngine).setFiltfiltEnabled(montage.isFiltfiltEnabled()); } addFilter(timeDomainSampleFilterEngine, e); } } } } } /** * Imports all {@link FFTSampleFilter FFT filters} from the * given montage to the {@link MultichannelSampleFilter}. * * @param montage the montage from which the filters will be imported */ private void importFFTSampleFiltersFromMontage(Montage montage) { int channelCount = montage.getMontageChannelCount(); int filterCount = montage.getSampleFilterCount(); SampleFilterDefinition[] definitions = new SampleFilterDefinition[filterCount]; LinkedList<SinglechannelSampleFilterEngine> chain; FFTSampleFilter fftFilter; FFTSampleFilter[] summaryFFTFilters = new FFTSampleFilter[channelCount]; Iterator<Range> rangeIterator; Range range; SampleSource input;//input for the next filter in the list int i, e; for (i = 0; i < filterCount; i++) { definitions[i] = montage.getSampleFilterAt(i); if (definitions[i] instanceof FFTSampleFilter) { fftFilter = (FFTSampleFilter) definitions[i]; rangeIterator = fftFilter.getRangeIterator(); while (rangeIterator.hasNext()) { range = rangeIterator.next(); for (e = 0; e < channelCount; e++) { if (!montage.isFilteringExcluded(i, e)) { if (summaryFFTFilters[e] == null) summaryFFTFilters[e] = new FFTSampleFilter(true); summaryFFTFilters[e].setRange(range, true); } } } } } for (e = 0; e < channelCount; e++) { if (summaryFFTFilters[e] != null) { chain = chains.get(e); if (chain.isEmpty()) input = new ChannelSelectorSampleSource(source, e); else input = chain.getLast(); addFilter(new FFTSinglechannelSampleFilter(input, summaryFFTFilters[e]), e); } } } /** * Clears the filter {@link SinglechannelSampleFilterEngine engines} and initializes * them creating {@link SinglechannelSampleFilterEngine filter engines} * based on {@link SampleFilterDefinition sample filters} (both FFT and Time Domain) from a given montage. * @param montage the montage used to create new engines * @throws MontageMismatchException never thrown */ protected synchronized void applyMontage(Montage montage) throws MontageMismatchException { try { semaphore.acquire(); reinitFilterChains(); if (!montage.isFilteringEnabled()) { return; } importTimeDomainFiltersFromMontage(montage); importFFTSampleFiltersFromMontage(montage); newMontage = true; } catch (InterruptedException ex) { java.util.logging.Logger.getLogger(MultichannelSampleFilter.class.getName()).log(Level.SEVERE, null, ex); } finally { semaphore.release(); } if (!(originalSource instanceof ChangeableMultichannelSampleSource)) { updateTimeDomainSampleFilterEnginesCache(0); } } }