/*
* 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.eclipse.dawnsci.analysis.dataset.function;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.dawnsci.analysis.api.downsample.DownsampleMode;
import org.eclipse.dawnsci.analysis.api.downsample.IDownsampleService;
import org.eclipse.dawnsci.analysis.dataset.impl.function.DatasetToDatasetFunction;
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.IndexIterator;
import org.eclipse.january.dataset.RGBDataset;
import org.eclipse.january.dataset.SliceIterator;
/**
* Down-sample a dataset by a given bin. Also is the implementation of a service
* so instead of using this class directly, one can consume the IDownsampleService
* from OSGi thus not making a hard dependency on uk.ac.diamond.scisoft.analysis
*/
public class Downsample implements DatasetToDatasetFunction, IDownsampleService {
private final DownsampleMode mode;
private final int[] bshape; // bin shape
/**
* Creates a default Downsample set to MAXIMUM with a 2x2 bin.
* Called by OSGi when contributing the IDownsampleService
*/
public Downsample() {
this(DownsampleMode.MAXIMUM, 2, 2);
}
/**
* This class down-samples (or subsamples) datasets according to given mode and sample bin shape
*
* {@code mode} can be POINT, MEAN, MAXIMUM
* @param mode
* @param binShape
*/
public Downsample(DownsampleMode mode, int... binShape) {
if (binShape.length == 0) {
throw new IllegalArgumentException("Zero-dimensional bin not allowed");
}
for (int b : binShape) {
if (b <= 0)
throw new IllegalArgumentException("Bin side must be greater than zero");
}
bshape = binShape;
this.mode = mode;
}
@Override
public List<Dataset> downsample(String enc, IDataset... data) throws Exception {
Downsample d = Downsample.decode(enc);
return d.value(data);
}
@Override
public List<Dataset> downsample(DownsampleMode mode, int[] binShape, IDataset... data) throws Exception{
Downsample d = new Downsample(mode, binShape);
return d.value(data);
}
/**
* The class implements down-sampling of datasets
*
* @param datasets
* @return a list of down-sampled datasets
*/
@Override
public List<Dataset> value(IDataset... datasets) {
if (datasets.length == 0)
return null;
List<Dataset> result = new ArrayList<Dataset>();
int brank = bshape.length;
for (IDataset idataset : datasets) {
Dataset dataset = DatasetUtils.convertToDataset(idataset);
final int[] dshape = dataset.getShape();
final int drank = dshape.length;
final int[] lbshape = new int[drank];
for (int i = 0; i < drank; i++) {
lbshape[i] = i < brank ? bshape[i] : 1; // adjust binning to dataset shape
}
final int[] shape = new int[drank];
for (int i = 0; i < drank; i++) {
shape[i] = (dshape[i] + bshape[i] - 1)/bshape[i];
}
final Dataset binned;
DownsampleMode m = mode;
if (dataset instanceof RGBDataset) {
binned = DatasetFactory.zeros(RGBDataset.class, shape);
// set the mode to point whenever RGB datasets are involved for the moment.
m = DownsampleMode.POINT;
} else {
binned = DatasetFactory.zeros(dataset.getElementsPerItem(), shape, dataset.getDType());
}
final IndexIterator biter = binned.getIterator(true);
final int[] bpos = biter.getPos();
final int[] spos = new int[drank];
final int[] epos = new int[drank];
final int isize = binned.getElementsPerItem();
// TODO In Java8 switch these loops to using ParallelStreams
switch (m) {
case POINT:
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i]*bpos[i];
}
binned.setObjectAbs(biter.index, dataset.getObject(spos));
}
break;
case MEAN:
if (isize == 1) {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i]) // ensure bin is within dataset
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
double mean = 0;
int num = 0;
while (siter.hasNext()) {
final double val = dataset.getElementDoubleAbs(siter.index);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
num++;
final double delta = val - mean;
mean += delta / num;
}
binned.setObjectAbs(biter.index, mean);
}
} else {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i])
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
final double[] mean = new double[isize];
int num = 0;
while (siter.hasNext()) {
for (int i = 0; i < isize; i++) {
final double val = dataset.getElementDoubleAbs(siter.index + i);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
num++;
final double delta = val - mean[i];
mean[i] += delta / num;
}
}
binned.setObjectAbs(biter.index, mean);
}
}
break;
case MAXIMUM:
if (isize == 1) {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i])
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
double max = Double.NEGATIVE_INFINITY;
while (siter.hasNext()) {
final double val = dataset.getElementDoubleAbs(siter.index);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
if (val > max)
max = val;
}
binned.setObjectAbs(biter.index, max);
}
} else {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i])
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
final double[] max = new double[isize];
for (int i = 0; i < isize; i++)
max[i] = Double.NEGATIVE_INFINITY;
while (siter.hasNext()) {
for (int i = 0; i < isize; i++) {
final double val = dataset.getElementDoubleAbs(siter.index + i);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
if (val > max[i])
max[i] = val;
}
}
binned.setObjectAbs(biter.index, max);
}
}
break;
case MINIMUM:
if (isize == 1) {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i])
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
double min = Double.POSITIVE_INFINITY;
while (siter.hasNext()) {
final double val = dataset.getElementDoubleAbs(siter.index);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
if (val < min)
min = val;
}
binned.setObjectAbs(biter.index, min);
}
} else {
while (biter.hasNext()) {
for (int i = 0; i < drank; i++) {
spos[i] = bshape[i] * bpos[i];
epos[i] = spos[i] + bshape[i];
if (epos[i] > dshape[i])
epos[i] = dshape[i];
}
SliceIterator siter = (SliceIterator) dataset.getSliceIterator(spos, epos, null);
final double[] min = new double[isize];
for (int i = 0; i < isize; i++)
min[i] = Double.POSITIVE_INFINITY;
while (siter.hasNext()) {
for (int i = 0; i < isize; i++) {
final double val = dataset.getElementDoubleAbs(siter.index + i);
if (Double.isInfinite(val) || Double.isNaN(val))
continue;
if (val < min[i])
min[i] = val;
}
}
binned.setObjectAbs(biter.index, min);
}
}
break;
}
result.add(binned);
}
return result;
}
public DownsampleMode getMode() {
return mode;
}
public int[] getBshape() {
return bshape;
}
public static String encode(Downsample s) {
StringBuilder buf = new StringBuilder();
buf.append(s.mode.name());
buf.append(":");
for (int i = 0; i < s.bshape.length; i++) {
buf.append(s.bshape[i]);
if (i<s.bshape.length-1) buf.append("x");
}
return buf.toString();
}
public static Downsample decode(String enc) {
final String[] sa = enc.split("\\:");
DownsampleMode dt = DownsampleMode.valueOf(sa[0]);
final String[] vals = sa[1].split("x");
final int[] bshape = new int[vals.length];
for (int i = 0; i < vals.length; i++) bshape[i] = Integer.parseInt(vals[i]);
return new Downsample(dt, bshape);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(bshape);
result = prime * result + ((mode == null) ? 0 : mode.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Downsample other = (Downsample) obj;
if (!Arrays.equals(bshape, other.bshape))
return false;
if (mode != other.mode)
return false;
return true;
}
}