org.dawnsci.plotting.tools.fitting.AbstractFittingTool.java Source code

Java tutorial

Introduction

Here is the source code for org.dawnsci.plotting.tools.fitting.AbstractFittingTool.java

Source

/*
 * Copyright (c) 2012 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 org.dawnsci.plotting.tools.fitting;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import org.dawb.common.ui.menu.MenuAction;
import org.dawb.common.ui.monitor.ProgressMonitorWrapper;
import org.dawb.common.ui.util.GridUtils;
import org.dawb.common.util.list.SortNatural;
import org.dawnsci.plotting.tools.preference.FittingPreferencePage;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.dawnsci.analysis.dataset.impl.Dataset;
import org.eclipse.dawnsci.analysis.dataset.roi.RectangularROI;
import org.eclipse.dawnsci.plotting.api.region.IRegion;
import org.eclipse.dawnsci.plotting.api.region.IRegion.RegionType;
import org.eclipse.dawnsci.plotting.api.region.IRegionListener;
import org.eclipse.dawnsci.plotting.api.region.RegionEvent;
import org.eclipse.dawnsci.plotting.api.region.RegionUtils;
import org.eclipse.dawnsci.plotting.api.tool.AbstractToolPage;
import org.eclipse.dawnsci.plotting.api.tool.IToolPage;
import org.eclipse.dawnsci.plotting.api.trace.ILineTrace;
import org.eclipse.dawnsci.plotting.api.trace.ITrace;
import org.eclipse.dawnsci.plotting.api.trace.ITraceListener;
import org.eclipse.dawnsci.plotting.api.trace.TraceEvent;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.ac.diamond.scisoft.analysis.fitting.Generic1DFitter;
import uk.ac.diamond.scisoft.analysis.fitting.functions.FunctionSquirts;
import uk.ac.diamond.scisoft.analysis.fitting.functions.FunctionSquirts.Squirt;

public abstract class AbstractFittingTool extends AbstractToolPage implements IRegionListener {

    private static final Logger logger = LoggerFactory.getLogger(AbstractFittingTool.class);

    protected Composite composite;
    protected FittedFunctions fittedFunctions;

    protected MenuAction tracesMenu;
    protected List<ILineTrace> selectedTraces;
    protected TableViewer viewer;
    protected Link algorithmMessage;
    protected FittingJob fittingJob;

    private RectangularROI fitBounds;
    protected IRegion fitRegion;

    private ISelectionChangedListener viewUpdateListener;
    private ITraceListener traceListener;

    private boolean addingPeaks = false;

    public AbstractFittingTool() {
        super();
        this.fittingJob = new FittingJob();

        /**
         * Use Vector here intentionally. It is slower but
         * synchronized which is required in this instance. 
         */
        this.selectedTraces = new Vector<ILineTrace>(31);

        this.traceListener = new ITraceListener.Stub() {
            @Override
            public void tracesUpdated(TraceEvent evt) {
                fittingJob.schedule();
            }

            @Override
            public void tracesAdded(TraceEvent evt) {

                @SuppressWarnings("unchecked")
                final List<ITrace> traces = evt.getSource() instanceof List ? (List<ITrace>) evt.getSource() : null;
                if (traces != null && fittedFunctions != null && !fittedFunctions.isEmpty()) {
                    traces.removeAll(fittedFunctions.getFittedTraces());
                }
                if (traces != null && !traces.isEmpty()) {
                    final int size = updateTracesChoice(traces.get(traces.size() - 1));
                    if (size > 0)
                        fittingJob.fit(true);
                }
            }

            @Override
            public void traceRemoved(TraceEvent evt) {
                if (evt.getSource() instanceof ITrace) {
                    if (!((ITrace) evt.getSource()).isUserTrace())
                        return;
                }
                updateTracesChoice(null);
            }

            @Override
            public void tracesRemoved(TraceEvent evet) {
                if (tracesMenu != null)
                    tracesMenu.clear();
                if (getSite() != null)
                    getSite().getActionBars().updateActionBars();
            }

            @Override
            public void traceAdded(TraceEvent evt) {
                //Get trace from event
                final ITrace trace = evt.getSource() instanceof ITrace ? ((ITrace) evt.getSource()) : null;
                //Ignore event if null
                if (trace == null)
                    return;
                //Ignore if not user trace
                if (!trace.isUserTrace())
                    return;

                if (fittedFunctions != null && !fittedFunctions.isEmpty()) {
                    if (fittedFunctions.getFittedTraces().contains(trace))
                        return;
                }

                final int size = updateTracesChoice(trace);
                if (size > 0)
                    fittingJob.fit(true);

            }
        };

    }

    /**
     * The fitted functions from the table for exporting.
     */
    public List<FittedFunction> getSortedFunctionList() {
        final List<FittedFunction> ret = new ArrayList<FittedFunction>(3);
        for (int i = 0; i < viewer.getTable().getItemCount(); i++) {
            final FittedFunction f = (FittedFunction) viewer.getElementAt(i);
            ret.add(f);
        }
        return ret;
    }

    public void sync(IToolPage with) {
        if (!with.getClass().equals(getClass()))
            return;
        final AbstractFittingTool other = (AbstractFittingTool) with;
        this.fittedFunctions = other.fittedFunctions.clone();
        this.fitRegion = other.fitRegion;
        this.tracesMenu = other.tracesMenu;
        this.selectedTraces = other.selectedTraces;
        viewer.setInput(fittedFunctions);
        viewer.refresh();
        fittingJob.schedule();
    }

    @Override
    public ToolPageRole getToolPageRole() {
        return ToolPageRole.ROLE_1D;
    }

    @Override
    public void createControl(Composite parent) {

        this.composite = new Composite(parent, SWT.NONE);
        composite.setLayout(new GridLayout(1, false));
        GridUtils.removeMargins(composite);

        viewer = new TableViewer(composite,
                SWT.FULL_SELECTION | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        createColumns(viewer);

        FittingViewerComparator vc = new FittingViewerComparator();
        viewer.setComparator(vc);

        for (int i = 0; i < viewer.getTable().getColumnCount(); i++) {
            viewer.getTable().getColumn(i).addSelectionListener(getTableColumnSortListener(vc, i));
        }

        viewer.getTable().setLinesVisible(true);
        viewer.getTable().setHeaderVisible(true);
        viewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
        viewer.setContentProvider(createContentProvider());
        createActions();

        getSite().setSelectionProvider(viewer);

        this.viewUpdateListener = new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                final StructuredSelection sel = (StructuredSelection) event.getSelection();
                if (fittedFunctions != null && sel != null && sel.getFirstElement() != null) {
                    if (sel.getFirstElement() instanceof NullFunction)
                        return;
                    fittedFunctions.setSelectedFit((FittedFunction) sel.getFirstElement());
                    viewer.refresh();
                }
            }
        };
        viewer.addSelectionChangedListener(viewUpdateListener);

        algorithmMessage = new Link(composite, SWT.NONE);
        algorithmMessage.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
        algorithmMessage.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {

                if (e.text != null && e.text.startsWith("configure")) {
                    if (!isActive())
                        return;
                    PreferenceDialog pref = PreferencesUtil.createPreferenceDialogOn(
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                            FittingPreferencePage.ID, null, null);
                    if (pref != null)
                        pref.open();
                } else {

                }
            }
        });
        activate();
    }

    protected SelectionListener getTableColumnSortListener(final FittingViewerComparator vc, final int index) {
        return new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                vc.setColumn(index);
                int dir = vc.getDirection();
                viewer.getTable().setSortDirection(dir);
                viewer.refresh();
            }
        };
    }

    /**
     * Implement method to provide colums with information on the type of fit
     * being done.
     * 
     * @param viewer
     */
    protected abstract List<TableViewerColumn> createColumns(TableViewer viewer);

    private IContentProvider createContentProvider() {
        return new IStructuredContentProvider() {
            @Override
            public void dispose() {
            }

            @Override
            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            }

            @Override
            public Object[] getElements(Object inputElement) {

                if (fittedFunctions == null)
                    return new NullFunction[] { new NullFunction() };
                if (fittedFunctions.size() < 1)
                    return new NullFunction[] { new NullFunction() };

                return fittedFunctions.toArray();
            }
        };
    }

    @Override
    public void activate() {

        if (getPlottingSystem() == null)
            return;
        if (isDisposed())
            return;

        super.activate();
        if (viewer != null && viewer.getControl().isDisposed())
            return;

        if (viewUpdateListener != null)
            viewer.addSelectionChangedListener(viewUpdateListener);
        if (this.traceListener != null)
            getPlottingSystem().addTraceListener(traceListener);
        updateTracesChoice(null);

        createNewFit();
    }

    /**
     * Method to start new selection area for fitting.
     */
    protected void createNewFit() {
        try {
            if (fittedFunctions != null)
                fittedFunctions.activate();
            getPlottingSystem().addRegionListener(this);
            this.fitRegion = getPlottingSystem().createRegion(
                    RegionUtils.getUniqueName("Fit selection", getPlottingSystem()),
                    getPlottingSystem().is2D() ? IRegion.RegionType.BOX : IRegion.RegionType.XAXIS);
            fitRegion.setRegionColor(ColorConstants.green);

            if (viewer != null) {
                viewer.refresh();
            }

        } catch (Exception e) {
            logger.error("Cannot put the selection into fitting region mode!", e);
        }

    }

    @Override
    public void deactivate() {

        super.deactivate();
        if (viewer != null && !viewer.getControl().isDisposed()) {
            if (viewUpdateListener != null)
                viewer.removeSelectionChangedListener(viewUpdateListener);
        }

        if (getPlottingSystem() != null) {
            if (this.traceListener != null)
                getPlottingSystem().removeTraceListener(traceListener);

            try {
                getPlottingSystem().removeRegionListener(this);
                if (fittedFunctions != null)
                    fittedFunctions.deactivate();

            } catch (Exception e) {
                logger.error("Cannot put the selection into fitting region mode!", e);
            }
        }
    }

    @Override
    public void setFocus() {
        if (viewer != null && !viewer.getControl().isDisposed())
            viewer.getControl().setFocus();
    }

    public void dispose() {
        deactivate();

        // Using clear and setting to null helps the garbage collector.
        if (fittedFunctions != null) {
            fittedFunctions.removeSelections(getPlottingSystem(), true);
            fittedFunctions.dispose();
        }
        fittedFunctions = null;

        viewUpdateListener = null;
        selectedTraces.clear();
        if (viewer != null)
            viewer.getControl().dispose();

        super.dispose();
    }

    @Override
    public Control getControl() {
        return composite;
    }

    @Override
    public void regionCreated(RegionEvent evt) {

    }

    @Override
    public void regionCancelled(RegionEvent evt) {
    }

    @Override
    public void regionNameChanged(RegionEvent evt, String oldName) {
        // TODO Auto-generated method stub

    }

    @Override
    public void regionAdded(RegionEvent evt) {
        if (evt == null || evt.getRegion() == null) {
            getPlottingSystem().clearRegions();
            return;
        }
        if (evt.getRegion().getRegionType() == RegionType.XAXIS) {
            fitRegion = evt.getRegion();
            fittingJob.fit(false);
        }
    }

    @Override
    public void regionRemoved(RegionEvent evt) {

    }

    @Override
    public void regionsRemoved(RegionEvent evt) {

    }

    protected void setAddingPeaks(boolean addPeaksToList) {
        this.addingPeaks = addPeaksToList;
    }

    protected final class FittingJob extends Job {

        boolean autoUpdate;
        private List<FittedFunction> previousFittedFunctions;

        public FittingJob() {
            super("Fit peaks");
            setPriority(Job.INTERACTIVE);
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            if (composite == null)
                return Status.CANCEL_STATUS;
            if (composite.isDisposed())
                return Status.CANCEL_STATUS;

            if (fitRegion == null)
                return Status.CANCEL_STATUS;
            RectangularROI bounds = (RectangularROI) fitRegion.getROI();
            if (bounds == null && !autoUpdate)
                return Status.CANCEL_STATUS;
            if (bounds == null && autoUpdate) {
                if (fittedFunctions == null)
                    return Status.CANCEL_STATUS;
                Display.getDefault().syncExec(new Runnable() {
                    @Override
                    public void run() {
                        previousFittedFunctions = new ArrayList<FittedFunction>(fittedFunctions.getFunctionList());
                        Collections.sort(previousFittedFunctions, new SortNatural<FittedFunction>(true));
                        clearAll();
                    }
                });
                for (FittedFunction function : previousFittedFunctions) {
                    IRegion region = function.getFwhm();
                    if (region != null && region.getROI() instanceof RectangularROI) {
                        // change the fwhm by 2xfwhm where
                        // <--width--> becomes <--width/2--><--width--><--width/2-->
                        RectangularROI rectangle = (RectangularROI) region.getROI();
                        double width = rectangle.getLength(0);
                        double xPoint = rectangle.getPointX();
                        double[] startPoint = rectangle.getPoint();
                        double[] endPoint = rectangle.getEndPoint();
                        double newStartXPoint = xPoint - (width / 2);
                        double newEndXPoint = endPoint[0] + (width / 2);
                        rectangle.setPoint(newStartXPoint, startPoint[1]);
                        rectangle.setEndPoint(new double[] { newEndXPoint, endPoint[1] });
                        IStatus status = doFit(rectangle, monitor);
                        if (status == Status.CANCEL_STATUS)
                            return Status.CANCEL_STATUS;
                    }
                }
            } else if (bounds != null)
                return doFit(bounds, monitor);
            return Status.OK_STATUS;
        }

        private IStatus doFit(RectangularROI bounds, IProgressMonitor monitor) {
            setFitBounds(bounds);
            getPlottingSystem().removeRegionListener(AbstractFittingTool.this);

            composite.getDisplay().syncExec(new Runnable() {
                public void run() {
                    getPlottingSystem().removeRegion(fitRegion);
                    if (fittedFunctions != null && !addingPeaks) {
                        fittedFunctions.removeSelections(getPlottingSystem(), false);
                    }
                }
            });
            if (selectedTraces.isEmpty())
                return Status.CANCEL_STATUS;

            if (monitor.isCanceled())
                return Status.CANCEL_STATUS;
            for (ILineTrace selectedTrace : selectedTraces) {

                // We chop x and y by the region bounds. We assume the
                // plot is an XAXIS selection therefore the indices in
                // y = indices chosen in x.
                final double[] p1 = bounds.getPointRef();
                final double[] p2 = bounds.getEndPoint();

                // We peak fit only the first of the data sets plotted for now.
                if (selectedTrace == null || selectedTrace.getXData() == null || selectedTrace.getYAxis() == null)
                    continue;
                Dataset x = (Dataset) selectedTrace.getXData().squeeze();
                Dataset y = (Dataset) selectedTrace.getYData().squeeze();

                if (monitor.isCanceled())
                    break;

                try {
                    Dataset[] a = Generic1DFitter.xintersection(x, y, p1[0], p2[0]);
                    x = a[0];
                    y = a[1];
                } catch (Throwable npe) {
                    logger.debug("Cannot fit!", npe);
                    continue;
                }

                try {
                    final FittedFunctions bean = getFittedFunctions(new FittedPeaksInfo(x, y,
                            new ProgressMonitorWrapper(monitor), getPlottingSystem(), selectedTrace));
                    if (bean != null)
                        for (FittedFunction p : bean.getFunctionList()) {
                            p.setX((Dataset) selectedTrace.getXData());
                            p.setY((Dataset) selectedTrace.getYData());
                            p.setDataTrace(selectedTrace);
                        }
                    // Add saved peaks if any.
                    if (fittedFunctions != null && !fittedFunctions.isEmpty() && bean != null) {
                        bean.addFittedFunctions(fittedFunctions.getFunctionList());
                    }
                    createFittedFunctionUI(bean);
                    pushFunctionsToPlotter();
                } catch (Exception ne) {
                    logger.error("Cannot fit functions!", ne);
                    return Status.CANCEL_STATUS;
                }
            }

            if (addingPeaks)
                composite.getDisplay().syncExec(new Runnable() {
                    public void run() {
                        createNewFit();
                    }
                });
            return Status.OK_STATUS;
        }

        /**
         * 
         * @param autoUpdate
         */
        public void fit(boolean autoUpdate) {
            this.autoUpdate = autoUpdate;
            cancel();
            schedule();
        }
    };

    /**
     * If the plottingsystem is in a PlotView, we push the functions to the GuiBean
     */
    abstract void pushFunctionsToPlotter();

    /**
     * 
     * @param fittedPeaksInfo
     * @return
     */
    protected abstract FittedFunctions getFittedFunctions(FittedPeaksInfo fittedPeaksInfo) throws Exception;

    /**
     * Creates specific UI for the function.
     * @param newBean
     */
    protected abstract void createFittedFunctionUI(final FittedFunctions newBean);

    /**
     * Used by the wizard to write the tools results to a file.
     * @param path
     * @return
     * @throws Exception
     */
    abstract String exportFittedData(final String path) throws Exception;

    protected int updateTracesChoice(ITrace selected) {

        if (tracesMenu == null)
            return 0;

        tracesMenu.clear();

        final Collection<ITrace> traces = getPlottingSystem().getTraces();
        if (traces == null || traces.size() < 0)
            return 0;
        if (fittedFunctions != null)
            traces.removeAll(fittedFunctions.getFittedTraces());

        selectedTraces.clear();
        int index = 0;
        //int selectionIndex=0;

        if (traces.size() > 3) {
            final Action selectAll = new Action("Select all", IAction.AS_PUSH_BUTTON) {
                public void run() {
                    selectedTraces.clear();
                    for (int i = 2; i < tracesMenu.size(); i++) {
                        TraceSelectAction ta = (TraceSelectAction) tracesMenu.getAction(i);
                        ta.setChecked(true);
                        selectedTraces.add(ta.iTrace);
                    }
                    if (fittingJob != null && isActive()) {
                        fittingJob.fit(false);
                    }
                }
            };
            tracesMenu.add(selectAll);
            final Action selectNone = new Action("Select none", IAction.AS_PUSH_BUTTON) {
                public void run() {
                    clearAll();
                    selectedTraces.clear();
                    for (int i = 2; i < tracesMenu.size(); i++) {
                        TraceSelectAction ta = (TraceSelectAction) tracesMenu.getAction(i);
                        ta.setChecked(false);
                    }
                    if (fittingJob != null && isActive()) {
                        fittingJob.fit(false);
                    }
                }
            };
            tracesMenu.add(selectNone);
        }

        for (final ITrace iTrace : traces) {
            if (!(iTrace instanceof ILineTrace))
                continue;

            final ILineTrace lineTrace = (ILineTrace) iTrace;

            if (!lineTrace.isUserTrace())
                continue;

            //if no trace selected, use first valid trace
            if (selected == null)
                selected = lineTrace;

            //Make the selected trace the fitted trace
            if (iTrace == selected) {
                selectedTraces.add(lineTrace);
            }

            final Action action = new TraceSelectAction(lineTrace);

            if (iTrace == selected) {
                action.setChecked(true);
            }
            tracesMenu.add(action);

            index++;
        }

        getSite().getActionBars().updateActionBars();

        return index;
    }

    private class TraceSelectAction extends Action {
        ILineTrace iTrace;

        TraceSelectAction(ILineTrace iTrace) {
            super(iTrace.getName(), IAction.AS_CHECK_BOX);
            this.iTrace = iTrace;
        }

        public void run() {
            if (iTrace instanceof ILineTrace) {
                ILineTrace lt = (ILineTrace) iTrace;
                if (isChecked()) {
                    selectedTraces.add(lt);
                } else {
                    selectedTraces.remove(lt);
                }
            }
            tracesMenu.setSelectedAction(this);
            if (fittingJob != null && isActive()) {
                fittingJob.fit(false);
            }
        }
    };

    /**
     * Called to create actions for this tool.
     */
    protected abstract void createActions();

    protected void clearAll() {
        if (!isActive())
            return;
        if (fittedFunctions != null) {
            fittedFunctions.removeSelections(getPlottingSystem(), true);
            fittedFunctions.dispose();
            fittedFunctions = null;

            pushFunctionsToPlotter();
        }

        // We sometimes get lagging regions, delete these too.
        final Collection<IRegion> regions = getPlottingSystem().getRegions();
        for (IRegion iRegion : regions) {
            if (iRegion.getUserObject() == FittedFunction.class) {
                getPlottingSystem().removeRegion(iRegion);
            }
        }

        viewer.refresh();
    }

    public void setFittedFunctions(FittedFunctions fittedFunctions) {
        this.fittedFunctions = fittedFunctions;
    }

    public RectangularROI getFitBounds() {
        return fitBounds;
    }

    public void setFitBounds(RectangularROI fitBounds) {
        this.fitBounds = fitBounds;
    }

    @Override
    public Serializable getToolData() {

        if (fittedFunctions == null)
            return null;
        final FunctionSquirts fs = new FunctionSquirts();

        for (FittedFunction ff : fittedFunctions.getFunctionList()) {
            fs.addSquirt(getSquirt(ff));
        }
        fs.setSelected(getSquirt(fittedFunctions.getSelectedPeak()));

        return fs;
    }

    private Squirt getSquirt(FittedFunction ff) {
        if (ff == null)
            return null;
        final Squirt skwert = new Squirt();
        skwert.setBounds(ff.getRoi());
        skwert.setFunction(ff.getFunction());
        skwert.setName(ff.getPeakName());
        skwert.setPeakFunctions(ff.getPeakFunctions());
        skwert.setRegions(ff.getRegions());
        skwert.setX(ff.getX());
        skwert.setY(ff.getY());
        return skwert;
    }

}