package pl.edu.fuw.fid.signalanalysis.dtf;
import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.SingularMatrixException;
import org.signalml.app.document.signal.SignalDocument;
import org.signalml.app.view.signal.SignalView;
import org.signalml.domain.montage.Montage;
import org.signalml.domain.montage.MontageMismatchException;
import org.signalml.domain.signal.SignalProcessingChain;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.domain.signal.space.SignalSpaceConstraints;
import org.signalml.plugin.export.NoActiveObjectException;
import org.signalml.plugin.export.signal.SvarogAccessSignal;
import org.signalml.plugin.export.view.AbstractSignalMLAction;
import org.signalml.plugin.export.view.SvarogAccessGUI;
import pl.edu.fuw.fid.signalanalysis.SignalAnalysisTools;
/**
* Manages DTF method computation. Gathers data and presents results.
*
* @author ptr@mimuw.edu.pl
*/
public class DtfMethodAction extends AbstractSignalMLAction {
private static final int SPECTRUM_SIZE = 100;
private static final String TITLE = "Directed Transfer Function";
private final SvarogAccessGUI guiAccess;
private final SvarogAccessSignal signalAccess;
public DtfMethodAction(SvarogAccessGUI guiAccess, SvarogAccessSignal signalAccess) {
super();
this.guiAccess = guiAccess;
this.signalAccess = signalAccess;
this.setText(TITLE);
}
@Override
public void actionPerformed(ActionEvent e) {
try {
SignalDocument signalDocument = (SignalDocument) signalAccess.getActiveSignalDocument();
DtfSettingsPanel settingsPanel = createSettingsPanel(signalDocument);
int result = settingsPanel.showAsConfirmDialog(guiAccess.getDialogParent());
if (result == JOptionPane.OK_OPTION) {
int maxModelOrder = settingsPanel.getMaxModelOrder();
int[] selectedChannels = settingsPanel.getSelectedChannels();
if (selectedChannels.length == 0) {
JOptionPane.showMessageDialog(guiAccess.getDialogParent(), "Select at least one channel.", "Try again", JOptionPane.WARNING_MESSAGE);
return;
}
proceedToComputation(signalDocument, selectedChannels, maxModelOrder);
}
} catch (SingularMatrixException ex) {
JOptionPane.showMessageDialog(guiAccess.getDialogParent(), "Cannot compute DTF. Lag autocorrelation matrix is singular.", "Error", JOptionPane.ERROR_MESSAGE);
} catch (MontageMismatchException ex) {
JOptionPane.showMessageDialog(guiAccess.getDialogParent(), "Montage mismatch.", "Error", JOptionPane.ERROR_MESSAGE);
} catch (NoActiveObjectException ex) {
JOptionPane.showMessageDialog(guiAccess.getDialogParent(), "Choose an active signal first.", "Error", JOptionPane.WARNING_MESSAGE);
}
}
/**
*
* @param models list of AR models for order 1, 2, 3, ...
* @param N sample count in data used to compute AR models
* @return
*/
private XYSeriesWithLegend[] computeCriteria(ArModel[] models, int N) {
XYSeriesWithLegend serieAIC = new XYSeriesWithLegend("AIC", "AIC(order) = log(det(V)) + 2 × order × channels² / samples");
XYSeriesWithLegend serieHQ = new XYSeriesWithLegend("Hannan-Quin", "HQ(order) = log(det(V)) + 2 × log(log(samples)) × order × channels² / samples");
XYSeriesWithLegend serieSch = new XYSeriesWithLegend("Schwartz", "SC(order) = log(det(V)) + log(samples) × order × channels² / samples");
int order = 1;
for (ArModel model : models) {
double det = model.getErrorDeterminant();
if (det > 0) {
double logdet = Math.log(det);
double logN = Math.log(N);
double k = model.getChannelCount();
double pk2_N = order*k*k/N;
serieAIC.add(order, logdet + 2*pk2_N);
serieHQ.add(order, logdet + 2*Math.log(logN)*pk2_N);
serieSch.add(order, logdet + logN*pk2_N);
}
order++;
}
return new XYSeriesWithLegend[] {
serieAIC,
serieHQ,
serieSch
};
}
private DtfSettingsPanel createSettingsPanel(SignalDocument signalDocument) throws NoActiveObjectException {
SignalView signalView = (SignalView) signalDocument.getDocumentView();
SignalSpaceConstraints signalSpaceConstraints = signalView.createSignalSpaceConstraints();
return new DtfSettingsPanel(signalSpaceConstraints);
}
private String[] getChannelNames(MultichannelSampleSource sampleSource, int[] selectedChannels) {
final String[] channels = new String[selectedChannels.length];
for (int c=0; c<selectedChannels.length; ++c) {
channels[c] = sampleSource.getLabel(selectedChannels[c]);
}
return channels;
}
private MultichannelSampleSource getSampleSource(SignalDocument signalDocument) throws MontageMismatchException {
SignalProcessingChain signalChain = SignalProcessingChain.createFilteredChain(signalDocument.getSampleSource());
Montage oldMontage = signalDocument.getMontage();
if (oldMontage != null) {
signalChain.applyMontageDefinition(oldMontage);
}
return signalChain.getOutput();
}
private void proceedToComputation(SignalDocument signalDocument, final int[] selectedChannels, int maxOrder) throws MontageMismatchException {
// extract data samples of selected channels
final MultichannelSampleSource sampleSource = getSampleSource(signalDocument);
final int N = sampleSource.getSampleCount(0);
RealMatrix data = SignalAnalysisTools.extractDataFromSignal(sampleSource, null, selectedChannels);
// compute AR models for all orders from 1 up to maxOrder
final ArModel[] models = new ArModel[maxOrder];
for (int order=1; order<=maxOrder; ++order) {
models[order-1] = ArModel.compute(data, sampleSource.getSamplingFrequency(), order);
}
// compute criteria facilitating order selection
XYSeriesWithLegend[] criteria = computeCriteria(models, N);
// make list of channel labels
final String[] channels = getChannelNames(sampleSource, selectedChannels);
// create tabbed pane
Montage montage = new Montage(signalDocument);
for (int i=0; i<selectedChannels.length; ++i) {
montage.addMontageChannel(selectedChannels[i]);
}
// display dialog with DTF results
DtfDialog dialog = new DtfDialog(guiAccess.getDialogParent(), criteria, channels, models, SPECTRUM_SIZE, montage);
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
dialog.setModal(false);
dialog.showDialog(null);
}
}