/*- * Copyright (c) 2016 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.saxs; //import org.apache.commons.lang.ArrayUtils; // Imports from org.eclipse import org.eclipse.january.IMonitor; import org.eclipse.january.MetadataException; import org.eclipse.january.dataset.Dataset; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.DatasetUtils; import org.eclipse.january.dataset.FloatDataset; import org.eclipse.january.dataset.DoubleDataset; import org.eclipse.january.dataset.DatasetFactory; import org.eclipse.january.dataset.IndexIterator; import org.eclipse.january.metadata.AxesMetadata; import org.eclipse.january.metadata.MaskMetadata; import org.eclipse.january.metadata.MetadataFactory; import org.eclipse.dawnsci.analysis.dataset.roi.RingROI; import org.eclipse.dawnsci.analysis.dataset.roi.SectorROI; import org.eclipse.dawnsci.analysis.api.processing.OperationData; import org.eclipse.dawnsci.analysis.api.processing.OperationRank; import org.eclipse.dawnsci.analysis.api.processing.OperationException; import org.eclipse.dawnsci.analysis.dataset.operations.AbstractOperation; import uk.ac.diamond.scisoft.analysis.processing.operations.saxs.HermanOrientationModel.NumberOfPis; // Imports from uk.ac.diamond import uk.ac.diamond.scisoft.analysis.roi.ROIProfile; // More information and the equation for the Herman Orientation Factor can be found in: // Crystallization and orientation studies in polypropylene/single wall carbon nanotube composite // A. R. Bhattacharyya, T. Sreekumar, T. Liu, S. Kumar, L. M. Ericson, R. H. Hauge and R. E. Smalley, Polymer, 2003, 44, 2373-2377. // DOI: 10.1016/S0032-3861(03)00073-9 // @author Tim Snow // The operation for a DAWN process to perform a Herman Orientation calculation on a given image public class HermanOrientationOperation extends AbstractOperation<HermanOrientationModel, OperationData> { // Let's give this process an ID tag @Override public String getId() { return "uk.ac.diamond.scisoft.analysis.processing.operations.saxs.HermanOrientationOperation"; } // Before we start, let's make sure we know how many dimensions of data are going in... @Override public OperationRank getInputRank() { return OperationRank.TWO; } // ...and out @Override public OperationRank getOutputRank() { return OperationRank.ONE; } // Now let's define the main calculation process @Override public OperationData process(IDataset dataset, IMonitor monitor) throws OperationException { // Lets apply an image mask, if present, using NaN's Dataset nanMaskDataset = DatasetUtils.convertToDataset(dataset); // If there is masking data we shall replaced the masked values by NaN's Object nanMaskValue = Double.NaN; // Better make sure it's the right type of NaN though if (dataset.getFirstMetadata(MaskMetadata.class) != null) { if (nanMaskDataset.getClass() == DoubleDataset.class) nanMaskValue = Double.NaN; else if (nanMaskDataset.getClass() == FloatDataset.class) nanMaskValue = Float.NaN; else nanMaskValue = 0; // Loop over the mask and the data and replace masked values by the NaN type chosen above Dataset mask = DatasetUtils.convertToDataset(dataset.getFirstMetadata(MaskMetadata.class).getMask()); for (IndexIterator iter = nanMaskDataset.getIterator(); iter.hasNext();) { if (!(boolean) mask.getElementBooleanAbs(iter.index)) nanMaskDataset.setObjectAbs(iter.index, nanMaskValue); } } // Now any masked pixel has the value NaN and will not be considered for subsequent evaluation // With this in mind, let's move on to the ROI that the HoF calculation will be performed on // First, let's check we've got the right kind of ROI if (!(model.getRegion() instanceof RingROI)) { throw new OperationException(this, new IllegalArgumentException("The ROI must be a ring ROI")); } SectorROI hermanSector = new SectorROI(); RingROI modelRingROI = (RingROI) model.getRegion(); // Then we'll set the centre point double[] centrePoint = modelRingROI.getPoint(); hermanSector.setPoint(centrePoint[0], centrePoint[1]); // Then set the radius of interest double[] radiiPoint = modelRingROI.getRadii(); hermanSector.setRadii(radiiPoint[0], radiiPoint[1]); // And finally, let's consider how much of the ring we're going to be evaluating as a function of pi NumberOfPis piEnum = model.getIntegrationRange(); double piMultiplier = ((double) piEnum.getNumberOfPis()) / 2; double hermanPiRange = piMultiplier * Math.PI; // Before setting the angle to investigate double integrationStartInRadians = (Math.PI / 180) * model.getIntegrationStartAngle(); hermanSector.setAngles(integrationStartInRadians, integrationStartInRadians + hermanPiRange); // Ok, with the mask applied and the ROI defined it's time to reduce the data Dataset[] reducedDataset = ROIProfile.sector(DatasetUtils.convertToDataset(dataset), null, hermanSector, false, true, false); // Then we can take the data and turn it into single dataset IDataset reducedData = reducedDataset[1]; // Let's find out how many points of data we have to work with int dataSize = reducedData.getSize(); // For plotting later, we should prepare to have the x axis values to hand... Dataset xAxisValues = DatasetFactory.createRange(dataSize, Dataset.FLOAT64); xAxisValues.setName("Angle (°)"); // Set up the data value array double integrationRadianStep = hermanPiRange / dataSize; double fractionNumerator = 0.00; double fractionDenominator = 0.00; double loopStepRadianValue = 0.00; double loopStepDegreeValue = 0.00; // Now let's set up the final variables required for the calculation double hermanCReciprocal = 1 / model.getHermanCValue(); // Do the HoF mathematics // Level two of the loop, this is to loop through the data points in the frame for(int loopIter = 0; loopIter < dataSize; loopIter++) { // To save this from being calculated many times loopStepRadianValue = integrationRadianStep * loopIter; loopStepDegreeValue = loopStepRadianValue / (Math.PI / 180); // The component parts of the fraction fractionNumerator += Math.pow(Math.cos(loopStepRadianValue), 2) * Math.sin(loopStepRadianValue) * reducedData.getDouble(loopIter); fractionDenominator += reducedData.getDouble(loopIter) * Math.sin(loopStepRadianValue); xAxisValues.set(loopStepDegreeValue, loopIter); } // Perform the calculation for this frame double hermanOrientationFactor = hermanCReciprocal * (((3 * (fractionNumerator / fractionDenominator)) - 1) / 2); // Must move the HoF into a dataset for DAWN // First up, let's create a one element dataset of a zero int[] datasetSize = {1}; Dataset hermanOrientationDataset = DatasetFactory.zeros(1, datasetSize, Dataset.FLOAT64); hermanOrientationDataset.setName("Herman Orientation Factor"); // Now we can stick in the calculated factor hermanOrientationDataset.set(hermanOrientationFactor, 0); // Also, as we have the x axis values and the reduced data, let's set up the plot axes for the user to look at AxesMetadata metadata = dataset.getFirstMetadata(AxesMetadata.class); // First up, if there's no metadata yet, let's make a space for some if (metadata == null) { try { metadata = MetadataFactory.createMetadata(AxesMetadata.class, 1); } catch (MetadataException e) { throw new OperationException(this, e); } metadata.setAxis(0, DatasetFactory.createRange(dataSize, Dataset.FLOAT64)); } // If there's no data, or if it's of zero length, let's correct that if (metadata.getAxis(0) == null || metadata.getAxis(0).length == 0) { metadata.setAxis(0, DatasetFactory.createRange(dataSize, Dataset.FLOAT64)); } // Now we can set the axis values to those calculated metadata.setAxis(0, xAxisValues); // And stick this metadata into the reduced data reducedData.setMetadata(metadata); // Give the data axis a name too reducedData.setName("Intensity (Counts)"); // Finally, we can create a new OperationData object for DAWN and return the Herman Orientation Factor OperationData toReturn = new OperationData(); // Fill it toReturn.setData(reducedData); toReturn.setAuxData(hermanOrientationDataset); // And then return it return toReturn; } }