/* SignalMLCodecSampleSource.java created 2007-09-24 * */ package org.signalml.domain.signal.samplesource; import java.util.Arrays; import org.apache.log4j.Logger; import org.signalml.codec.SignalMLCodecException; import org.signalml.codec.SignalMLCodecReader; import org.signalml.domain.signal.MultichannelSignalResampler; import org.signalml.domain.signal.NaiveMultichannelSignalResampler; import org.signalml.plugin.export.SignalMLException; import org.signalml.plugin.export.change.listeners.PluginSignalChangeListener; import org.signalml.util.Util; /** * This class represents the source of samples from the signal described * in the SignalML markup language. * * @see SignalMLCodecReader * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. */ public class SignalMLCodecSampleSource extends AbstractMultichannelSampleSource implements OriginalMultichannelSampleSource, ResamplableSampleSource { protected static final Logger logger = Logger.getLogger(SignalMLCodecSampleSource.class); /** * the {@link SignalMLCodecReader reader} that gets all informations * about the signal from file */ private SignalMLCodecReader reader = null; /** * the number of samples per second */ private float samplingFrequency; /** * the number of channels */ private int channelCount; /** * the calibration of the signal */ private float calibration; /** * the number of samples in the single channel */ private int sampleCount; /** * if this source is capable of returning a the number of samples * per second */ private boolean samplingFrequencyCapable = false; /** * if this source is capable of returning the number of channels */ private boolean channelCountCapable = false; /** * if this source is capable of returning a calibration */ private boolean calibrationCapable = false; /** * true if frequency of sampling is equal for all channels, false * otherwise */ private boolean uniformSampling = true; /** * an array containing for each channel the number of samples per second */ private float[] channelSampling; /** * an array of labels of channels */ private String[] labels; /** * informs whether the sampling frequency has changed */ private boolean samplingFrequencyExternal = false; /** * informs whether the channel count has changed */ private boolean channelCountExternal = false; /** * informs whether the calibration has changed */ private boolean calibrationExternal = false; /** * the {@link MultichannelSignalResampler resampler} of the signal */ private MultichannelSignalResampler resampler = null; /** * Constructor. Creates the source of samples based on a given * {@link SignalMLCodecReader reader}. * @param reader the reader that gets all informations about signal * from file * @throws SignalMLException if codec doesn't support max offset, so * is unusable */ public SignalMLCodecSampleSource(SignalMLCodecReader reader) throws SignalMLException { super(); this.reader = reader; try { sampleCount = reader.get_max_offset() + 1; } catch (SignalMLCodecException ex) { logger.error("Codec doesn't support max offset - unusable"); throw ex; } try { channelCountCapable = reader.is_number_of_channels(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support is_number_of_channels, left false"); logger.debug("Caught exception was", e); channelCountCapable = false; } if (channelCountCapable) { try { channelCount = reader.get_number_of_channels(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support channel count, assumed 1"); logger.debug("Caught exception was", e); channelCount = 1; channelCountCapable = false; } } try { samplingFrequencyCapable = reader.is_sampling_frequency(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support is_sampling_frequency, left false"); logger.debug("Caught exception was", e); } if (samplingFrequencyCapable) { try { uniformSampling = reader.is_uniform_sampling_frequency(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support uniform sampling frequency info, left true"); logger.debug("Caught exception was", e); } if (!uniformSampling) { logger.warn("WARNING: signal sampling is not uniform. Naive upsampling will be used for select channels"); resampler = new NaiveMultichannelSignalResampler(); collectChannelSampling(); } else { try { samplingFrequency = reader.get_sampling_frequency(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support sampling frequency, left null"); logger.debug("Caught exception was", e); } } } try { this.calibrationCapable = reader.is_calibration(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support is_callibration, assumed false"); logger.debug("Caught exception was", e); } readLabels(); logger.info("sampleCount = " + sampleCount); logger.info("samplingFrequency = " + samplingFrequency); } /** * Reads the frequency of sampling (number of samples per second) for * each channel from file. */ private void collectChannelSampling() { channelSampling = new float[channelCount]; for (int i=0; i<channelCount; i++) { try { channelSampling[i] = reader.get_sampling_frequency(i); if (channelSampling[i] > samplingFrequency) { samplingFrequency = channelSampling[i]; } } catch (SignalMLCodecException e) { logger.warn("WARNING: codec didn't return channel sampling for channel [" + i + "] - assumed default"); logger.debug("Caught exception was", e); channelSampling[i] = 0; } } for (int i=0; i<channelCount; i++) { if (channelSampling[i] == 0) { channelSampling[i] = samplingFrequency; } } } /** * Reads the labels of channels from file. If some labels are missing * the default labels are used. */ private void readLabels() { labels = null; boolean hasNames = false; try { hasNames = reader.is_channel_names(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support is_channel_names, assumed false"); logger.debug("Caught exception was", e); } if (hasNames) { try { labels = reader.get_channel_names(); } catch (SignalMLCodecException e) { logger.warn("WARNING: codec doesn't support get_channel_names"); logger.debug("Caught exception was", e); } } if (labels == null) { return; } int i; for (i=0; i<labels.length; i++) { labels[i] = Util.trimString(labels[i]); if (labels[i] == null || labels[i].length() == 0) { labels[i] = "L" + i; } } if (labels.length < channelCount) { logger.debug("some labels missing"); String[] newLabels = new String[channelCount]; for (i=0; i<labels.length; i++) { newLabels[i] = labels[i]; } for (; i<channelCount; i++) { newLabels[i] = "L" + i; } labels = newLabels; } } /** * Returns the {@link SignalMLCodecReader reader} that gets all * informations about the signal from file * @return the reader that gets all informations about the signal * from file */ public SignalMLCodecReader getReader() { return reader; } @Override public float getSamplingFrequency() { return samplingFrequency; } @Override public int getChannelCount() { return channelCount; } /** * Returns if this source is capable of returning a calibration * @return true if this source is capable of returning a * calibration, false otherwise */ @Override public boolean isCalibrationCapable() { return calibrationCapable; } /** * Returns if this source is capable of returning a sampling * frequency * @return true if this source is capable of returning a * sampling frequency, false otherwise */ @Override public boolean isSamplingFrequencyCapable() { return samplingFrequencyCapable; } /** * Returns if this source is capable of returning a channel count * @return true if this source is capable of returning a * channel count, false otherwise */ @Override public boolean isChannelCountCapable() { return channelCountCapable; } @Override public float[] getCalibrationGain() { throw new UnsupportedOperationException("Calling float[] getCalibrationGain is not supported for SignalMLCodecSampleSource."); } @Override public String getLabel(int channel) { if (channel < 0 || channel >= channelCount) { throw new IndexOutOfBoundsException("Bad channel number [" + channel + "]"); } if (labels == null) { labels = new String[channelCount]; for (int i=0; i<channelCount; i++) { labels[i] = "L" + (i+1); } } return labels[channel]; } @Override public int getDocumentChannelIndex(int channel) { return channel; } @Override public int getSampleCount(int channel) { return sampleCount; } /** * Returns the given number of samples for a given channel starting * from a given position in time. * If sampling frequency for the channel is different from default * the signal is {@link MultichannelSignalResampler resampled}. * @param channel the number of channel * @param target the array to which results will be written starting * from position <code>arrayOffset</code> * @param signalOffset the position (in time) in the signal starting * from which samples will be returned * @param count the number of samples to be returned * @param arrayOffset the offset in <code>target</code> array starting * from which samples will be written * @throws IndexOutOfBoundsException if channel of given index doesn't * exist * or some of the requested samples are not in the signal * or created result doesn't fit in the <code>target</code> array */ @Override public void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) { synchronized (this) { if (channel < 0 || channel >= channelCount) { throw new IndexOutOfBoundsException("Bad channel number [" + channel + "]"); } if ((signalOffset < 0) || ((signalOffset + count) > sampleCount)) { throw new IndexOutOfBoundsException("Signal range [" + signalOffset + ":" + count + "] doesn't fit in the signal"); } if ((arrayOffset < 0) || ((arrayOffset + count) > target.length)) { throw new IndexOutOfBoundsException("Target range [" + arrayOffset + ":" + count + "] doesn't fit in the target array"); } if (uniformSampling || channelSampling[channel] == samplingFrequency) { getRawSamples(channel, target, signalOffset, count, arrayOffset); } else { resampler.resample(this, channel, target, signalOffset, count, arrayOffset, samplingFrequency, channelSampling[channel]); } if (this.calibrationCapable) { float gain; try { gain = this.reader.get_calibration(channel); } catch (SignalMLCodecException e) { logger.error("get_calibration(channel) failed", e); try { gain = this.reader.get_calibration(); } catch (SignalMLCodecException e2) { logger.error("get_calibration() failed", e2); throw new RuntimeException(e); } } float offset = this.getCalibrationOffset()[channel]; //logger.debug(String.format("[%d] gain=%f offset=%f", channel, gain, offset)); for (int i=0; i<count; i++) { target[arrayOffset + i] *= gain; target[arrayOffset + i] += offset; } } } } @Override public void getRawSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) { synchronized (this) { int targetIndex = arrayOffset; int i = signalOffset; int limit = signalOffset + count; try { for (; i<limit; i++) { target[targetIndex] = reader.getChannelSample(i, channel); targetIndex++; } } catch (SignalMLCodecException ex) { logger.error("Failed to get sample, filling the rest with zero and exiting", ex); for (; i<limit; i++) { target[targetIndex] = 0.0F; targetIndex++; } return; } } } /** * Sets the calibration to the given value and tries to update * it in the file. * @param calibration the new value of calibration */ @Override public void setCalibrationGain(float calibration) { synchronized (this) { if (this.calibration != calibration) { float last = this.calibration; this.calibration = calibration; calibrationExternal = true; try { reader.set_calibration(calibration); } catch (SignalMLCodecException ex) { logger.error("Failed to propagate calibration to the codec", ex); } pcSupport.firePropertyChange(CALIBRATION_PROPERTY, new Float(last), new Float(calibration)); } } } @Override public void setSamplingFrequency(float samplingFrequency) { synchronized (this) { if (this.samplingFrequency != samplingFrequency) { float last = this.samplingFrequency; this.samplingFrequency = samplingFrequency; samplingFrequencyExternal = true; try { reader.set_sampling_frequency(samplingFrequency); } catch (SignalMLCodecException ex) { logger.error("Failed to propagate sampling frequency to the codec", ex); } if (!uniformSampling) { collectChannelSampling(); } pcSupport.firePropertyChange(SAMPLING_FREQUENCY_PROPERTY, new Float(last), new Float(samplingFrequency)); } } } @Override public void setChannelCount(int channelCount) { synchronized (this) { if (this.channelCount != channelCount) { int last = this.channelCount; this.channelCount = channelCount; channelCountExternal = true; try { reader.set_number_of_channels(channelCount); } catch (SignalMLCodecException ex) { logger.error("Failed to propagate channel count to the codec", ex); } readLabels(); if (!uniformSampling) { collectChannelSampling(); } pcSupport.firePropertyChange(CHANNEL_COUNT_PROPERTY, last, channelCount); } } } @Override public OriginalMultichannelSampleSource duplicate() throws SignalMLException { SignalMLCodecReader newReader = reader.getCodec().createReader(); newReader.open(reader.getCurrentFilename()); SignalMLCodecSampleSource duplicate = new SignalMLCodecSampleSource(newReader); if (channelCountExternal) { duplicate.setChannelCount(channelCount); } if (samplingFrequencyExternal) { duplicate.setSamplingFrequency(samplingFrequency); } if (calibrationExternal) { duplicate.setCalibrationGain(calibration); } return duplicate; } /** * Closes the file and removes the <code>reader</code> from this * source. */ @Override public void destroy() { reader.close(); reader = null; } @Override public boolean areIndividualChannelsCalibrationCapable() { return false; } @Override public void setCalibrationGain(float[] calibration) { throw new UnsupportedOperationException("Setting calibration gain for individual channels is not supported for the SignalMLCodecSampleSource"); } @Override public float getSingleCalibrationGain() { return calibration; } @Override public float[] getCalibrationOffset() { float[] calibrationOffset = new float[getChannelCount()]; Arrays.fill(calibrationOffset, 0); return calibrationOffset; } /** * Setting calibration offset is not supported for the * SignalMLCodecsSampleSource. * @param calibrationOffset */ @Override public void setCalibrationOffset(float[] calibrationOffset) { throw new UnsupportedOperationException("Not supported yet."); } /** * Setting calibration offset is not supported for the * SignalMLCodecsSampleSource. * @param calibrationOffset */ @Override public void setCalibrationOffset(float calibrationOffset) { throw new UnsupportedOperationException("Not supported yet."); } @Override public float getSingleCalibrationOffset() { return 0.0F; } @Override public void addSignalChangeListener(PluginSignalChangeListener listener) { //this sample source doesn't support signalchangelisteners. } }