/*- ******************************************************************************* * Copyright (c) 2011, 2014 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 * * Contributors: * Matthew Gerring - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.dawnsci.analysis.dataset.operations; import java.io.Serializable; import java.util.Arrays; import java.util.List; 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.IOperationModel; import org.eclipse.dawnsci.analysis.dataset.slicer.SliceFromSeriesMetadata; import org.eclipse.january.IMonitor; import org.eclipse.january.MetadataException; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.ILazyDataset; import org.eclipse.january.dataset.ShapeUtils; import org.eclipse.january.metadata.AxesMetadata; import org.eclipse.january.metadata.MetadataFactory; /** * Abstract implementation of IOperation. * * Simplest method to add a new operation is to extend this class and override the process method, which receives either 1D or 2D * datasets and should return either 2D or 1D datasets with correctly configured axesmetadata, and single value datasets of shape [1] * as auxiliary data. * * Overriding execute gives access to the unsqueezed data (rank the same as the initial dataset being processed) and all axes items, * but care must be taken to maintain the size 1 dimensions and axes, which the process method does for you. * * Should return OperationData unless there is a very good reason to extend it. * * @param <T> * @param <D> */ public abstract class AbstractOperation<T extends IOperationModel, D extends OperationData> extends AbstractOperationBase<T, D> { protected abstract OperationData process(IDataset input, IMonitor monitor) throws OperationException; @Override public D execute(IDataset slice, IMonitor monitor) throws OperationException { IDataset view = slice.getSliceView().squeeze(); //check mandatory metadata SliceFromSeriesMetadata ssm = getSliceSeriesMetadata(view); if (ssm == null) throw new OperationException(this, "Metadata not present, contact support"); OperationData output = process(view,monitor); if (output == null) return null; return updateOutputToFullRank(output, slice); } /** * Pads the output dataset (and axes) to the match the rank of the input dataset, accounting for any loss of dimensionality * in the process. Suitable only for 2D and 1D datasets * * @param output * @param original * @return <D> * @throws OperationException */ private D updateOutputToFullRank(OperationData output, IDataset original) throws OperationException { int outr = output.getData().getRank(); int inr = original.getRank(); //Check ranks acceptable for this step if (getOutputRank().equals(OperationRank.ZERO) || getOutputRank().equals(OperationRank.NONE) || getOutputRank().getRank() > 2) throw new OperationException(null, "Invalid Operation Rank!"); int rankDif = 0; if (!getOutputRank().equals(OperationRank.SAME)) { // TODO: a case that is currently not covered is ANY -> ANY... e.g. a sum operation that can deal with any rank will return that rank minus one if (getInputRank().equals(OperationRank.ANY) && getOutputRank().equals(OperationRank.ANY)) { throw new OperationException(this, "Both Operation Rangs set to ANY is currently not supported"); } else if (getInputRank().equals(OperationRank.ANY)) { rankDif = ShapeUtils.squeezeShape(original.getShape(), false).length - getOutputRank().getRank(); } else { rankDif = getInputRank().getRank() - getOutputRank().getRank(); } } //Single image/line case, nothing to alter if (inr == outr) return (D)output; List<AxesMetadata> metadata = null; try { metadata = original.getMetadata(AxesMetadata.class); } catch (Exception e) { throw new OperationException(this, e); } //Clone and sort dimensions for searching int[] datadims = getOriginalDataDimensions(original).clone(); int[] oddims = datadims.clone(); Arrays.sort(datadims); Arrays.sort(oddims); if (datadims.length > outr) { datadims = new int[]{datadims[0]}; } //Update rank of dataset (will automatically update rank of axes) updateOutputDataShape(output.getData(), inr-rankDif, datadims, rankDif); updateAxes(output.getData(),original,metadata,rankDif, datadims, oddims); updateAuxData(output.getAuxData(), original); return (D)output; } /** * Update the rank of the output data, and its axes to be consistent with the input * * @param out * @param rank * @param dataDims * @param rankDif */ private void updateOutputDataShape(IDataset out, int rank, int[] dataDims, int rankDif) { int[] shape = out.getSliceView().squeeze().getShape(); int[] updated = new int[rank]; Arrays.fill(updated, 1); if (rankDif == 0) { //1D-1D or 2D - 2D for (int i = 0; i< shape.length; i++) updated[dataDims[i]] = shape[i]; } else if ( rankDif > 0) { //1D || 0D if (shape.length != 0) updated[dataDims[0]] = shape[0]; } else if (rankDif < 0) { //2D from 1D updated[dataDims[0]] = shape[0]; updated[updated.length-1] = shape[1]; } for (int i = 0 ; i < Math.min(dataDims.length,shape.length); i++) { updated[dataDims[i]] = shape[i]; } out.setShape(updated); } /** * Add missing axes to the updated rank axes metadata * * @param output * @param original * @param ometadata * @param rankDif * @param datadims */ private void updateAxes(IDataset output, IDataset original, List<AxesMetadata> ometadata, int rankDif, int[] datadims, int[] odatadim) { if (ometadata != null && !ometadata.isEmpty() && ometadata.get(0) != null) { List<AxesMetadata> metaout = null; //update it all for new data; try { metaout = output.getMetadata(AxesMetadata.class); } catch (Exception e) { throw new OperationException(this, e); } AxesMetadata inMeta = ometadata.get(0); AxesMetadata axOut = null; if (metaout != null && !metaout.isEmpty()) axOut = metaout.get(0); if (axOut == null) { try { axOut = MetadataFactory.createMetadata(AxesMetadata.class, original.getRank() - rankDif); } catch (MetadataException e) { throw new OperationException(this, e); } } //Clone to get copies of lazy datasets AxesMetadata cloneMeta = (AxesMetadata) inMeta.clone(); if (rankDif == 0) { for (int i = 0; i< original.getRank(); i++) { if (Arrays.binarySearch(datadims, i) < 0 && Arrays.binarySearch(odatadim, i)<0) { ILazyDataset[] axis = cloneMeta.getAxis(i); if (axis != null) axOut.setAxis(i, axis); } } } else { int j = 0; int[] shape = new int[output.getRank()]; Arrays.fill(shape, 1); for (int i = 0; i< original.getRank(); i++) { if (Arrays.binarySearch(odatadim, i) < 0) { if (Arrays.binarySearch(datadims, i) < 0) { ILazyDataset[] axis = cloneMeta.getAxis(i); if (axis != null) { for (ILazyDataset ax : axis) if (ax != null) ax.setShape(shape); axOut.setAxis(i+j, cloneMeta.getAxis(i)); } } } else { j--; } } } output.setMetadata(axOut); } } /** * Updates the auxiliary data (of shape [1]) to correct rank and adds all axes of rank zero (when squeezed) * @param auxData * @param original */ private void updateAuxData(Serializable[] auxData, IDataset original){ if (auxData == null || auxData.length == 0 || auxData[0] == null) return; List<AxesMetadata> metadata = null; try { metadata = original.getMetadata(AxesMetadata.class); } catch (Exception e) { throw new OperationException(this, e); } for (int i = 0; i < auxData.length; i++) { if (!(auxData[i] instanceof IDataset)) { continue; } IDataset ds = (IDataset)auxData[i]; int outr = ds.getRank(); int inr = original.getRank(); int rankDif = 0; if (!getOutputRank().equals(OperationRank.SAME)) { if (getInputRank().equals(OperationRank.ANY)) { rankDif = ShapeUtils.squeezeShape(original.getShape(), false).length - outr; } else { rankDif = getInputRank().getRank() - outr; } } int[] datadims = getOriginalDataDimensions(original).clone(); int[] oddims = datadims.clone(); if (datadims.length > outr) { datadims = new int[]{datadims[0]}; } Arrays.sort(datadims); Arrays.sort(oddims); updateOutputDataShape(ds, inr-rankDif, datadims, rankDif); updateAxes(ds,original,metadata,rankDif, datadims, oddims); } } }