/* ExportSignalAction.java created 2008-01-27
*
*/
package org.signalml.app.action.signal;
import static org.signalml.app.util.i18n.SvarogI18n._;
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import org.apache.log4j.Logger;
import org.signalml.app.action.AbstractFocusableSignalMLAction;
import org.signalml.app.action.selector.SignalDocumentFocusSelector;
import org.signalml.app.config.preset.Preset;
import org.signalml.app.document.FileBackedDocument;
import org.signalml.app.document.TagDocument;
import org.signalml.app.document.signal.SignalDocument;
import org.signalml.app.document.signal.SignalMLDocument;
import org.signalml.app.model.signal.SignalExportDescriptor;
import org.signalml.app.view.common.dialogs.OptionPane;
import org.signalml.app.view.common.dialogs.PleaseWaitDialog;
import org.signalml.app.view.common.dialogs.errors.Dialogs;
import org.signalml.app.view.signal.PositionedTag;
import org.signalml.app.view.signal.SampleSourceUtils;
import org.signalml.app.view.signal.SignalPlot;
import org.signalml.app.view.signal.SignalScanResult;
import org.signalml.app.view.signal.SignalView;
import org.signalml.app.view.signal.export.ExportSignalDialog;
import org.signalml.app.view.workspace.ViewerFileChooser;
import org.signalml.app.worker.document.ExportSignalWorker;
import org.signalml.app.worker.signal.ScanSignalWorker;
import org.signalml.domain.signal.ExportFormatType;
import org.signalml.domain.signal.SignalProcessingChain;
import org.signalml.domain.signal.raw.RawSignalDescriptor;
import org.signalml.domain.signal.raw.RawSignalDescriptor.SourceSignalType;
import org.signalml.domain.signal.raw.RawSignalDescriptorWriter;
import org.signalml.domain.signal.raw.RawSignalSampleType;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.domain.signal.space.MarkerTimeSpace;
import org.signalml.domain.signal.space.SegmentedSampleSourceFactory;
import org.signalml.domain.signal.space.SignalSpace;
import org.signalml.domain.signal.space.SignalSpaceConstraints;
import org.signalml.domain.signal.space.TimeSpaceType;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.plugin.export.signal.SignalSelection;
import org.signalml.plugin.export.signal.Tag;
import org.signalml.util.Util;
/** ExportSignalAction
*
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class ExportSignalAction extends AbstractFocusableSignalMLAction<SignalDocumentFocusSelector> {
private static final long serialVersionUID = 1L;
protected static final Logger logger = Logger.getLogger(ExportSignalAction.class);
private ExportSignalDialog exportSignalDialog;
private PleaseWaitDialog pleaseWaitDialog;
private ViewerFileChooser fileChooser;
private Component optionPaneParent;
private RawSignalDescriptorWriter descriptorWriter;
public ExportSignalAction(SignalDocumentFocusSelector signalDocumentFocusSelector) {
super(signalDocumentFocusSelector);
setText(_("Export Signal"));
setToolTip(_("Export signal to simple binary, ASCII or EEGLab format"));
setMnemonic(KeyEvent.VK_E);
}
@Override
public void actionPerformed(ActionEvent ev) {
logger.debug("Export signal");
SignalDocument signalDocument = getActionFocusSelector().getActiveSignalDocument();
if (signalDocument == null) {
logger.warn("Target document doesn't exist or is not a signal");
return;
}
SignalView signalView = (SignalView) signalDocument.getDocumentView();
SignalPlot masterPlot = signalView.getMasterPlot();
TagDocument tagDocument = signalDocument.getActiveTag();
SignalExportDescriptor signalExportDescriptor;
Preset preset = exportSignalDialog.getPresetManager().getDefaultPreset();
if (preset == null) {
signalExportDescriptor = new SignalExportDescriptor();
} else {
signalExportDescriptor = (SignalExportDescriptor) preset;
}
SignalSpace space = signalExportDescriptor.getSignalSpace();
SignalSelection signalSelection = signalView.getSignalSelection(masterPlot);
Tag tag = null;
PositionedTag tagSelection = signalView.getTagSelection(masterPlot);
if (tagSelection != null) {
if (tagDocument != null && tagSelection.getTagPositionIndex() == signalDocument.getTagDocuments().indexOf(tagDocument)) {
tag = tagSelection.getTag();
}
}
SignalSpaceConstraints constraints = signalView.createSignalSpaceConstraints();
space.configureFromSelections(signalSelection, tag);
if (tagDocument != null) {
signalExportDescriptor.setTagSet(tagDocument.getTagSet());
tagDocument.updateSignalSpaceConstraints(constraints);
} else {
signalExportDescriptor.setTagSet(null);
constraints.setMarkerStyles(null);
}
signalExportDescriptor.setPageSize(masterPlot.getPageSize());
signalExportDescriptor.setBlockSize(masterPlot.getBlockSize());
constraints.setRequireCompletePages(false);
exportSignalDialog.setConstraints(constraints);
boolean ok = exportSignalDialog.showDialog(signalExportDescriptor, true);
if (!ok) {
return;
}
ExportFiles exportFiles = chooseFiles(signalExportDescriptor);
if (exportFiles == null)
return;
SignalSpace signalSpace = signalExportDescriptor.getSignalSpace();
SignalProcessingChain signalChain;
try {
signalChain = masterPlot.getSignalChain().createLevelCopyChain(signalSpace.getSignalSourceLevel());
} catch (SignalMLException ex) {
logger.error("Failed to create subchain", ex);
Dialogs.showExceptionDialog((Window) null, ex);
return;
}
SegmentedSampleSourceFactory factory = SegmentedSampleSourceFactory.getSharedInstance();
MultichannelSampleSource sampleSource = factory.getContinuousOrSegmentedSampleSource(signalChain, signalSpace, signalExportDescriptor.getTagSet(), signalExportDescriptor.getPageSize(), signalExportDescriptor.getBlockSize());
normalizeSamplesIfNeeded(sampleSource, signalExportDescriptor);
if (saveSignal(sampleSource, exportFiles.getSignalFile(), signalExportDescriptor) == false)
return;
if (signalExportDescriptor.getFormatType() == ExportFormatType.RAW
&& signalExportDescriptor.isSaveXML())
if (saveDescriptor(sampleSource, signalExportDescriptor, masterPlot, exportFiles) == false)
return;
}
/**
* Shows dialog for choosing files to which the signal and its descriptor
* should be exported.
* @param signalExportDescriptor the desciptor describing the parameters
* of the export
* @return true if files were chosen, false if the user cancelled choosing
* files
*/
protected ExportFiles chooseFiles(SignalExportDescriptor signalExportDescriptor) {
SignalDocument signalDocument = getActionFocusSelector().getActiveSignalDocument();
File fileSuggestion = null;
if (signalDocument instanceof FileBackedDocument) {
File originalFile = ((FileBackedDocument) signalDocument).getBackingFile();
if (originalFile != null) {
originalFile = new File(originalFile.getName());
String extension = Util.getFileExtension(originalFile, false);
if (signalExportDescriptor.getFormatType() == ExportFormatType.RAW
&& !"bin".equals(extension)) {
fileSuggestion = Util.changeOrAddFileExtension(originalFile, "bin");
} else if (signalExportDescriptor.getFormatType() == ExportFormatType.EEGLab
&& !"set".equals(extension)) {
fileSuggestion = Util.changeOrAddFileExtension(originalFile, "set");
} else if (signalExportDescriptor.getFormatType() == ExportFormatType.MATLAB
&& !"mat".equals(extension)) {
fileSuggestion = Util.changeOrAddFileExtension(originalFile, "mat");
} else if (signalExportDescriptor.getFormatType() == ExportFormatType.ASCII
&& !"ascii".equals(extension)){
fileSuggestion = Util.changeOrAddFileExtension(originalFile, "ascii");
}
}
}
File file = null;
File xmlFile = null;
boolean hasFile = false;
do {
switch(signalExportDescriptor.getFormatType()) {
case RAW: file = fileChooser.chooseExportSignalFile(optionPaneParent, fileSuggestion); break;
case EEGLab: file = fileChooser.chooseExportEEGLabSignalFile(optionPaneParent, fileSuggestion); break;
case ASCII: file = fileChooser.chooseExportASCIISignalFile(optionPaneParent, fileSuggestion); break;
case MATLAB: file = fileChooser.chooseExportMatlabSignalFile(optionPaneParent, fileSuggestion); break;
}
if (file == null) {
return null;
}
String defaultExtension = signalExportDescriptor.getFormatType().getDefaultExtension();
file = new File(Util.changeOrAddFileExtension(file, defaultExtension).getAbsolutePath());
hasFile = true;
if (file.exists()) {
int res = OptionPane.showFileAlreadyExists(optionPaneParent, file.getName());
if (res != OptionPane.OK_OPTION) {
hasFile = false;
}
}
if (hasFile && signalExportDescriptor.isSaveXML()) {
xmlFile = Util.changeOrAddFileExtension(file, "xml");
if (xmlFile.exists() || xmlFile.equals(file)) {
int res = OptionPane.showFileAlreadyExists(optionPaneParent, xmlFile.getName());
if (res != OptionPane.OK_OPTION) {
hasFile = false;
}
}
}
} while (!hasFile);
return new ExportFiles(file, xmlFile);
}
/**
* Normalizes samples if it is needed or the user chosed an appropriate
* option.
* @param sampleSource source of samples to be exported
* @param signalExportDescriptor the desciptor describing the parameters
* of the export
*/
protected void normalizeSamplesIfNeeded(MultichannelSampleSource sampleSource, SignalExportDescriptor signalExportDescriptor) {
RawSignalSampleType sampleType = signalExportDescriptor.getSampleType();
if (sampleType == RawSignalSampleType.INT || sampleType == RawSignalSampleType.SHORT) {
// normalization - check signal half-amplitude maximum
ScanSignalWorker scanWorker = new ScanSignalWorker(sampleSource, pleaseWaitDialog);
scanWorker.execute();
pleaseWaitDialog.setActivity(_("scanning signal"));
pleaseWaitDialog.configureForDeterminate(0, SampleSourceUtils.getMaxSampleCount(sampleSource), 0);
pleaseWaitDialog.waitAndShowDialogIn(optionPaneParent, 500, scanWorker);
SignalScanResult signalScanResult = null;
try {
signalScanResult = scanWorker.get();
} catch (InterruptedException ex) {
// ignore
} catch (ExecutionException ex) {
logger.error("Worker failed to save", ex.getCause());
Dialogs.showExceptionDialog((Window) null, ex);
return;
}
double maxSignalAbsValue = Math.max(Math.abs(signalScanResult.getMaxSignalValue()), Math.abs(signalScanResult.getMinSignalValue()));
double maxTypeAbsValue = 0;
if (sampleType == RawSignalSampleType.INT) {
maxTypeAbsValue = Math.min((Integer.MAX_VALUE-1), -(Integer.MIN_VALUE+1));
} else {
maxTypeAbsValue = Math.min((Short.MAX_VALUE-1), -(Short.MIN_VALUE+1));
}
boolean normalize = signalExportDescriptor.isNormalize();
if (!normalize) {
// check if normalization needs to be forced
if (maxTypeAbsValue < Math.ceil(maxSignalAbsValue)) {
int ans = OptionPane.showNormalizationUnavoidable(optionPaneParent);
if (ans != OptionPane.OK_OPTION) {
return;
}
normalize = true;
signalExportDescriptor.setNormalize(normalize);
}
}
if (normalize) {
signalExportDescriptor.setNormalizationFactor(maxTypeAbsValue / maxSignalAbsValue);
}
}
}
/**
* Saves the samples to a given file.
* @param sampleSource source containing samples to be exported
* @param signalFile a file to which the signal should be saved
* @param signalExportDescriptor the desciptor describing the parameters
* of the export
* @return true if succeeded, false otherwise
*/
public boolean saveSignal(MultichannelSampleSource sampleSource, File signalFile, SignalExportDescriptor signalExportDescriptor) {
int minSampleCount = SampleSourceUtils.getMinSampleCount(sampleSource);
ExportSignalWorker worker = new ExportSignalWorker(sampleSource, signalFile, signalExportDescriptor, pleaseWaitDialog, getActionFocusSelector().getActiveSignalDocument());
worker.execute();
pleaseWaitDialog.configureForDeterminate(0, minSampleCount, 0);
pleaseWaitDialog.waitAndShowDialogIn(optionPaneParent, 500, worker);
try {
worker.get();
} catch (InterruptedException ex) {
// ignore
} catch (ExecutionException ex) {
logger.error("Worker failed to save", ex.getCause());
Dialogs.showExceptionDialog((Window) null, ex);
return false;
}
return true;
}
/**
* Saves the XML descriptor for the signal file.
* @param sampleSource source of samples which should be exported
* @param signalExportDescriptor the desciptor describing the parameters
* of the export
* @param masterPlot a {@link SignalPlot} containing the samples to be
* exported
* @param exportFiles {@link ExportFiles} containing files to which
* the signal and its descriptor should be saved
* @return true if succeeded, false otherwise
*/
public boolean saveDescriptor(MultichannelSampleSource sampleSource, SignalExportDescriptor signalExportDescriptor, SignalPlot masterPlot, ExportFiles exportFiles) {
if (descriptorWriter == null) {
descriptorWriter = new RawSignalDescriptorWriter();
}
SignalDocument signalDocument = getActionFocusSelector().getActiveSignalDocument();
SignalSpace signalSpace = signalExportDescriptor.getSignalSpace();
RawSignalDescriptor rawDescriptor = new RawSignalDescriptor();
float samplingFrequency = sampleSource.getSamplingFrequency();
rawDescriptor.setBlocksPerPage(masterPlot.getBlocksPerPage());
rawDescriptor.setByteOrder(signalExportDescriptor.getByteOrder());
int channelCount = sampleSource.getChannelCount();
rawDescriptor.setChannelCount(channelCount);
rawDescriptor.setEegSystemName(signalDocument.getMontage().getEegSystemName());
if (signalExportDescriptor.isNormalize()) {
rawDescriptor.setCalibrationGain((float)(1 / signalExportDescriptor.getNormalizationFactor()));
} else {
rawDescriptor.setCalibrationGain(1F);
}
rawDescriptor.setCalibrationOffset(0);
String[] labels = new String[channelCount];
for (int i=0; i<channelCount; i++) {
labels[i] = sampleSource.getLabel(i);
}
rawDescriptor.setChannelLabels(labels);
rawDescriptor.setExportDate(new Date());
rawDescriptor.setExportFileName(exportFiles.getSignalFile().getName());
TimeSpaceType timeSpaceType = signalSpace.getTimeSpaceType();
if (timeSpaceType == TimeSpaceType.MARKER_BASED) {
MarkerTimeSpace markerTimeSpace = signalSpace.getMarkerTimeSpace();
rawDescriptor.setMarkerOffset(markerTimeSpace.getStartTime());
rawDescriptor.setPageSize((float)(markerTimeSpace.getSegmentLength()));
} else {
rawDescriptor.setMarkerOffset(0);
rawDescriptor.setPageSize(masterPlot.getPageSize());
}
int minSampleCount = SampleSourceUtils.getMinSampleCount(sampleSource);
rawDescriptor.setSampleCount(minSampleCount);
rawDescriptor.setSampleType(signalExportDescriptor.getSampleType());
rawDescriptor.setSamplingFrequency(samplingFrequency);
if (signalDocument instanceof FileBackedDocument) {
File sourceFile = ((FileBackedDocument) signalDocument).getBackingFile();
if (sourceFile != null) {
rawDescriptor.setSourceFileName(sourceFile.getName());
}
}
if (signalDocument instanceof SignalMLDocument) {
SignalMLDocument signalMLDocument = (SignalMLDocument) signalDocument;
rawDescriptor.setSourceSignalType(SourceSignalType.SIGNALML);
rawDescriptor.setSourceSignalMLFormat(signalMLDocument.getFormatName());
rawDescriptor.setSourceSignalMLSourceUID(signalMLDocument.getSourceUID());
} else {
rawDescriptor.setSourceSignalType(SourceSignalType.RAW);
}
try {
descriptorWriter.writeDocument(rawDescriptor, exportFiles.getXmlFile());
} catch (IOException ex) {
logger.error("Worker failed to save xml", ex);
Dialogs.showExceptionDialog((Window) null, ex);
return false;
}
return true;
}
@Override
public void setEnabledAsNeeded() {
SignalDocumentFocusSelector x = getActionFocusSelector();
if (null != x)
setEnabled(x.getActiveSignalDocument() != null);
}
public ExportSignalDialog getExportSignalDialog() {
return exportSignalDialog;
}
public void setExportSignalDialog(ExportSignalDialog exportSignalDialog) {
this.exportSignalDialog = exportSignalDialog;
}
public PleaseWaitDialog getPleaseWaitDialog() {
return pleaseWaitDialog;
}
public void setPleaseWaitDialog(PleaseWaitDialog pleaseWaitDialog) {
this.pleaseWaitDialog = pleaseWaitDialog;
}
public ViewerFileChooser getFileChooser() {
return fileChooser;
}
public void setFileChooser(ViewerFileChooser fileChooser) {
this.fileChooser = fileChooser;
}
public Component getOptionPaneParent() {
return optionPaneParent;
}
public void setOptionPaneParent(Component optionPaneParent) {
this.optionPaneParent = optionPaneParent;
}
/**
* This class holds the files to which the signal and its descriptor
* will be saved.
*/
protected class ExportFiles {
/**
* The file to which the signal will be exported.
*/
private File signalFile;
/**
* The file to which the exported signal XML descriptor will be written.
*/
private File xmlFile;
/**
* Constructor.
* @param signalFile a file to which the signal will be saved
* @param xmlFile a file to which the signal descriptor will be saved
*/
public ExportFiles(File signalFile, File xmlFile) {
this.signalFile = signalFile;
this.xmlFile = xmlFile;
}
/**
* Returns a file to which the signal should be exported.
* @return a file to which the signal should be exported
*/
public File getSignalFile() {
return signalFile;
}
/**
* Returns a file to which the signal's descriptor should be written.
* @return a file to which the signal's descriptor should be written;
*/
public File getXmlFile() {
return xmlFile;
}
}
}