/* MultichannelSampleMontage.java created 2007-09-24
*
*/
package org.signalml.domain.signal;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import org.apache.log4j.Logger;
import org.signalml.domain.montage.system.ChannelType;
import org.signalml.domain.montage.Montage;
import org.signalml.domain.montage.MontageChannel;
import org.signalml.domain.montage.MontageMismatchException;
import org.signalml.domain.montage.SourceChannel;
import org.signalml.domain.montage.system.ChannelFunction;
import org.signalml.domain.montage.SignalConfigurer;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.exception.SanityCheckException;
/**
* This class represents a source of samples for a {@link Montage montage}.
* Using the given montage and source of samples combines (adds with coefficients) different
* {@link SourceChannel source channels} to provide desired output.
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class MultichannelSampleMontage extends MultichannelSampleProcessor {
protected static final Logger logger = Logger.getLogger(MultichannelSampleMontage.class);
private static final String MATRIX_PROPERTY = "matrix";
/**
* an array of entries holding information about
* {@link MontageChannel montage channels}. Indexes in the array are the
* same as indexes of montage channels in the {@link Montage montage}.
*/
private Entry[] montage;
/**
* a 2D array associating {@link SourceChannel source channels}
* (their indexes) with {@link MontageChannel montage channels} for
* which they are primary channels.
* <code>reverseMontage[i]</code> - an array of indexes of montage
* channels which have source channel of index <code>i</code> as primary
* channel.
*/
private int[][] reverseMontage;
/**
* an array of coefficients (references) between
* {@link MontageChannel montage channels} and
* {@link SourceChannel source channels}.
* <code>matrixData[i][j]</code> the coefficient (reference) between
* montage channel of index <code>i</code> and the source channel
* of index <code>j</code>.
*/
private float[][] matrixData;
/**
* an auxiliary array to hold samples of source channels
*/
private double[] auxSamples;
/**
* the montage used to set parameters of this source
*/
private Montage currentMontage = null;
/**
* an array associating montage channels with the number of samples
* in them
*/
private int[] sampleCounts;
/**
* Constructor. Creates a source of montage samples for the signal of
* given {@link SignalType type} based on a given {@link Montage montage}
* (to set all attributes) and a given source of samples.
* @param signalType the type of a signal
* @param source the source of (source) samples
* @param montage the montage used to set all attributes of this
* sample source
* @throws MontageMismatchException thrown when the number of channels
* in <code>source</code> is different then the number of source
* channels in the montage.
*/
public MultichannelSampleMontage(MultichannelSampleSource source, Montage montage) throws MontageMismatchException {
super(source);
setCurrentMontage(montage);
}
/**
* Constructor. Creates a source of montage samples for the signal of
* given {@link SignalType type} based on a given source of samples.
* As a montage empty montage for a given type of signal is used.
* @param signalType the type of a signal
* @param source the source of (source) samples
* @throws SanityCheckException must not happen, means error in code.
*/
public MultichannelSampleMontage(MultichannelSampleSource source) {
super(source);
this.currentMontage = getFailsafeMontage();
try {
applyMontage(this.currentMontage);
} catch (MontageMismatchException ex) {
throw new SanityCheckException("Applying fail safe montage must not fail");
}
}
/**
* Creates an empty montage for a given type of signal (attribute
* <code>signalType</code>
* @return an empty montage for a given type of signal.
*/
private Montage getFailsafeMontage() {
return SignalConfigurer.createMontage(source.getChannelCount());
}
/**
* Returns the number of channels in this source.
* @return the number of channels in this source.
*/
@Override
public int getChannelCount() {
return montage.length;
}
/**
* Fires this source that the montage has changed. Updates attributes
* from the (changed) montage.
* @param evt an event object describing the change
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
try {
setCurrentMontage(currentMontage);
} catch (MontageMismatchException ex) {
logger.debug("Exception after property change", ex);
}
super.propertyChange(evt);
}
@Override
public int getDocumentChannelIndex(int channel) {
return source.getDocumentChannelIndex(montage[channel].primaryChannel);
}
/**
* Returns indexes of {@link MontageChannel montage channels} in which
* the given {@link SourceChannel source channel} is the primary channel.
* @param channel the index of the source channel
* @return an array of indexes of montage channels in which
* the given source channel is the primary channel.
*/
public int[] getMontageChannelIndices(int channel) {
return reverseMontage[channel];
}
/**
* Returns the label of a {@link MontageChannel montage channel} of
* a given index.
* If there is no label of a montage channel, label of primary
* channel is used.
* @param channel the index of the montage channel
* @return the label of a montage channel of a given index.
*/
@Override
public String getLabel(int channel) {
Entry me = montage[channel];
if (me.label != null) {
return me.label;
}
return source.getLabel(me.primaryChannel);
}
/**
* For the given {@link MontageChannel montage channel} returns
* the label of its primary channel.
* @param channel the index of the montage channel
* @return the label of the primary channel
*/
public String getPrimaryLabel(int channel) {
Entry me = montage[channel];
if (me.primaryLabel != null) {
return me.primaryLabel;
}
return source.getLabel(me.primaryChannel);
}
/**
* Returns an array of labels of {@link MontageChannel montage channels}.
* If there is no label of any montage channel, label of primary
* channel is used.
* @return an array of labels of montage channels
*/
public String[] getLabels() {
int cnt = getChannelCount();
String[] labels = new String[cnt];
for (int i=0; i<cnt; i++) {
labels[i] = getLabel(i);
}
return labels;
}
/**
* Returns an array of labels of primary channels (ordered by indexes
* of {@link MontageChannel montage channels}).
* @return an array of labels of primary channels
*/
public String[] getPrimaryLabels() {
int cnt = getChannelCount();
String[] labels = new String[cnt];
for (int i=0; i<cnt; i++) {
labels[i] = getPrimaryLabel(i);
}
return labels;
}
@Override
public int getSampleCount(int channel) {
return sampleCounts[channel];
}
/**
* Returns the given number of samples for a given
* {@link MontageChannel montage channel} starting from a given position
* in time.
* @param channel the number of the montage 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
*/
@Override
public void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) {
int channelCnt = this.currentMontage.getSourceChannelCount();
int primaryChannel = montage[channel].primaryChannel;
int i, e;
//In case we have 'fake' primaryChannel (eg. in montage there is a channel added by 'add empty channel'
//we need to immitate source samples as 0
if (currentMontage.getSourceChannelAt(primaryChannel).getFunction() == ChannelFunction.ZERO) {
this.fillSamplesWith((double) 0.0, target, signalOffset, count, arrayOffset);
} else if (currentMontage.getSourceChannelAt(primaryChannel).getFunction() == ChannelFunction.ONE) {
this.fillSamplesWith((double) 1.0, target, signalOffset, count, arrayOffset);
} else {
source.getSamples(primaryChannel, target, signalOffset, count, arrayOffset);
}
float coeff = matrixData[channel][primaryChannel];
int idx;
if (coeff != 1) {
idx = arrayOffset;
for (e=0; e<count; e++) {
target[idx] *= coeff;
idx++;
}
}
for (i=0; i<channelCnt; i++) {
if (i == primaryChannel) {
continue;
}
coeff = matrixData[channel][i];
if (coeff != 0) {
if (auxSamples == null || auxSamples.length < count) {
auxSamples = new double[count];
}
if (currentMontage.getSourceChannelAt(i).getFunction() == ChannelFunction.ZERO)
this.fillSamplesWith((double) 0.0, auxSamples, signalOffset, count, 0);
else if (currentMontage.getSourceChannelAt(i).getFunction() == ChannelFunction.ONE)
this.fillSamplesWith((double) 1.0, auxSamples, signalOffset, count, 0);
else
source.getSamples(i, auxSamples, signalOffset, count, 0);
idx = arrayOffset;
if (coeff != 1) {
for (e=0; e<count; e++) {
target[idx] += auxSamples[e] * coeff;
idx++;
}
} else {
for (e=0; e<count; e++) {
target[idx] += auxSamples[e];
idx++;
}
}
}
}
}
protected void fillSamplesWith(double value, double[] target, int signalOffset, int count, int arrayOffset) {
for (int i=arrayOffset; i<arrayOffset+count; i++)
target[i] = value;
}
/**
* Returns an array of entries holding information about
* {@link MontageChannel montage channels}. Indexes in the array are the
* same as indexes of montage channels in the {@link Montage montage}.
* @return an array of entries with information about montage channels
*/
public Entry[] getMontage() {
return montage;
}
/**
* Returns the montage associated with this sample source
* @return the montage associated with this sample source
*/
public Montage getCurrentMontage() {
return currentMontage;
}
/**
* Sets the {@link Montage montage} to be associated with this sample
* source and uses it to set attributes of this sample source.
* @param currentMontage the montage to be used
* @throws MontageMismatchException thrown if the number of channels in
* the <code>source</code> is different then the number of source
* channels in the given montage.
*/
public void setCurrentMontage(Montage currentMontage) throws MontageMismatchException {
try {
applyMontage(currentMontage);
} catch (MontageMismatchException ex) {
logger.error("Failed to apply montage", ex);
this.currentMontage = getFailsafeMontage();
applyMontage(this.currentMontage);
throw ex;
}
this.currentMontage = currentMontage;
}
/**
* Returns a 2D array associating {@link SourceChannel source channels}
* (their indexes) with {@link MontageChannel montage channels} for
* which they are primary channels
* @return a 2D array associating source channels with montage channels
* for which they are primary channels
*/
public int[][] getReverseMontage() {
return reverseMontage;
}
/**
* Returns an array of coefficients (references) between
* {@link MontageChannel montage channels} and
* {@link SourceChannel source channels}.
* <code>matrixData[i][j]</code> the coefficient (reference) between
* montage channel of index <code>i</code> and the source channel
* of index <code>j</code>.
* @return an array of coefficients between montage channels and
* source channels
*/
public float[][] getMatrixData() {
return matrixData;
}
/**
* Sets the attributes of this source to the values taken from a given
* montage
* @param montage the montage to be used
* @throws MontageMismatchException thrown if the number of channels in
* the <code>source</code> is different then the number of source
* channels in the given montage
*/
private void applyMontage(Montage montage) throws MontageMismatchException {
int srcCnt = montage.getSourceChannelCount();
/*if (srcCnt != source.getChannelCount()) {
throw new MontageMismatchException("error.badChannelCount");
}*/
int cnt = montage.getMontageChannelCount();
int i;
int e;
int sampleCount;
Entry[] entries = new Entry[cnt];
float[][] matrix = new float[cnt][];
int[] sampleCounts = new int[cnt];
ArrayList<LinkedList<Integer>> backRefs = new ArrayList<LinkedList<Integer>>(srcCnt);
for (i=0; i<srcCnt; i++) {
backRefs.add(new LinkedList<Integer>());
}
LinkedList<Integer> channelBackRefs;
int primaryChannel;
for (i=0; i<cnt; i++) {
primaryChannel = montage.getMontagePrimaryChannelAt(i);
channelBackRefs = backRefs.get(primaryChannel);
channelBackRefs.add(i);
entries[i] = new Entry(primaryChannel, montage.getSourceChannelLabelAt(primaryChannel), montage.getMontageChannelLabelAt(i));
matrix[i] = montage.getReferenceAsFloat(i);
sampleCounts[i] = source.getSampleCount(primaryChannel);
// compensate for possible shorter reference
for (e=0; e<matrix[i].length; e++) {
if (e != primaryChannel && matrix[i][e] != 0) {
sampleCount = source.getSampleCount(e);
if (sampleCount < sampleCounts[i]) {
sampleCounts[i] = sampleCount;
}
}
}
}
int[][] backRefArr = new int[srcCnt][];
int[] channelBackRefArr;
Iterator<Integer> it;
for (i=0; i<srcCnt; i++) {
channelBackRefs = backRefs.get(i);
channelBackRefArr = new int[channelBackRefs.size()];
e = 0;
it = channelBackRefs.iterator();
while (it.hasNext()) {
channelBackRefArr[e] = it.next();
e++;
}
backRefArr[i] = channelBackRefArr;
}
int oldCount = (this.montage != null ? this.montage.length : 0);
this.montage = entries;
this.reverseMontage = backRefArr;
pcSupport.firePropertyChange(CHANNEL_COUNT_PROPERTY, oldCount, entries.length);
float[][] oldMatrixData = this.matrixData;
this.matrixData = matrix;
this.sampleCounts = sampleCounts;
pcSupport.firePropertyChange(MATRIX_PROPERTY, oldMatrixData, matrixData);
}
/**
* For a given {@link MontageChannel montage channel} holds:
* 1. the number of {@link SourceChannel primary channel} associated with
* this montage channel
* 2. the label of a source channel
* 3. the label of this montage channel
*/
private class Entry {
/**
* the number of a {@link SourceChannel primary channel}
* associated with this montage channel
*/
private int primaryChannel;
/**
* the label of a {@link SourceChannel primary channel}
*/
private String primaryLabel;
/**
* the label of this montage channel
*/
private String label;
/**
* Constructor. Creates an entry with given informations.
* @param sourceChannel the number of a
* {@link SourceChannel primary channel} associated with this
* montage channel
* @param primaryLabel the label of a
* {@link SourceChannel primary channel}
* @param label the label of this montage channel
*/
public Entry(int sourceChannel, String primaryLabel, String label) {
this.primaryChannel = sourceChannel;
this.primaryLabel = primaryLabel;
this.label = label;
}
}
}