package org.signalml.app.document; import static org.signalml.app.util.i18n.SvarogI18n._; import java.beans.IntrospectionException; import java.beans.PropertyChangeListener; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.apache.log4j.Logger; import org.signalml.app.document.signal.AbstractSignal; import org.signalml.app.document.signal.SignalChecksumProgressMonitor; import org.signalml.app.model.components.LabelledPropertyDescriptor; import org.signalml.app.model.document.opensignal.ExperimentDescriptor; import org.signalml.app.model.monitor.MonitorRecordingDescriptor; import org.signalml.app.view.signal.SignalPlot; import org.signalml.app.view.signal.SignalView; import org.signalml.app.worker.monitor.DisconnectFromExperimentWorker; import org.signalml.app.worker.monitor.MonitorWorker; import org.signalml.app.worker.monitor.SignalRecorderWorker; import org.signalml.app.worker.monitor.TagRecorder; import org.signalml.domain.montage.MontageMismatchException; import org.signalml.domain.signal.SignalChecksum; import org.signalml.domain.signal.SignalProcessingChain; import org.signalml.domain.signal.samplesource.RoundBufferMultichannelSampleSource; import org.signalml.domain.tag.StyledMonitorTagSet; import org.signalml.plugin.export.SignalMLException; import org.signalml.plugin.export.view.DocumentView; /** * @author Mariusz Podsiadło * */ public class MonitorSignalDocument extends AbstractSignal implements MutableDocument { /** * A property describing whether this document is recording its signal. */ public static String IS_RECORDING_PROPERTY = "isRecording"; /** * A logger to save history of execution at. */ protected static final Logger logger = Logger.getLogger(MonitorSignalDocument.class); /** * The name of this document. */ private String name="Signal online"; /** * Describes the parameters of the monitor connected to this {@link MonitorSignalDocument}. */ private ExperimentDescriptor descriptor; /** * This worker is responsible for receiving the monitor signal and tags. */ private MonitorWorker monitorWorker; /** * This worker is responsible for recording the samples to a file. */ private SignalRecorderWorker signalRecorderWorker = null; /** * This worker is responsible for recording the tags to a file. */ private TagRecorder tagRecorderWorker = null; /** * Tag set to which the {@link MonitorWorker} saves tags. */ private StyledMonitorTagSet tagSet; /** * Whether the signal was saved. */ private boolean saved = true; /** * How often (in milliseconds) should {@link SignalPlot signal plots} be * refreshed by the {@link RefreshPlotsTimerTask}. */ private static long refreshPlotsInterval = 50; /** * A timer used to refresh {@link SignalPlot signal plots} after new samples * have been added. */ private Timer refreshPlotsTimer; public MonitorSignalDocument(ExperimentDescriptor descriptor) { super(); this.descriptor = descriptor; float freq = descriptor.getSignalParameters().getSamplingFrequency(); double ps = descriptor.getSignalParameters().getPageSize(); int sampleCount = (int) Math.ceil(ps * freq); sampleSource = new RoundBufferMultichannelSampleSource(descriptor.getSignalParameters().getChannelCount(), sampleCount); sampleSource.setCalibrationGain(descriptor.getSignalParameters().getCalibrationGain()); sampleSource.setCalibrationOffset(descriptor.getSignalParameters().getCalibrationOffset()); ((RoundBufferMultichannelSampleSource) sampleSource).setLabels(descriptor.getAmplifier().getSelectedChannelsLabels()); ((RoundBufferMultichannelSampleSource) sampleSource).setDocumentView(getDocumentView()); ((RoundBufferMultichannelSampleSource) sampleSource).setSamplingFrequency(freq); refreshPlotsTimer = new Timer(); refreshPlotsTimer.schedule(new RefreshPlotsTimerTask(), 0, refreshPlotsInterval); } public void setName(String name) { this.name = name; } public float[] getGain() { return descriptor.getSignalParameters().getCalibrationGain(); } public float[] getOffset() { return descriptor.getSignalParameters().getCalibrationOffset(); } @Override public void setDocumentView(DocumentView documentView) { super.setDocumentView(documentView); if (documentView != null) { for (Iterator<SignalPlot> i = ((SignalView) documentView).getPlots().iterator(); i.hasNext();) { SignalPlot signalPlot = i.next(); SignalProcessingChain signalChain = SignalProcessingChain.createNotBufferedFilteredChain(sampleSource); try { signalChain.applyMontageDefinition(this.getMontage()); } catch (MontageMismatchException ex) { logger.error("Failed to apply montage to the Signal Chain.", ex); } signalPlot.setSignalChain(signalChain); } } if (sampleSource != null && sampleSource instanceof RoundBufferMultichannelSampleSource) { ((RoundBufferMultichannelSampleSource) sampleSource).setDocumentView(documentView); } } @Override public void openDocument() throws SignalMLException, IOException { setSaved(true); if (descriptor.getJmxClient() == null) { throw new IOException(); } logger.info("Start initializing monitor data."); tagSet = new StyledMonitorTagSet(descriptor.getSignalParameters().getPageSize(), 5, descriptor.getSignalParameters().getSamplingFrequency()); if (descriptor.getTagStyles() != null) { tagSet.copyStylesFrom(descriptor.getTagStyles()); } TagDocument tagDoc = new TagDocument(tagSet); tagDoc.setParent(this); monitorWorker = new MonitorWorker(descriptor, (RoundBufferMultichannelSampleSource) sampleSource, tagSet); monitorWorker.execute(); logger.info("Monitor executed."); } @Override public void closeDocument() throws SignalMLException { refreshPlotsTimer.cancel(); //stop recording try { stopMonitorRecording(); } catch (IOException ex) { logger.error("Cannot stop monitor recording for this monitor.", ex); } //stop monitor worker if (monitorWorker != null && !monitorWorker.isCancelled()) { monitorWorker.cancel(false); do { try { Thread.sleep(1); } catch (InterruptedException e) {} } while (!monitorWorker.isFinished()); monitorWorker = null; } tagSet.stopTagsRemoving(); //disconnect from Jmx DisconnectFromExperimentWorker worker = new DisconnectFromExperimentWorker(descriptor); worker.execute(); while (!worker.isDone()) ; //close document super.closeDocument(); } @Override public int getBlockCount() { return 1; } @Override public float getBlockSize() { return getPageSize(); } @Override public int getChannelCount() { return sampleSource.getChannelCount(); } @Override public SignalChecksum[] getChecksums(String[] types, SignalChecksumProgressMonitor monitor) throws SignalMLException { return null; } @Override public String getFormatName() { return null; } @Override public float getMaxSignalLength() { return sampleSource.getSampleCount(0); } @Override public float getMinSignalLength() { return sampleSource.getSampleCount(0); } @Override public int getPageCount() { return 1; } @Override public float getPageSize() { return pageSize; } @Override public void setPageSize(float pageSize) { if (this.pageSize != pageSize) { float last = this.pageSize; this.pageSize = pageSize; this.blockSize = pageSize; pcSupport.firePropertyChange(PAGE_SIZE_PROPERTY, last, pageSize); } } @Override public int getBlocksPerPage() { return 1; } @Override public void setBlocksPerPage(int blocksPerPage) { if (blocksPerPage != 1) throw new IllegalArgumentException(); this.blocksPerPage = 1; } /** * Returns whether this monitor document is recording the signal (and optionally tags) * to a file. * * @return true, if this {@link MonitorSignalDocument} is recording the signal (and optionally * tags) it monitors, false otherwise */ public boolean isRecording() { if (signalRecorderWorker != null) return true; return false; } @Override public String getName() { return name; } @Override public boolean isSaved() { return saved; } @Override public void setSaved(boolean saved) { if (this.saved != saved) { this.saved = saved; pcSupport.firePropertyChange(AbstractMutableFileDocument.SAVED_PROPERTY, !saved, saved); } } public void invalidate() { setSaved(false); } @Override public final void saveDocument() throws SignalMLException, IOException { throw new UnsupportedOperationException("Saving monitor document is not supported - use monitor recording instead."); } @Override public void newDocument() throws SignalMLException { } /** * Starts to record this monitor document samples and tags according * to the configuration (file names etc.) maintained in the * {@link ExperimentDescriptor} related to this MonitorSignalDocument * (this configuration can be changed by changing this object: * {@link MonitorSignalDocument#getExperimentDescriptor()}. * After calling {@link MonitorSignalDocument#stopMonitorRecording()} the data * will be written to the given files. * * @throws FileNotFoundException thrown when the files for buffering, signal and tag recording are not found */ public void startMonitorRecording() throws FileNotFoundException { MonitorRecordingDescriptor monitorRecordingDescriptor = descriptor.getMonitorRecordingDescriptor(); boolean tagsRecordingEnabled = monitorRecordingDescriptor.isTagsRecordingEnabled(); // starting signal recorder String dataPath = monitorRecordingDescriptor.getSignalRecordingFilePath(); signalRecorderWorker = new SignalRecorderWorker(dataPath, descriptor); // if tags recording is enabled: starting tag recorder and connecting it // to signal recorder if (tagsRecordingEnabled) { String tagsPath = monitorRecordingDescriptor.getTagsRecordingFilePath(); tagRecorderWorker = new TagRecorder(tagsPath); signalRecorderWorker.setTagRecorder(tagRecorderWorker); } // connecting recorders to the monitor worker monitorWorker.connectSignalRecorderWorker(signalRecorderWorker); monitorWorker.connectTagRecorderWorker(tagRecorderWorker); pcSupport.firePropertyChange(IS_RECORDING_PROPERTY, false, true); } /** * Stops to record data and writes the recorded data to the files set * in the {@link ExperimentDescriptor}. * * @throws IOException thrown where an error while writing the recorded * data to the given files occurs */ public void stopMonitorRecording() throws IOException { //stops the monitorWorker from sending more samples and tags to the recorders monitorWorker.disconnectTagRecorderWorker(); monitorWorker.disconnectSignalRecorderWorker(); if (signalRecorderWorker != null) { signalRecorderWorker.save(); } if (tagRecorderWorker != null) { tagRecorderWorker.save(); } tagRecorderWorker = null; signalRecorderWorker = null; pcSupport.firePropertyChange(IS_RECORDING_PROPERTY, true, false); } /** * Returns a list of properties that {@link PropertyChangeListener * PropertyChangeListeners} can handle. * * @return a list of properties * @throws IntrospectionException if an exception occurs during introspection */ @Override public List<LabelledPropertyDescriptor> getPropertyList() throws IntrospectionException { List<LabelledPropertyDescriptor> list = super.getPropertyList(); list.add(new LabelledPropertyDescriptor(_("is recording"), IS_RECORDING_PROPERTY, MonitorSignalDocument.class, "isRecording", null)); return list; } /** * Returns an {@link ExperimentDescriptor} which describes the open monitor * associated with this {@link MonitorSignalDocument}. * * @return an {@link ExperimentDescriptor} associated with this {@link * MonitorSignalDocument}. */ public ExperimentDescriptor getExperimentDescriptor() { return descriptor; } class RefreshPlotsTimerTask extends TimerTask { @Override public void run() { //logger.debug("refreshig plot - curent timestamp: " + System.currentTimeMillis()); if (documentView != null && ((SignalView) documentView).getPlots() != null) { for (Iterator<SignalPlot> i = ((SignalView) documentView).getPlots().iterator(); i.hasNext();) i.next().repaint(); } } } public MonitorWorker getMonitorWorker() { return monitorWorker; } }