/* MontageChannel.java created 2007-10-23
*
*/
package org.signalml.domain.montage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Set;
import java.util.Map.Entry;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* This class represents a channel of a {@link Montage montage}.
* Montage channel is a difference between the voltage of the electrode
* and voltage of some reference (may be another electrode or average of electrodes).
*
* This class is composed of one selected {@link SourceChannel source channel} and
* references to another channels.
* It contains also exclusions for {@link MontageSampleFilter filters}.
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
@XStreamAlias("montagechannel")
public class MontageChannel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* primary {@link SourceChannel soruce channel} to which the current
* object is related
*/
private SourceChannel primaryChannel;
/**
* label for this channel
*/
private String label;
/**
* a HashMap associating other {@link SourceChannel soruce channels}
* with Strings representing their reference to the primaryChannel
*/
private HashMap<SourceChannel,String> referenceMap;
private boolean excludeAllFilters = false;
private transient Boolean bipolarCache = null;
/**
* Constructor. Creates an empty MontageChannel.
*/
protected MontageChannel() {
}
/**
* Constructor. Creates MontageChannel with a given primary channel.
* No references are set.
* @param primaryChannel a {@link SourceChannel soruce channel}
* which will be primary for created object
*/
public MontageChannel(SourceChannel primaryChannel) {
this.primaryChannel = primaryChannel;
referenceMap = new HashMap<SourceChannel, String>();
}
/**
* Copy constructor.
* @param channel a MontageChannel object to be copied
* @param sourceChannels a list of source channels needed for creation
*/
public MontageChannel(MontageChannel channel, ArrayList<SourceChannel> sourceChannels) {
this.primaryChannel = sourceChannels.get(channel.primaryChannel.getChannel());
this.label = channel.label;
Set<Entry<SourceChannel, String>> entrySet = channel.referenceMap.entrySet();
this.referenceMap = new HashMap<SourceChannel, String>(entrySet.size());
for (Entry<SourceChannel, String> e : entrySet) {
this.referenceMap.put(sourceChannels.get(e.getKey().getChannel()), e.getValue());
}
this.excludeAllFilters = channel.excludeAllFilters;
}
/**
* Returns the primary channel for this montage channel.
* @return the primary channel for this montage channel
*/
public SourceChannel getPrimaryChannel() {
return primaryChannel;
}
/**
* Returns the label of this montage channel.
* @return the label of this montage channel
*/
public String getLabel() {
return label;
}
/**
* Sets a label to a given value.
* @param label String with a label to be set
*/
public void setLabel(String label) {
this.label = label;
}
/**
* Returns a reference to a given {@link SourceChannel source channel}.
* @param channel a SourceChannel to which reference is to be found
* @return the found reference
*/
public String getReference(SourceChannel channel) {
if (channel == primaryChannel) {
return "1";
}
return referenceMap.get(channel);
}
/**
* Sets reference to a given {@link SourceChannel source channel} to
* a given value.
* @param channel a SourceChannel to which reference is to be set
* @param value a value of reference to be set
* @throws NumberFormatException if String is not a valid
* reference (holds neither float value nor a fraction)
*/
public void setReference(SourceChannel channel, String value) throws NumberFormatException {
if (channel == primaryChannel) {
return;
}
bipolarCache = null;
if (value == null || value.isEmpty()) {
referenceMap.remove(channel);
} else {
// validate reference without saving the float result
parseReference(value);
referenceMap.put(channel, value);
}
}
/**
* Removes a reference to a given {@link SourceChannel source channel}.
* @param channel a source channel to which reference is to be removed
*/
public void removeReference(SourceChannel channel) {
bipolarCache = null;
referenceMap.remove(channel);
}
/**
* Checks if there is a reference to a given
* {@link SourceChannel source channel}.
* @param channel a source channel to which we are looking for
* a reference
* @return true if a reference to a given source channel exists,
* false otherwise
*/
public boolean hasReference(SourceChannel channel) {
return referenceMap.containsKey(channel);
}
/**
* Checks if a current object has any reference.
* @return true if current object has any reference, false otherwise
*/
public boolean hasReference() {
return !referenceMap.isEmpty();
}
/**
* Returns all references of this montage channel in the form of Strings.
* @param references all references of a current object
*/
public void getReferences(String[] references) {
Set<Entry<SourceChannel, String>> entrySet = referenceMap.entrySet();
Arrays.fill(references, null);
references[ primaryChannel.getChannel()] = "1";
for (Entry<SourceChannel, String> e : entrySet) {
references[ e.getKey().getChannel()] = e.getValue();
}
}
/**
* Returns all references of this montage channel in the form of floats.
* @param references an array in which references of a current object
* will be remembered
* @throws NumberFormatException if any String reference is
* not a valid reference (holds neither float value nor a fraction)
*/
public void getReferencesAsFloat(float[] references) throws NumberFormatException {
Set<Entry<SourceChannel, String>> entrySet = referenceMap.entrySet();
Arrays.fill(references, 0F);
references[ primaryChannel.getChannel()] = 1F;
for (Entry<SourceChannel, String> e : entrySet) {
references[ e.getKey().getChannel()] = parseReference(e.getValue());
}
}
/**
* Sets references of this montage channel to a given values.
* @param references an array of Strings representing references
* @param sourceChannels a list of source channels
* @throws NumberFormatException thrown when any String reference
* is not a valid reference (holds neither float value nor a fraction)
*/
public void setReferences(String[] references, ArrayList<SourceChannel> sourceChannels) throws NumberFormatException {
int primaryIndex = primaryChannel.getChannel();
int i;
for (i=0; i<references.length; i++) {
// validate reference without saving the result
parseReference(references[i]);
}
bipolarCache = null;
referenceMap.clear();
for (i=0; i<references.length; i++) {
if (references[i] == null || references[i].isEmpty()) {
continue;
}
referenceMap.put(sourceChannels.get(i), references[i]);
}
}
/**
* Parses a String with a reference and returns reference in the
* form of a float.
* @param ref a String to be parsed
* @return float a value of a reference
* @throws NumberFormatException thrown when String is not
* a valid reference (holds neither float value nor a fraction)
*/
public static float parseReference(String ref) throws NumberFormatException {
if (ref == null) {
return(0F);
}
String item = ref.trim();
if (item.isEmpty()) {
return(0F);
}
String[] parts;
int hi, lo;
if (item.contains("/")) {
parts = item.split("\\s*/\\s*");
if (parts == null || parts.length != 2) {
throw new NumberFormatException("error.badReferenceValue");
}
hi = Integer.parseInt(parts[0]);
lo = Integer.parseInt(parts[1]);
if (lo == 0) {
throw new NumberFormatException("error.badReferenceValue");
}
return (((float) hi) / lo);
} else {
try {
return Float.parseFloat(item);
} catch (NumberFormatException ex) {
throw new NumberFormatException("error.badReferenceValue");
}
}
}
/**
* Checks if a String is a valid reference (holds float value or
* a fraction).
* @param ref a String to be checked
* @return true if a String is a valid reference, false otherwise
*/
public static boolean isCorrectReference(String ref) {
try {
parseReference(ref);
} catch (NumberFormatException ex) {
return false;
}
return true;
}
/**
* Returns if this montage channel has a bipolar reference
* (channel without references is bipolar).
* @return true if this montage channel has a bipolar reference,
* false otherwise
*/
public boolean isBipolarReference() {
if (bipolarCache == null) {
int size = referenceMap.size();
if (size == 0) {
bipolarCache = true;
}
else if (size > 1) {
bipolarCache = false;
}
else if (!referenceMap.entrySet().iterator().next().getValue().equals("-1")) {
bipolarCache = false;
} else {
bipolarCache = true;
}
}
return bipolarCache.booleanValue();
}
/**
* Checks if the reference to a given
* {@link SourceChannel source channel} is symmetric.
* @param channel a SourceChannel object
* @return true if the reference is symmetric, false otherwise
*/
//TODO to dla size 2 będzie symetryczne przy reference "-1/2", ale nie będzie przy "-0.5". Czy tak ma być?
public boolean isSymmetricWeight(SourceChannel channel) {
if (channel == primaryChannel) {
return false;
}
int size = referenceMap.size();
String ref = referenceMap.get(channel);
if (ref == null) {
return false;
}
return ref.equals("-1/" + size);
}
/**
* Checks if a given montage channel has the same references as
* this montage channel.
* @param channel a MontageChannel to be compared to the current object
* @param sourceChannels a list of source channels
* @return true if a given montage channel has the same references as
* this montage channel, false otherwise
*/
public boolean isEqualReference(MontageChannel channel, ArrayList<SourceChannel> sourceChannels) {
int size = referenceMap.size();
if (size != channel.referenceMap.size()) {
return false;
}
Set<Entry<SourceChannel,String>> entrySet = referenceMap.entrySet();
String other;
for (Entry<SourceChannel, String> entry : entrySet) {
other = channel.referenceMap.get(sourceChannels.get(entry.getKey().getChannel()));
if (other == null || !other.equals(entry.getValue())) {
return false;
}
}
return true;
}
/**
* Returns if all filters are excluded.
* @return true if all filters are excluded, false otherwise
*/
public boolean isExcludeAllFilters() {
return excludeAllFilters;
}
/**
* Set excludeAllFilters parameter to a given value.
* @param excludeAllFilters a value to be set
*/
public void setExcludeAllFilters(boolean excludeAllFilters) {
this.excludeAllFilters = excludeAllFilters;
}
}