/*- * Copyright 2015 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package uk.ac.diamond.scisoft.analysis.processing.operations; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.dawb.common.services.ServiceManager; import org.eclipse.dawnsci.analysis.api.fitting.functions.IDataBasedFunction; import org.eclipse.dawnsci.analysis.api.fitting.functions.IFunction; import org.eclipse.dawnsci.analysis.api.fitting.functions.IParameter; import org.eclipse.dawnsci.analysis.api.persistence.IPersistenceService; import org.eclipse.dawnsci.analysis.api.persistence.IPersistentFile; import org.eclipse.dawnsci.analysis.api.processing.Atomic; import org.eclipse.dawnsci.analysis.api.processing.OperationData; import org.eclipse.dawnsci.analysis.api.processing.OperationException; import org.eclipse.dawnsci.analysis.api.processing.OperationRank; import org.eclipse.dawnsci.analysis.api.processing.model.AbstractOperationModel; import org.eclipse.dawnsci.analysis.api.roi.IROI; import org.eclipse.dawnsci.analysis.dataset.operations.AbstractOperation; import org.eclipse.dawnsci.analysis.dataset.roi.RectangularROI; import org.eclipse.january.DatasetException; import org.eclipse.january.IMonitor; import org.eclipse.january.MetadataException; import org.eclipse.january.dataset.Dataset; import org.eclipse.january.dataset.DatasetFactory; import org.eclipse.january.dataset.DatasetUtils; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.ILazyDataset; import org.eclipse.january.dataset.Maths; import org.eclipse.january.metadata.AxesMetadata; import org.eclipse.january.metadata.MetadataFactory; import uk.ac.diamond.scisoft.analysis.fitting.FittingConstants.FIT_ALGORITHMS; import uk.ac.diamond.scisoft.analysis.fitting.Generic1DFitter; import uk.ac.diamond.scisoft.analysis.fitting.functions.Add; import uk.ac.diamond.scisoft.analysis.optimize.ApacheOptimizer; import uk.ac.diamond.scisoft.analysis.optimize.ApacheOptimizer.Optimizer; import uk.ac.diamond.scisoft.analysis.optimize.GeneticAlg; import uk.ac.diamond.scisoft.analysis.optimize.IOptimizer; @Atomic public class FunctionFittingOperation extends AbstractOperation<FunctionFittingModel, OperationData> { private volatile FitInformation info; private PropertyChangeListener listener; @Override public String getId() { return "uk.ac.diamond.scisoft.analysis.processing.operations.FunctionFittingOperation"; } protected OperationData process(IDataset input, IMonitor monitor) throws OperationException { FitInformation finfo = getFitInformation(model); Add copy = null; try { copy = (Add)finfo.original.copy(); } catch (Exception e1) { throw new OperationException(this, "Could not copy function!"); } ILazyDataset[] firstAxes = getFirstAxes(input); Dataset x = null; if (firstAxes != null && firstAxes[0] != null) { try { x = DatasetUtils.sliceAndConvertLazyDataset(firstAxes[0]); } catch (DatasetException e) { throw new OperationException(this, e); } } else { x = DatasetFactory.createRange(input.getSize(), Dataset.FLOAT64); } Dataset[] traceROI; try { traceROI = Generic1DFitter.selectInRange(x, DatasetUtils.convertToDataset(input), finfo.points[0],finfo.points[1]); } catch (Throwable npe) { throw new OperationException(this,npe.getMessage()); } Add outfit = null; boolean success = true; try { outfit = doFit(traceROI[0], traceROI[1], copy, model.getOptimiser()); } catch (Exception e) { success = false; outfit = copy; } List<IDataset> params = new ArrayList<IDataset>(); Map<String,Integer> nameMap = new HashMap<String,Integer>(); for (IFunction f : outfit.getFunctions()) { String n = f.getName(); if (nameMap.containsKey(n)){ int c = nameMap.get(n); c++; nameMap.put(n, c); n = n+c; } else { nameMap.put(n,0); n=n+0; } for (IParameter p : f.getParameters()) { String pn = p.getName(); double v = p.getValue(); if (!success) v = Double.NaN; String fullName = n +"_"+pn; Dataset d = DatasetFactory.createFromObject(new double[]{v}); d.setName(fullName); d.squeeze(); params.add(d); } } double r = outfit.residual(true, traceROI[1], null, new IDataset[] {traceROI[0]}); if (!success) r = Double.NaN; Dataset residual = DatasetFactory.createFromObject(r); residual.squeeze(); residual.setName("rms"); params.add(residual); IDataset outx = traceROI[0].getSliceView(); String xName = x.getName(); if (xName == null || xName.isEmpty()) { xName = "xAxis"; } outx.setName(xName); Dataset vals = outfit.calculateValues(outx); vals.setName("function"); Dataset res = Maths.subtract(traceROI[1], vals); res.setName("residual"); try { AxesMetadata ax; ax = MetadataFactory.createMetadata(AxesMetadata.class, 1); ax.addAxis(0, outx); vals.addMetadata(ax); ax = MetadataFactory.createMetadata(AxesMetadata.class, 1); ax.addAxis(0, outx); res.addMetadata(ax); } catch (MetadataException e) { throw new OperationException(this, e); } params.add(vals); params.add(res); return new OperationData(input, (Serializable[])params.toArray(new IDataset[params.size()])); } private FitInformation getFitInformation(FunctionFittingModel model) { FitInformation localInfo = info; if (localInfo == null) { synchronized(this) { localInfo = info; if (localInfo == null) { try { IPersistenceService service = (IPersistenceService)ServiceManager.getService(IPersistenceService.class); IPersistentFile pf = service.getPersistentFile(model.getFilePath()); Map<String, IFunction> functions = pf.getFunctions(null); Map<String, IROI> rois = pf.getROIs(null); IROI roi = rois.get("fit_region"); RectangularROI rr = null; if (roi instanceof RectangularROI) { rr = (RectangularROI)roi; } final double[] p1 = rr.getPointRef(); final double[] p2 = rr.getEndPoint(); double[] points = new double[]{p1[0],p2[0]}; Add resultFunc = new Add(); for (Entry<String,IFunction> f : functions.entrySet()){ if (f.getKey().contains("initial")) continue; resultFunc.addFunction(f.getValue()); } if (resultFunc.getFunctions().length == 0) { for (Entry<String,IFunction> f : functions.entrySet()){ resultFunc.addFunction(f.getValue()); } } Add original = (Add)resultFunc.copy(); if (model.getOptimiser() == FIT_ALGORITHMS.APACHELEVENBERGMAQUARDT) clearRanges(original); FitInformation i = new FitInformation(); i.original = original; i.points = points; info = localInfo = i;; } catch (Exception e) { throw new OperationException(this, "Could not load functions from file"); } } } } return localInfo; } private void clearRanges(Add function) { IFunction[] functions = function.getFunctions(); for (IFunction f : functions) { IParameter[] ps = f.getParameters(); for (IParameter p :ps) { p.setLimits(-Double.MAX_VALUE, Double.MAX_VALUE); } } } @Override public OperationRank getInputRank() { return OperationRank.ONE; } @Override public OperationRank getOutputRank() { return OperationRank.ONE; } private Add doFit(Dataset x, Dataset y, Add resultFunction, FIT_ALGORITHMS algorithm) throws Exception{ double accuracy = 10e-5; IFunction[] functionCopies = resultFunction.getFunctions(); for (IFunction function : functionCopies) { if (function instanceof IDataBasedFunction) { IDataBasedFunction dataBasedFunction = (IDataBasedFunction) function; dataBasedFunction.setData(x, y); } } IOptimizer optimizer = null; switch (algorithm) { default: case APACHENELDERMEAD: optimizer = new ApacheOptimizer(Optimizer.SIMPLEX_NM); break; case GENETIC: optimizer = new GeneticAlg(accuracy); break; case APACHECONJUGATEGRADIENT: optimizer = new ApacheOptimizer(Optimizer.CONJUGATE_GRADIENT); break; case APACHELEVENBERGMAQUARDT: optimizer = new ApacheOptimizer(Optimizer.LEVENBERG_MARQUARDT); break; } optimizer.optimize(new IDataset[] {x}, y, resultFunction); return resultFunction; } @Override public void setModel(FunctionFittingModel model) { super.setModel(model); if (listener == null) { listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { info = null; } }; } else { ((AbstractOperationModel)this.model).removePropertyChangeListener(listener); } ((AbstractOperationModel)this.model).addPropertyChangeListener(listener); } private class FitInformation { public Add original; public double[] points; } }