/* MarkerSegmentedSignalSource.java created 2008-01-27 * */ package org.signalml.domain.signal.space; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.signalml.app.view.signal.SampleSourceUtils; import org.signalml.domain.montage.system.IChannelFunction; import org.signalml.domain.signal.MultichannelSampleProcessor; import org.signalml.domain.signal.SignalProcessingChain; import org.signalml.domain.signal.samplesource.MultichannelSampleSource; import org.signalml.domain.signal.samplesource.MultichannelSegmentedSampleSource; import org.signalml.domain.tag.StyledTagSet; import org.signalml.plugin.export.signal.SignalSelection; import org.signalml.plugin.export.signal.SignalSelectionType; import org.signalml.plugin.export.signal.Tag; import org.signalml.plugin.export.signal.TagStyle; /** * This class represents the {@link MultichannelSampleSource source} of samples * for the neighbourhood of the markers for the selected channels. * Contains the number of samples before and after the markers that should be available * and subset of channels that can be accessed. * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. */ public class MarkerSegmentedSampleSource extends MultichannelSampleProcessor implements MultichannelSegmentedSampleSource { /** * the number of samples in the segment */ private int segmentLength; /** * an array of the beginnings of segments */ private int[] offsets; /** * the number of channels in this source */ private int channelCount; /** * an array mapping indexes in this source to the indexes of channels * in the actual source */ private int[] channelIndices; /** * the number of segments that can not be used (the required * neighbourhood of the marker is not in the signal). */ private int unusableSegmentCount; /** * Number of segments which were rejected because artifact tags were found in them. */ private int artifactRejectedSegmentsCount; /** * the first sample relative to the marker that should be included in * the segment */ private int startSample; /** * the number of samples per second */ private float samplingFrequency; /** * Constructor. Creates a source without segments * @param source the actual source of samples */ protected MarkerSegmentedSampleSource(MultichannelSampleSource source) { super(source); } public MarkerSegmentedSampleSource(MultichannelSampleSource source, StyledTagSet tagSet, List<String> markerStyleNames, double startTime, double segmentLength, ChannelSpace channelSpace) { this(source, null, null, tagSet, markerStyleNames, new ArrayList<String>(), startTime, segmentLength, channelSpace); } /** * Constructor. Creates the source of samples based on the given * {@link MultichannelSampleSource source} of all channels. * @param source the source of all samples * @param tagSet the set of tags (including markers) * @param markerStyleNames the name of the type of a marker * @param startTime the segment start time relative to the marker position * (e.g. if the startTime is equal to 2, each segments begins with a sample * that is 2 seconds after the selected markers). * @param length the length of each segment * @param channelSpace the set of channels */ public MarkerSegmentedSampleSource(MultichannelSampleSource source, Double startMarkerSelection, Double endMarkerSelection, StyledTagSet tagSet, List<String> markerStyleNames, List<String> artifactStyleNames, double startTime, double length, ChannelSpace channelSpace) { super(source); if (channelSpace != null) { channelCount = channelSpace.size(); channelIndices = channelSpace.getSelectedChannels(); } else { // all channels channelCount = source.getChannelCount(); channelIndices = new int[channelCount]; for (int i=0; i<channelCount; i++) { channelIndices[i] = i; } } samplingFrequency = source.getSamplingFrequency(); startSample = convertTimeToSamples(startTime); this.segmentLength = convertTimeToSamples(length); List<TagStyle> tagStyles = getTagStyles(markerStyleNames, tagSet); List<TagStyle> artifactStyles = getTagStyles(artifactStyleNames, tagSet); int minSampleCount = SampleSourceUtils.getMinSampleCount(source); int startMarkerSelectionInSamples = startMarkerSelection == null ? 0 : convertTimeToSamples(startMarkerSelection); int endMarkerSelectionInSamples = endMarkerSelection == null ? minSampleCount : convertTimeToSamples(endMarkerSelection); int markerSample; int averagedCount = 0; int[] offsetArr = new int[tagSet.getTagCount()]; for (Tag tag: tagSet.getTags()) { if (tagStyles.contains(tag.getStyle())) { markerSample = (int) Math.floor(samplingFrequency * tag.getPosition()); if (markerSample + startSample < startMarkerSelectionInSamples) { // not enough samples before unusableSegmentCount++; continue; } if (endMarkerSelectionInSamples < markerSample + startSample + segmentLength) { // not enough samples after unusableSegmentCount++; continue; } if (overlapsWithArtifactTag(tagSet, tag, artifactStyles, startTime, length)) { artifactRejectedSegmentsCount++; continue; } // sample is ok offsetArr[averagedCount] = markerSample + startSample; averagedCount++; } } offsets = Arrays.copyOf(offsetArr, averagedCount); } protected boolean overlapsWithArtifactTag(StyledTagSet tagSet, Tag tag, List<TagStyle> artifactStyles, double startTime, double segmentLength) { SignalSelection averagingSelection = new SignalSelection(SignalSelectionType.CHANNEL, tag.getPosition() + startTime, segmentLength); for (Tag artTag: tagSet.getTags()) { if (artifactStyles.contains(artTag.getStyle())) { if (artTag.overlaps(averagingSelection)) return true; } } return false; } protected List<TagStyle> getTagStyles(List<String> styleNames, StyledTagSet tagSet) { List<TagStyle> tagStyles = new ArrayList<TagStyle>(); for (String styleName: styleNames) { TagStyle markerStyle = tagSet.getStyle(SignalSelectionType.CHANNEL, styleName); tagStyles.add(markerStyle); } return tagStyles; } /** * Constructor. Creates the source of samples based on the given * {@link MultichannelSampleSource source} of all channels an the given * {@link MarkerSegmentedSampleSourceDescriptor descriptor}. * @param source the source of samples for all channels * @param descriptor the descriptor of this source */ public MarkerSegmentedSampleSource(MultichannelSampleSource source, MarkerSegmentedSampleSourceDescriptor descriptor) { this(source); segmentLength = descriptor.getSegmentLength(); offsets = descriptor.getOffsets(); channelCount = descriptor.getChannelCount(); channelIndices = descriptor.getChannelIndices(); unusableSegmentCount = descriptor.getUnusableSegmentCount(); startSample = descriptor.getStartTime(); segmentLength = descriptor.getSegmentLength(); } /** * Creates the {@link MarkerSegmentedSampleSourceDescriptor descriptor} * of this source. * @return the descriptor of this source */ @Override public MarkerSegmentedSampleSourceDescriptor createDescriptor() { MarkerSegmentedSampleSourceDescriptor descriptor = new MarkerSegmentedSampleSourceDescriptor(); descriptor.setStartTime(startSample); descriptor.setSegmentLength(segmentLength); descriptor.setOffsets(offsets); descriptor.setChannelCount(channelCount); descriptor.setChannelIndices(channelIndices); descriptor.setUnusableSegmentCount(unusableSegmentCount); return descriptor; } @Override public double getSegmentTime(int segment) { return ((double) offsets[segment]) / samplingFrequency; } @Override public void getSamples(int channel, double[] target, int signalOffset, int count, int arrayOffset) { if (count == 0) { return; } int skippedSegments = signalOffset / segmentLength; int skippedSamples = signalOffset % segmentLength; int neededCount = count; int targetOffset = arrayOffset; if (skippedSamples > 0) { // get the rest of the first segment affected, if not at segment boundary int firstSegmentLeftover = Math.min(segmentLength-skippedSamples, neededCount); source.getSamples(channelIndices[channel], target, offsets[skippedSegments], firstSegmentLeftover, targetOffset); neededCount -= firstSegmentLeftover; targetOffset += firstSegmentLeftover; skippedSegments++; } int wholeSegments = neededCount / segmentLength; if (wholeSegments > 0) { for (int i=0; i<wholeSegments; i++) { source.getSamples(channelIndices[channel], target, offsets[skippedSegments+i], segmentLength, targetOffset); neededCount -= segmentLength; targetOffset += segmentLength; } } if (neededCount > 0) { source.getSamples(channelIndices[channel], target, offsets[skippedSegments+wholeSegments], neededCount, targetOffset); } } @Override public int getSegmentCount() { return offsets.length; } /** * Returns the segment length in samples */ public int getSegmentLengthInSamples() { return segmentLength; } @Override public int getDocumentChannelIndex(int channel) { return source.getDocumentChannelIndex(channelIndices[channel]); } @Override public int getChannelCount() { return channelCount; } @Override public int getSampleCount(int channel) { return (offsets.length * segmentLength); } @Override public void getSegmentSamples(int channel, double[] target, int segment) { source.getSamples(channelIndices[channel], target, offsets[segment], segmentLength, 0); } @Override public int getUnusableSegmentCount() { return unusableSegmentCount; } public int getArtifactRejectedSegmentsCount() { return artifactRejectedSegmentsCount; } @Override public String getLabel(int channel) { return super.getLabel(channelIndices[channel]); } public IChannelFunction getChannelFunction(int channel) { if (getSource() instanceof SignalProcessingChain) { SignalProcessingChain chain = (SignalProcessingChain) getSource(); if (chain.getMontage() == null) return null; else return chain.getMontage().getCurrentMontage().getSourceChannelFunctionAt(channelIndices[channel]); } else { return null; } } protected int convertTimeToSamples(double time) { return (int) Math.ceil(samplingFrequency * time); } }