/* MP5Method.java created 2007-10-03
*
*/
package org.signalml.method.mp5;
import static org.signalml.app.util.i18n.SvarogI18n._;
import static org.signalml.app.util.i18n.SvarogI18n._R;
import java.io.File;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.signalml.app.util.XMLUtils;
import org.signalml.domain.book.BookBuilder;
import org.signalml.domain.book.BookBuilderImpl;
import org.signalml.domain.book.BookFormatException;
import org.signalml.domain.book.DefaultBookBuilder;
import org.signalml.domain.book.IncrementalBookWriter;
import org.signalml.domain.book.StandardBook;
import org.signalml.domain.book.StandardBookAtomWriterImpl;
import org.signalml.domain.book.StandardBookSegment;
import org.signalml.domain.book.StandardBookSegmentWriter;
import org.signalml.domain.book.StandardBookSegmentWriterImpl;
import org.signalml.domain.book.StandardBookWriter;
import org.signalml.domain.signal.MultichannelSampleProcessor;
import org.signalml.domain.signal.SignalProcessingChain;
import org.signalml.domain.signal.SignalProcessingChainDescriptor;
import org.signalml.domain.signal.SignalWriterMonitor;
import org.signalml.domain.signal.filter.export.MultichannelSampleFilterForExport;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.domain.signal.samplesource.MultichannelSegmentedSampleSource;
import org.signalml.domain.signal.space.MarkerSegmentedSampleSourceDescriptor;
import org.signalml.domain.signal.space.SegmentedSampleSourceDescriptor;
import org.signalml.domain.signal.space.SelectionSegmentedSampleSourceDescriptor;
import org.signalml.method.AbstractMethod;
import org.signalml.method.CleanupMethod;
import org.signalml.method.ComputationException;
import org.signalml.method.MethodExecutionTracker;
import org.signalml.method.SerializableMethod;
import org.signalml.method.TrackableMethod;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.plugin.export.method.BaseMethodData;
import org.signalml.util.Util;
import org.springframework.validation.Errors;
import pl.edu.fuw.MP.Core.BookLibraryV5Writer;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.Annotations;
/** MP5Method
*
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class MP5Method extends AbstractMethod implements TrackableMethod, SerializableMethod, CleanupMethod, SignalWriterMonitor {
protected static final Logger logger = Logger.getLogger(MP5Method.class);
private static final String UID = "93a02d05-92ce-4634-b51a-5b5c558be506";
private static final String NAME = "mp5";
private static final int[] VERSION = new int[] {1,0};
private File tempDirectory = null;
private MP5ExecutorLocator executorLocator;
private MP5ExecutorConfigurer executorConfigurer;
private XStream streamer;
private MethodExecutionTracker tracker;
public MP5Method() throws SignalMLException {
super();
}
public File getTempDirectory() {
return tempDirectory;
}
public void setTempDirectory(File tempDirectory) {
this.tempDirectory = tempDirectory;
}
public MP5ExecutorLocator getExecutorLocator() {
return executorLocator;
}
public void setExecutorLocator(MP5ExecutorLocator executorLocator) {
this.executorLocator = executorLocator;
}
public MP5ExecutorConfigurer getExecutorConfigurer() {
return executorConfigurer;
}
public void setExecutorConfigurer(MP5ExecutorConfigurer executorConfigurer) {
this.executorConfigurer = executorConfigurer;
}
@Override
public Object doComputation(Object dataObj, final MethodExecutionTracker tracker) throws ComputationException {
this.tracker = tracker;
logger.debug("Beginning computation of MP5");
MP5Data data = (MP5Data) dataObj;
tracker.setMessage(_("Creating files"));
createWorkingDirectory(data);
File workingDirectory = data.getWorkingDirectory();
MultichannelSegmentedSampleSource sampleSource = data.getSampleSource();
int totalSegmentCount = sampleSource.getSegmentCount();
int segment = 0;
if (isDataSuspended(data)) {
segment = data.getCompletedSegments();
data.setSuspended(false);
}
synchronized (tracker) {
tracker.setTickerLimits(new int[] { totalSegmentCount, 1, 1, 1, 1 });
tracker.setTickers(new int[] { segment, 0, 0, 0, 0 });
}
MP5Executor executor = executorLocator.findExecutor(data.getExecutorUID());
if (executor == null) {
throw new ComputationException("Executor not found [" + executor + "]");
}
if (executorConfigurer != null) {
executorConfigurer.configure(executor);
}
logger.debug("Using mp5 executor [" + executor + "]");
File segmentBookFile;
boolean segmentOk;
prepareFiltering(sampleSource);
while (segment < totalSegmentCount) {
if (testAbortSuspend(tracker,data,segment)) {
return null;
}
tracker.setMessage(_R("Processing segment {0}", (segment+1)));
segmentBookFile = new File(workingDirectory, "result_" + Integer.toString(segment) + ".b");
segmentOk = executor.execute(data, segment, segmentBookFile, tracker);
if (!segmentOk) {
testAbortSuspend(tracker,data,segment);
return null;
}
segment++;
tracker.setTickers(new int[] { segment, 0, 0, 0 });
}
tracker.setMessage(_("Collecting results"));
//
// Collect partial books from mp5 native code into one
//
File collectedBookFile = new File(workingDirectory, "collected.b");
if (collectedBookFile.exists()) {
collectedBookFile.delete();
}
File bookFile;
int i;
BookBuilder builder = new BookBuilderImpl();
StandardBookWriter book = builder.createBook();
book.setDate(Util.now("yy/MM/dd"));
IncrementalBookWriter bookWriter = null;
BookBuilder bookBuilder = DefaultBookBuilder.getInstance();
StandardBook partialBook = null;
StandardBookSegment[] segments;
for (i=0; i<totalSegmentCount; i++) {
bookFile = new File(workingDirectory, "result_" + i + ".b");
if (!bookFile.exists()) {
throw new ComputationException("Missing partial result file [" + bookFile.getAbsolutePath() + "]");
}
try {
partialBook = bookBuilder.readBook(bookFile);
} catch (IOException ex) {
logger.error("Failed to read partial book [" + bookFile.getAbsolutePath() + "] - i/o exception", ex);
throw new ComputationException(ex);
} catch (BookFormatException ex) {
logger.error("Failed to read partial book [" + bookFile.getAbsolutePath() + "]", ex);
throw new ComputationException(ex);
}
segments = partialBook.getSegmentAt(0); //each partial book contains only one segment
book.setCalibration(partialBook.getCalibration());
book.setEnergyPercent(partialBook.getEnergyPercent());
book.setSamplingFrequency(partialBook.getSamplingFrequency());
int numberOfChannels = segments.length;
((BookLibraryV5Writer) book).setNumberOfChannels(numberOfChannels);
if (bookWriter == null) {
try {
bookWriter = builder.writeBookIncremental(book, collectedBookFile.getAbsolutePath());
} catch (IOException ex) {
logger.error("Failed to write book [" + collectedBookFile.getAbsolutePath() + "] - i/o exception", ex);
throw new ComputationException(ex);
}
}
StandardBookSegmentWriter seg = new StandardBookSegmentWriterImpl(book);
for (int channel = 0; channel < numberOfChannels; channel++) {
seg.setChannelNumber(segments[channel].getChannelNumber());
seg.setSegmentNumber(i+1);
seg.setSegmentLength(segments[channel].getSegmentLength());
seg.clearAtoms();
for (int j = 0; j < segments[channel].getAtomCount(); j++) {
StandardBookAtomWriterImpl bookAtomWriter = new StandardBookAtomWriterImpl(segments[channel].getAtomAt(j));
seg.addAtom(bookAtomWriter);
seg.setSignalSamples(segments[channel].getSignalSamples());
}
try {
bookWriter.writeSegment(seg);
} catch (IOException ex) {
logger.error("Failed to write segment to [" + collectedBookFile.getAbsolutePath() + "] - i/o exception", ex);
throw new ComputationException(ex);
}
}
logger.debug("Writing book done");
}
if (bookWriter != null) {
try {
bookWriter.close();
} catch (IOException ex) {
logger.error("Failed to close file [" + collectedBookFile.getAbsolutePath() + "] - i/o exception", ex);
throw new ComputationException(ex);
}
}
MP5Result result = new MP5Result();
result.setBookFilePath(collectedBookFile.getAbsolutePath());
// for result serialization
data.setBookFilePath(collectedBookFile.getAbsolutePath());
tracker.setMessage(_("Finished"));
return result;
}
private void prepareFiltering(MultichannelSampleSource sampleSource) {
MultichannelSampleProcessor channelSubsetSampleSource = ((MultichannelSampleProcessor)sampleSource);
SignalProcessingChain signalProcessingChain = ((SignalProcessingChain)channelSubsetSampleSource.getSource());
if (signalProcessingChain.getOutput() instanceof MultichannelSampleFilterForExport) {
MultichannelSampleFilterForExport multichannelSampleFilterForExport = (MultichannelSampleFilterForExport) signalProcessingChain.getOutput();
int maximumSampleCount = signalProcessingChain.getSampleCount(0);
tracker.setTickerLimit(4, maximumSampleCount);
multichannelSampleFilterForExport.setSignalWriterMonitor(this);
}
}
private boolean testAbortSuspend(MethodExecutionTracker tracker, MP5Data data, int segment) {
if (tracker.isRequestingAbort()) {
logger.debug("Terminating execution");
return true;
}
if (tracker.isRequestingSuspend()) {
logger.debug("Suspending execution");
data.setCompletedSegments(segment);
data.setSuspended(true);
return true;
}
return false;
}
private void createWorkingDirectory(MP5Data data) throws ComputationException {
File workingDirectory = data.getWorkingDirectory();
if (workingDirectory == null) {
workingDirectory = new File(tempDirectory, Util.getRandomHexString(8));
data.setWorkingDirectory(workingDirectory);
}
if (!workingDirectory.exists()) {
boolean ok = workingDirectory.mkdir();
if (!ok) {
logger.error("Failed to create working directory");
throw new ComputationException(new IOException("Failed to create working dir"));
}
} else {
if (!workingDirectory.canWrite()) {
logger.error("Working directory not writeable");
throw new ComputationException(new IOException("Working directory not writeable"));
}
}
}
@Override
public void validate(Object dataObj, Errors errors) {
super.validate(dataObj, errors);
if (!errors.hasErrors()) {
MP5Data data = (MP5Data) dataObj;
data.validate(errors);
if (!errors.hasErrors()) {
MP5Executor executor = executorLocator.findExecutor(data.getExecutorUID());
if (executor == null) {
errors.rejectValue("executorUID", "error.mp5.executorNotFound", new Object[] { data.getExecutorUID() }, "Executor not found");
}
}
}
}
@Override
public int getTickerCount() {
return 5;
}
@Override
public String getTickerLabel(int ticker) {
switch (ticker) {
case 0 :
return _("Segment progress");
case 1 :
return _("Channel progress");
case 2 :
return _("Atom (non-linear progress)");
case 3 :
return _("Current atom matching progress");
case 4:
return _("Data filtering");
default :
throw new IndexOutOfBoundsException();
}
}
@Override
public String getUID() {
return UID;
}
@Override
public String getName() {
return NAME;
}
@Override
public int[] getVersion() {
return VERSION;
}
@Override
public BaseMethodData createData() {
return new MP5Data();
}
@Override
public Class<?> getResultClass() {
return MP5Result.class;
}
@Override
public boolean supportsDataClass(Class<?> clazz) {
return MP5Data.class.isAssignableFrom(clazz);
}
@Override
public boolean isDataSuspended(Object data) {
return ((MP5Data) data).isSuspended();
}
@Override
public void readFromPersistence(Object dataObj, File file) throws IOException, SignalMLException {
MP5Data data = (MP5Data) dataObj;
logger.debug("Reading from file [" + file.getAbsolutePath() +"]");
XMLUtils.objectFromFile(data, file, getStreamer());
SignalProcessingChainDescriptor chainDescriptor = data.getChainDescriptor();
if (chainDescriptor == null) {
throw new NullPointerException("No chain descriptor");
}
SegmentedSampleSourceDescriptor sourceDescriptor = data.getSourceDescriptor();
if (sourceDescriptor == null) {
throw new NullPointerException("No source descriptor");
}
SignalProcessingChain chain = new SignalProcessingChain(chainDescriptor);
data.setSampleSource(sourceDescriptor.createSegmentedSource(chain));
}
@Override
public File writeToPersistence(Object dataObj) throws IOException, SignalMLException {
MP5Data data = (MP5Data) dataObj;
File workingDirectory = data.getWorkingDirectory();
if (workingDirectory == null) {
throw new SignalMLException("No working directory");
}
File file = new File(workingDirectory, "serialized.xml");
logger.debug("Writing to file [" + file.getAbsolutePath() +"]");
XMLUtils.objectToFile(data, file, getStreamer());
return file;
}
public XStream getStreamer() {
if (streamer == null) {
streamer = createMP5Streamer();
}
return streamer;
}
private XStream createMP5Streamer() {
XStream streamer = XMLUtils.getDefaultStreamer();
XMLUtils.configureStreamerForMontage(streamer);
Annotations.configureAliases(
streamer,
SignalProcessingChainDescriptor.class,
SelectionSegmentedSampleSourceDescriptor.class,
MarkerSegmentedSampleSourceDescriptor.class,
MP5Data.class,
MP5Parameters.class
);
streamer.setMode(XStream.ID_REFERENCES);
return streamer;
}
@Override
public void cleanUp(Object dataObj) {
MP5Data data = (MP5Data) dataObj;
File workingDirectory = data.getWorkingDirectory();
if (workingDirectory != null && workingDirectory.exists()) {
logger.info("Cleaning up directory [" + workingDirectory.getAbsolutePath() + "]");
File[] files = workingDirectory.listFiles();
boolean deleteOk;
for (File f : files) {
logger.info("Deleting file [" + f.getAbsolutePath() + "]");
deleteOk = f.delete();
if (!deleteOk) {
logger.error("Failed to delete");
}
}
deleteOk = workingDirectory.delete();
if (!deleteOk) {
logger.error("Failed to delete directory");
}
}
}
@Override
public void setProcessedSampleCount(int sampleCount) {
if (tracker != null)
tracker.setTicker(4, sampleCount);
}
@Override
public void abort() {
// TODO Auto-generated method stub
}
@Override
public boolean isRequestingAbort() {
return false;
}
}