uk.ac.diamond.scisoft.analysis.dataset.AbstractDataset.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.diamond.scisoft.analysis.dataset.AbstractDataset.java

Source

/*
 * Copyright 2011 Diamond Light Source Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.ac.diamond.scisoft.analysis.dataset;

import gda.analysis.io.ScanFileHolderException;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math.complex.Complex;
import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatistic;
import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import org.apache.commons.math.stat.descriptive.moment.Variance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.ac.diamond.scisoft.analysis.io.IMetaData;
import uk.ac.gda.monitor.IMonitor;

/**
 * Generic container class for data 
 * <p/>
 * Each subclass has an array of primitive types, elements of this array are grouped or
 * compounded to make items 
 * <p/>
 * Data items can be boolean, integer, float, complex float, vector float, etc
 */
public abstract class AbstractDataset implements IDataset {
    /**
     * Update this when there are any serious changes to API
     */
    protected static final long serialVersionUID = -6891075135217265625L;

    /**
     * Setup the logging facilities
     */
    transient protected static final Logger abstractLogger = LoggerFactory.getLogger(AbstractDataset.class);

    /**
     * Boolean
     */
    public static final int BOOL = 0;
    /**
     * Signed 8-bit integer
     */
    public static final int INT8 = 1;
    /**
     * Signed 16-bit integer
     */
    public static final int INT16 = 2;
    /**
     * Signed 32-bit integer
     */
    public static final int INT32 = 3;
    /**
     * Integer (same as signed 32-bit integer)
     */
    public static final int INT = INT32;
    /**
     * Signed 64-bit integer
     */
    public static final int INT64 = 4;
    /**
     * 32-bit floating point
     */
    public static final int FLOAT32 = 5;
    /**
     * 64-bit floating point
     */
    public static final int FLOAT64 = 6;
    /**
     * Floating point (same as 64-bit floating point)
     */
    public static final int FLOAT = FLOAT64;
    /**
     * 64-bit complex floating point (real and imaginary parts are 32-bit floats)
     */
    public static final int COMPLEX64 = 7;
    /**
     * 128-bit complex floating point (real and imaginary parts are 64-bit floats)
     */
    public static final int COMPLEX128 = 8;
    /**
     * Complex floating point (same as 64-bit floating point)
     */
    public static final int COMPLEX = COMPLEX128;

    /**
     * String
     */
    public static final int STRING = 9;

    /**
     * Object
     */
    public static final int OBJECT = 10;

    private static final int ARRAYMUL = 100;

    /**
     * Array of signed 8-bit integers
     */
    public static final int ARRAYINT8 = ARRAYMUL * INT8;
    /**
     * Array of signed 16-bit integers
     */
    public static final int ARRAYINT16 = ARRAYMUL * INT16;
    /**
     * Array of three signed 16-bit integers for RGB values
     */
    public static final int RGB = ARRAYINT16 + 3;
    /**
     * Array of signed 32-bit integers
     */
    public static final int ARRAYINT32 = ARRAYMUL * INT32;
    /**
     * Array of signed 64-bit integers
     */
    public static final int ARRAYINT64 = ARRAYMUL * INT64;
    /**
     * Array of 32-bit floating points
     */
    public static final int ARRAYFLOAT32 = ARRAYMUL * FLOAT32;
    /**
     * Array of 64-bit floating points
     */
    public static final int ARRAYFLOAT64 = ARRAYMUL * FLOAT64;

    private static boolean isDTypeElemental(int dtype) {
        return (dtype <= COMPLEX128 || dtype == RGB);
    }

    /**
     * Limit to strings output via the toString() method
     */
    private static final int MAX_STRING_LENGTH = 120;

    /**
     * Limit to number of sub-blocks output via the toString() method
     */
    private static final int MAX_SUBBLOCKS = 6;

    private static final float ARRAY_ALLOCATION_EXTENSION = 0.5f;

    /**
     * The shape or dimensions of the dataset
     */
    protected int[] shape;
    protected int size; // number of items, this can be smaller than dataSize for discontiguous datasets

    /**
     * The shape of the entire dataset memory footprint
     */
    protected int[] dataShape;
    protected int dataSize; // true size of data

    /**
     * The data itself, held in a 1D array, but the object will wrap it to appear as possessing as many dimensions as
     * wanted
     */
    protected Serializable odata = null;

    /**
     * Set aliased data as base data
     */
    abstract protected void setData();

    protected String name = "";

    /**
     * These members hold cached values. If their values are null, then recalculate, otherwise just use the values
     */
    transient protected HashMap<String, Object> storedValues = null;

    /**
     * Holds metadata as map from strings
     */
    @Deprecated
    protected Map<String, ? extends Serializable> metadata;

    /**
     * @return metadata map
     * @deprecated Use setMetadata(IMetaData metadata) and create an object that extends IMetadata as the argument
     */
    @Deprecated
    public Map<String, ? extends Serializable> getMetadataMap() {
        return metadata;
    }

    /**
     * @param key
     * @return an object
     * @deprecated Use getMetaData() which returns a IMetaData object
     */
    @Deprecated
    public Object getMetadata(String key) {
        if (metadata == null) {
            return null;
        }
        return metadata.get(key);
    }

    /**
     * @param metadata
     *            map
     * @deprecated Use setMetadata(IMetaData metadata) and create an object 
     * that extends IMetadata as the argument
     */
    @Deprecated
    public void setMetadataMap(Map<String, ? extends Serializable> metadata) {
        this.metadata = metadata;
    }

    /**
     * Make a (shallow) copy of metadata map
     * 
     * @param metadata
     *            map
     * @return copied map
     */
    @Deprecated
    public static Map<String, ? extends Serializable> copyMetadataMap(
            Map<String, ? extends Serializable> metadata) {
        Map<String, Serializable> copy = null;
        if (metadata != null) {
            copy = new HashMap<String, Serializable>();
            copy.putAll(metadata);
        }
        return copy;
    }

    /**
     * @param key
     * @param value
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public void setMetadata(String key, Serializable value) {
        if (metadata == null) {
            metadata = new HashMap<String, Serializable>();
        }
        ((Map<String, Serializable>) metadata).put(key, value);
    }

    /**
     * This dictates whether a dataset is allowed to be extended with a setting at a position outside of dataset's shape
     */
    protected boolean extendible = true;

    /**
     * @return true if dataset is extendible
     */
    public boolean isExtendible() {
        return extendible;
    }

    /**
     * Set extendibility of dataset
     * 
     * @param extendible
     */
    public void setExtendible(boolean extendible) {
        this.extendible = extendible;
    }

    /**
     * Constructor required for serialisation.
     */
    public AbstractDataset() {
    }

    /**
     * This is a <b>synchronized</b> version of the clone method
     * 
     * @return a copy of dataset
     */
    public synchronized AbstractDataset synchronizedCopy() {
        return clone();
    }

    /**
     * @return true if dataset has same shape and data values
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!getClass().equals(obj.getClass())) {
            if (getRank() == 0) // for "scalar" datasets
                return obj.equals(getObject());
            return false;
        }

        AbstractDataset other = (AbstractDataset) obj;
        if (getElementsPerItem() != other.getElementsPerItem())
            return false;
        if (!Arrays.equals(shape, other.shape)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return (Integer) getMaxMin("hash");
    }

    @Override
    public AbstractDataset clone() {
        AbstractDataset c = null;
        try {
            // copy across the data
            switch (getDtype()) {
            case BOOL:
                c = new BooleanDataset((BooleanDataset) this);
                break;
            case INT8:
                c = new ByteDataset((ByteDataset) this);
                break;
            case INT16:
                c = new ShortDataset((ShortDataset) this);
                break;
            case INT32:
                c = new IntegerDataset((IntegerDataset) this);
                break;
            case INT64:
                c = new LongDataset((LongDataset) this);
                break;
            case ARRAYINT8:
                c = new CompoundByteDataset((CompoundByteDataset) this);
                break;
            case ARRAYINT16:
                c = new CompoundShortDataset((CompoundShortDataset) this);
                break;
            case ARRAYINT32:
                c = new CompoundIntegerDataset((CompoundIntegerDataset) this);
                break;
            case ARRAYINT64:
                c = new CompoundLongDataset((CompoundLongDataset) this);
                break;
            case FLOAT32:
                c = new FloatDataset((FloatDataset) this);
                break;
            case FLOAT64:
                c = new DoubleDataset((DoubleDataset) this);
                break;
            case ARRAYFLOAT32:
                c = new CompoundFloatDataset((CompoundFloatDataset) this);
                break;
            case ARRAYFLOAT64:
                c = new CompoundDoubleDataset((CompoundDoubleDataset) this);
                break;
            case COMPLEX64:
                c = new ComplexFloatDataset((ComplexFloatDataset) this);
                break;
            case COMPLEX128:
                c = new ComplexDoubleDataset((ComplexDoubleDataset) this);
                break;
            default:
                abstractLogger.error("Dataset of unknown type!");
                break;
            }
        } catch (OutOfMemoryError e) {
            throw new OutOfMemoryError("Not enough memory available to create dataset");
        }

        return c;
    }

    /**
     * Cast a dataset
     * 
     * @param dtype
     *            dataset type
     * @return a converted dataset
     */
    public AbstractDataset cast(final int dtype) {
        if (getDtype() == dtype) {
            return this;
        }
        return DatasetUtils.cast(this, dtype);
    }

    /**
     * Cast a dataset
     * 
     * @param repeat
     * @param dtype
     *            dataset type
     * @param isize
     *            item size
     * @return a converted dataset
     */
    public AbstractDataset cast(final boolean repeat, final int dtype, final int isize) {
        if (getDtype() == dtype && getElementsPerItem() == isize) {
            return this;
        }
        return DatasetUtils.cast(this, repeat, dtype, isize);
    }

    /**
     * @return whole view of dataset (i.e. data buffer is shared)
     */
    public abstract AbstractDataset getView();

    /**
     * Generate an index dataset for current dataset
     * 
     * @return an index dataset
     */
    public IntegerDataset getIndices() {
        final IntegerDataset ret = DatasetUtils.indices(shape);
        if (getName() != null) {
            ret.setName("Indices of " + getName());
        }
        return ret;
    }

    /**
     * Permute copy of dataset's axes so that given order is old order:
     * 
     * <pre>
     *  axisPerm = (p(0), p(1),...) => newdata(n(0), n(1),...) = olddata(o(0), o(1), ...)
     *  such that n(i) = o(p(i)) for all i
     * </pre>
     * 
     * I.e. for a 3D dataset (1,0,2) implies the new dataset has its 1st dimension running along the old dataset's 2nd
     * dimension and the new 2nd is the old 1st. The 3rd dimension is left unchanged.
     * 
     * @param axes
     *            if zero length then axes order reversed
     * @return remapped copy of data
     */
    public AbstractDataset transpose(int... axes) {
        return DatasetUtils.transpose(this, axes);
    }

    /**
     * Swap two axes in dataset
     * 
     * @param axis1
     * @param axis2
     * @return swapped dataset
     */
    public AbstractDataset swapaxes(int axis1, int axis2) {
        return DatasetUtils.swapAxes(this, axis1, axis2);
    }

    /**
     * Append copy of dataset with another dataset along n-th axis
     * 
     * @param other
     * @param axis
     *            number of axis (negative number counts from last)
     * @return appended dataset
     * @deprecated Use {@link DatasetUtils#append(IDataset, IDataset, int)}
     */
    @Deprecated
    public AbstractDataset append(AbstractDataset other, int axis) {
        abstractLogger.warn("Use DatasetUtils.append");
        return DatasetUtils.append(this, other, axis);
    }

    /**
     * Flatten shape
     * 
     * @return a flattened dataset which is a view if dataset is contiguous otherwise is a copy
     */
    public AbstractDataset flatten() {
        AbstractDataset result;

        if (shape.length <= 1 || dataShape == null) {
            result = getView();
        } else {
            result = clone();
        }
        result.shape = new int[] { result.size };
        return result;
    }

    /**
     * Calculate total number of items in given shape
     * @param shape
     * @return size
     */
    public static int calcSize(final int[] shape) {
        int size = 1;
        double dsize = 1.0;

        if (shape.length == 1) {
            if (shape[0] == 0) {
                return 0;
            }

            size *= shape[0];
            dsize *= shape[0];
        } else {
            for (int i = 0; i < shape.length; i++) {
                // make sure the indexes isn't zero or negative
                if (shape[i] <= 0) {
                    throw new IllegalArgumentException("The " + i + "-th is " + shape[i]
                            + " which is an illegal argument as it is zero or negative");
                }

                size *= shape[i];
                dsize *= shape[i];
            }
        }

        // check to see if the size is larger than an integer, i.e. we can't allocate it
        if (dsize > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("Size of the dataset is too large to allocate");
        }
        return size;
    }

    /**
     * Find dataset type that best fits given types The best type takes into account complex and array datasets
     * 
     * @param atype
     *            first dataset type
     * @param btype
     *            second dataset type
     * @return best dataset type
     */
    public static int getBestDType(final int atype, final int btype) {
        int besttype;

        int a = atype >= ARRAYINT8 ? atype / 100 : atype;
        int b = btype >= ARRAYINT8 ? btype / 100 : btype;

        besttype = a > b ? a : b;

        if (atype >= ARRAYINT8 || btype >= ARRAYINT8) {
            if (besttype >= COMPLEX64) {
                throw new IllegalArgumentException("Complex type cannot be promoted to compound type");
            }
            besttype *= 100;
        }

        return besttype;
    }

    /**
     * The largest dataset type suitable for a summation of around a few thousand items without changing from the "kind"
     * of dataset
     * 
     * @param otype
     * @return largest dataset type available for given dataset type
     */
    public static int getLargestDType(final int otype) {
        switch (otype) {
        case BOOL:
        case INT8:
        case INT16:
            return INT32;
        case INT32:
        case INT64:
            return INT64;
        case FLOAT32:
        case FLOAT64:
            return FLOAT64;
        case COMPLEX64:
        case COMPLEX128:
            return COMPLEX128;
        case ARRAYINT8:
        case ARRAYINT16:
            return ARRAYINT32;
        case ARRAYINT32:
        case ARRAYINT64:
            return ARRAYINT64;
        case ARRAYFLOAT32:
        case ARRAYFLOAT64:
            return ARRAYFLOAT64;
        }
        throw new IllegalArgumentException("Unsupported dataset type");
    }

    /**
     * Find floating point dataset type that best fits given types The best type takes into account complex and array
     * datasets
     * 
     * @param otype
     *            old dataset type
     * @return best dataset type
     */
    public static int getBestFloatDType(final int otype) {
        int btype;
        switch (otype) {
        case BOOL:
        case INT8:
        case INT16:
        case ARRAYINT8:
        case ARRAYINT16:
        case FLOAT32:
        case ARRAYFLOAT32:
        case COMPLEX64:
            btype = FLOAT32; // demote, if necessary
            break;
        case INT32:
        case INT64:
        case ARRAYINT32:
        case ARRAYINT64:
        case FLOAT64:
        case ARRAYFLOAT64:
        case COMPLEX128:
            btype = FLOAT64; // promote, if necessary
            break;
        default:
            btype = otype; // for array datasets, preserve type
            break;
        }

        return btype;
    }

    /**
     * Find floating point dataset type that best fits given class The best type takes into account complex and array
     * datasets
     * 
     * @param cls
     *            of an item or element
     * @return best dataset type
     */
    public static int getBestFloatDType(Class<? extends Object> cls) {
        return getBestFloatDType(getDTypeFromClass(cls));
    }

    transient private static final Map<Class<?>, Integer> dtypeMap = createDTypeMap();

    private static Map<Class<?>, Integer> createDTypeMap() {
        Map<Class<?>, Integer> result = new HashMap<Class<?>, Integer>();
        result.put(Boolean.class, BOOL);
        result.put(Byte.class, INT8);
        result.put(Short.class, INT16);
        result.put(Integer.class, INT32);
        result.put(Long.class, INT64);
        result.put(Float.class, FLOAT32);
        result.put(Double.class, FLOAT64);
        result.put(Complex.class, COMPLEX128);
        result.put(String.class, STRING);
        result.put(Object.class, OBJECT);
        return result;
    }

    /**
     * Get dataset type from a class
     * 
     * @param cls
     * @return dataset type
     */
    public static int getDTypeFromClass(Class<? extends Object> cls) {
        Integer dtype = dtypeMap.get(cls);
        if (dtype == null) {
            throw new IllegalArgumentException("Class of object not supported");
        }
        return dtype;
    }

    /**
     * Get dataset type from an object. The following are supported: Java Number objects, Apache common math Complex
     * objects, Java arrays and lists
     * 
     * @param obj
     * @return dataset type
     */
    public static int getDTypeFromObject(Object obj) {
        int dtype = BOOL;

        if (obj == null) {
            return dtype;
        }

        if (obj instanceof List<?>) {
            List<?> jl = (List<?>) obj;
            int l = jl.size();
            for (int i = 0; i < l; i++) {
                int ldtype = getDTypeFromObject(jl.get(i));
                if (ldtype > dtype) {
                    dtype = ldtype;
                }
            }
        } else if (obj.getClass().isArray()) {
            int l = Array.getLength(obj);
            for (int i = 0; i < l; i++) {
                Object lo = Array.get(obj, i);
                int ldtype = getDTypeFromObject(lo);
                if (ldtype > dtype) {
                    dtype = ldtype;
                }
            }
        } else {
            dtype = getDTypeFromClass(obj.getClass());
        }
        return dtype;
    }

    /**
     * Get dataset type from given dataset
     * @param d
     * @return dataset type
     */
    public static int getDType(ILazyDataset d) {
        if (d instanceof AbstractDataset)
            return ((AbstractDataset) d).getDtype();
        return getDTypeFromClass(d.elementClass());
    }

    /**
     * get shape from object (array or list supported)
     * @param obj
     * @return shape
     */
    protected static int[] getShapeFromObject(final Object obj) {
        ArrayList<Integer> lshape = new ArrayList<Integer>();

        getShapeFromObj(lshape, obj, 0);
        if (obj != null && lshape.size() == 0) {
            return new int[0]; // cope with a single item
        }
        final int rank = lshape.size();
        final int[] shape = new int[rank];
        for (int i = 0; i < rank; i++) {
            shape[i] = lshape.get(i);
        }

        return shape;
    }

    private static void getShapeFromObj(final ArrayList<Integer> ldims, Object obj, int depth) {
        if (obj == null)
            return;

        if (obj instanceof List<?>) {
            List<?> jl = (List<?>) obj;
            int l = jl.size();
            updateShape(ldims, depth, l);
            for (int i = 0; i < l; i++) {
                Object lo = jl.get(i);
                getShapeFromObj(ldims, lo, depth + 1);
            }
        } else if (obj.getClass().isArray()) {
            final int l = Array.getLength(obj);
            updateShape(ldims, depth, l);
            for (int i = 0; i < l; i++) {
                Object lo = Array.get(obj, i);
                getShapeFromObj(ldims, lo, depth + 1);
            }
        } else {
            return; // not an array of any type
        }
    }

    private static void updateShape(final ArrayList<Integer> ldims, final int depth, final int l) {
        if (depth >= ldims.size()) {
            ldims.add(l);
        } else if (l > ldims.get(depth)) {
            ldims.set(depth, l);
        }
    }

    /**
     * Fill dataset from object at depth dimension
     * @param obj
     * @param depth
     * @param pos position
     */
    public void fillData(Object obj, final int depth, final int[] pos) {
        if (obj == null) {
            int dtype = getDtype();
            if (dtype == FLOAT32)
                set(Float.NaN, pos);
            else if (dtype == FLOAT64)
                set(Double.NaN, pos);
            return;
        }

        if (obj instanceof List<?>) {
            List<?> jl = (List<?>) obj;
            int l = jl.size();
            for (int i = 0; i < l; i++) {
                Object lo = jl.get(i);
                fillData(lo, depth + 1, pos);
                pos[depth]++;
            }
            pos[depth] = 0;
        } else if (obj.getClass().isArray()) {
            int l = Array.getLength(obj);
            for (int i = 0; i < l; i++) {
                Object lo = Array.get(obj, i);
                fillData(lo, depth + 1, pos);
                pos[depth]++;
            }
            pos[depth] = 0;
        } else {
            set(obj, pos);
        }
    }

    protected static boolean toBoolean(final Object b) {
        if (b instanceof Number) {
            return ((Number) b).longValue() != 0;
        } else if (b instanceof Boolean) {
            return ((Boolean) b).booleanValue();
        } else if (b instanceof Complex) {
            return ((Complex) b).getReal() != 0;
        } else {
            throw new IllegalArgumentException("Argument is of unsupported class");
        }
    }

    protected static long toLong(final Object b) {
        if (b instanceof Number) {
            double t = ((Number) b).doubleValue();
            if (Double.isNaN(t) || Double.isInfinite(t)) {
                return 0;
            }
            return ((Number) b).longValue();
        } else if (b instanceof Boolean) {
            return ((Boolean) b).booleanValue() ? 1 : 0;
        } else if (b instanceof Complex) {
            return (long) ((Complex) b).getReal();
        } else {
            throw new IllegalArgumentException("Argument is of unsupported class");
        }
    }

    protected static double toReal(final Object b) {
        if (b instanceof Number) {
            return ((Number) b).doubleValue();
        } else if (b instanceof Boolean) {
            return ((Boolean) b).booleanValue() ? 1 : 0;
        } else if (b instanceof Complex) {
            return ((Complex) b).getReal();
        } else {
            throw new IllegalArgumentException("Argument is of unsupported class");
        }
    }

    protected static double toImag(final Object b) {
        if (b instanceof Number) {
            return 0;
        } else if (b instanceof Boolean) {
            return 0;
        } else if (b instanceof Complex) {
            return ((Complex) b).getImaginary();
        } else {
            throw new IllegalArgumentException("Argument is not a number");
        }
    }

    protected void expandDataShape(final int[] nshape) {
        // expand the allocated memory by the amount specified in ARRAY_ALLOCATION_EXTENSION

        // now check to see whether the additional space is required
        final int rank = dataShape.length;
        for (int i = 0; i < rank; i++) {
            if (dataShape[i] > 0) {
                double change = nshape[i] - dataShape[i];
                if (change > 0) {
                    change /= dataShape[i];
                    if (change < 0.1) {
                        change = ARRAY_ALLOCATION_EXTENSION;
                    }
                    dataShape[i] *= 1 + change;
                }
            }
        }
    }

    private boolean isAllZeros(final int[] a) {
        int amax = a.length;
        for (int i = 0; i < amax; i++) {
            if (a[i] != 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param withPosition
     *            set true if position is needed
     * @return an IndexIterator tailored for this dataset
     */
    public IndexIterator getIterator(final boolean withPosition) {
        if (shape.length <= 1 || dataShape == null || isAllZeros(dataShape)) {
            return (withPosition) ? new ContiguousIteratorWithPosition(shape, size) : new ContiguousIterator(size);
        }
        return new DiscontiguousIterator(shape, dataShape, dataSize);
        // return getSliceIterator(null, null, null, null); // alternative way (probably a little slower)
    }

    /**
     * @return an IndexIterator tailored for this dataset
     */
    public IndexIterator getIterator() {
        return getIterator(false);
    }

    /**
     * @param axes
     * @return a PositionIterator that misses out axes
     */
    public PositionIterator getPositionIterator(final int... axes) {
        return new PositionIterator(shape, axes);
    }

    /**
     * @param start
     *            specifies the starting indexes
     * @param stop
     *            specifies the stopping indexes (nb, these are <b>not</b> included in the slice)
     * @param step
     *            specifies the steps in the slice
     * @return an slice iterator that operates like an IndexIterator
     */
    public IndexIterator getSliceIterator(final int[] start, final int[] stop, final int[] step) {
        int rank = shape.length;

        int[] lstart, lstop, lstep;

        if (step == null) {
            lstep = new int[rank];
            Arrays.fill(lstep, 1);
        } else {
            lstep = step;
        }

        if (start == null) {
            lstart = new int[rank];
        } else {
            lstart = start;
        }

        if (stop == null) {
            lstop = new int[rank];
        } else {
            lstop = stop;
        }

        int[] newShape;
        if (rank > 1 || (rank > 0 && shape[0] > 0)) {
            newShape = checkSlice(start, stop, lstart, lstop, lstep);
        } else {
            newShape = new int[rank];
        }

        if (rank <= 1 || dataShape == null) {
            return new SliceIterator(shape, size, lstart, lstep, newShape);
        }

        return new SliceIterator(dataShape, dataSize, lstart, lstep, newShape);
    }

    /**
     * Get a slice iterator that is defined by a starting position and a set of axes to include
     * 
     * @param pos
     * @param axes
     *            to include
     * @return slice iterator
     */
    protected SliceIterator getSliceIteratorFromAxes(final int[] pos, boolean[] axes) {
        int rank = shape.length;
        int[] start;
        int[] stop = new int[rank];
        int[] step = new int[rank];

        if (pos == null) {
            start = new int[rank];
        } else if (pos.length == rank) {
            start = pos.clone();
        } else {
            throw new IllegalArgumentException("pos array length is not equal to rank of dataset");
        }
        if (axes == null) {
            axes = new boolean[rank];
            Arrays.fill(axes, true);
        } else if (axes.length != rank) {
            throw new IllegalArgumentException("axes array length is not equal to rank of dataset");
        }

        for (int i = 0; i < rank; i++) {
            if (axes[i]) {
                start[i] = 0;
                stop[i] = shape[i];
            } else {
                stop[i] = start[i] + 1;
            }
            step[i] = 1;
        }
        return (SliceIterator) getSliceIterator(pos, stop, step);
    }

    /**
     * Copy content from axes in given position to array
     * 
     * @param pos
     *            - null means position at origin
     * @param axes
     *            - true means copy
     * @param dest
     */
    abstract public void copyItemsFromAxes(final int[] pos, final boolean[] axes, final AbstractDataset dest);

    /**
     * Set content on axes in given position to values in array
     * 
     * @param pos
     * @param axes
     *            - true means copy
     * @param src
     */
    abstract public void setItemsOnAxes(final int[] pos, final boolean[] axes, final Object src);

    /**
     * Check slice and alter parameters if necessary
     * 
     * @param oldShape
     * @param start
     *            can be null
     * @param stop
     *            can be null
     * @param lstart
     *            can be a reference to start if that is not null
     * @param lstop
     *            can be a reference to start if that is not null
     * @param lstep
     * @returns newShape
     */
    public static int[] checkSlice(final int[] oldShape, final int[] start, final int[] stop, final int[] lstart,
            final int[] lstop, final int[] lstep) {
        // number of steps, or new shape, taken in each dimension is
        // shape = (stop - start + step - 1) / step if step > 0
        // (stop - start + step + 1) / step if step < 0
        //
        // thus the final index in each dimension is
        // start + (shape-1)*step

        int rank = oldShape.length;

        if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
            throw new IllegalArgumentException(
                    "No of indexes does not match data dimensions: you passed it start=" + lstart.length + ", stop="
                            + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
        }

        final int[] newShape = new int[rank];

        // sanitise input
        for (int i = 0; i < rank; i++) {
            if (lstep[i] == 0) {
                throw new IllegalArgumentException(
                        "The step array is not allowed any zero entries: " + i + "-th entry is zero");
            }
            if (start != null) {
                if (start[i] < 0) {
                    start[i] += oldShape[i];
                }
                if (start[i] < 0) {
                    start[i] = lstep[i] > 0 ? 0 : -1;
                }
                if (start[i] > oldShape[i]) {
                    start[i] = lstep[i] > 0 ? oldShape[i] : oldShape[i] - 1;
                }
            } else {
                lstart[i] = lstep[i] > 0 ? 0 : oldShape[i] - 1;
            }

            if (stop != null) {
                if (stop[i] < 0) {
                    if (lstep[i] > 0)
                        stop[i] += oldShape[i];
                }
                if (stop[i] < 0) {
                    stop[i] = -1;
                }
                if (stop[i] > oldShape[i]) {
                    stop[i] = oldShape[i];
                }
            } else {
                lstop[i] = lstep[i] > 0 ? oldShape[i] : -1;
            }
            if (lstart[i] == lstop[i]) {
                throw new IllegalArgumentException("Same indices in start and stop");
            }
            if ((lstep[i] > 0) != (lstart[i] < lstop[i])) {
                throw new IllegalArgumentException("Start=" + lstart[i] + " and stop=" + lstop[i]
                        + " indices are incompatible with step=" + lstep[i]);
            }
            if (lstep[i] > 0) {
                newShape[i] = (lstop[i] - lstart[i] - 1) / lstep[i] + 1;
            } else {
                newShape[i] = (lstop[i] - lstart[i] + 1) / lstep[i] + 1;
            }

        }
        return newShape;
    }

    protected int[] checkSlice(int[] start, int[] stop, int[] lstart, int[] lstop, int[] lstep) {
        return checkSlice(shape, start, stop, lstart, lstop, lstep);
    }

    /**
     * This is modelled after the NumPy array slice
     * @param obj
     *            specifies the object used to set the specified slice
     * @param start
     *            specifies the starting indexes
     * @param stop
     *            specifies the stopping indexes (nb, these are <b>not</b> included in the slice)
     * @param step
     *            specifies the steps in the slice
     * 
     * @return The dataset with the sliced set to object
     */
    public AbstractDataset setSlice(final Object obj, final int[] start, final int[] stop, final int[] step) {
        return setSlice(obj, (SliceIterator) getSliceIterator(start, stop, step));
    }

    /**
     * @param obj
     *            specifies the object used to set the specified slice
     * @param iterator
     *            specifies the slice iterator
     * 
     * @return The dataset with the sliced set to object
     */
    abstract public AbstractDataset setSlice(final Object obj, final SliceIterator iterator);

    /**
     * Get an iterator that visits every item in this dataset where the corresponding item in choice dataset is true
     * 
     * @param choice
     * @return an iterator of dataset that visits items chosen by given choice dataset
     */
    public BooleanIterator getBooleanIterator(BooleanDataset choice) {
        return new BooleanIterator(getIterator(), choice);
    }

    /**
     * This is modelled after the NumPy get item with a condition specified by a boolean dataset
     * 
     * @param selection
     *            a boolean dataset of same shape to use for selecting items
     * @return The new selected dataset
     */
    public AbstractDataset getByBoolean(BooleanDataset selection) {
        checkCompatibility(selection);

        final int length = ((Number) selection.sum()).intValue();
        final int is = getElementsPerItem();
        AbstractDataset r = zeros(is, new int[] { length }, getDtype());
        BooleanIterator biter = getBooleanIterator(selection);

        int i = 0;
        while (biter.hasNext()) {
            r.setObjectAbs(i, getObjectAbs(biter.index));
            i += is;
        }
        return r;
    }

    /**
     * This is modelled after the NumPy set item with a condition specified by a boolean dataset
     * @param obj
     *            specifies the object used to set the selected items
     * @param selection
     *            a boolean dataset of same shape to use for selecting items
     * 
     * @return The dataset with modified content
     */
    abstract public AbstractDataset setByBoolean(final Object obj, BooleanDataset selection);

    /**
     * This is modelled after the NumPy get item with an index dataset
     * 
     * @param index
     *            an integer dataset
     * @return The new selected dataset by indices
     */
    public AbstractDataset getByIndex(IntegerDataset index) {
        final int is = getElementsPerItem();
        final AbstractDataset r = zeros(is, index.getShape(), getDtype());
        final IntegerIterator iter = new IntegerIterator(index, size, is);

        int i = 0;
        while (iter.hasNext()) {
            r.setObjectAbs(i, getObjectAbs(iter.index));
            i += is;
        }
        return r;
    }

    /**
     * This is modelled after the NumPy set item with an index dataset
     * @param obj
     *            specifies the object used to set the selected items
     * @param index
     *            an integer dataset
     * 
     * @return The dataset with modified content
     */
    abstract public AbstractDataset setByIndex(final Object obj, IntegerDataset index);

    /**
     * @param dtype
     * @return class of constituent element
     */
    public static Class<?> elementClass(final int dtype) {
        switch (dtype) {
        case BOOL:
            return Boolean.class;
        case INT8:
        case ARRAYINT8:
            return Byte.class;
        case INT16:
        case ARRAYINT16:
        case RGB:
            return Short.class;
        case INT32:
        case ARRAYINT32:
            return Integer.class;
        case INT64:
        case ARRAYINT64:
            return Long.class;
        case FLOAT32:
        case ARRAYFLOAT32:
            return Float.class;
        case FLOAT64:
        case ARRAYFLOAT64:
            return Double.class;
        case COMPLEX64:
            return Float.class;
        case COMPLEX128:
            return Double.class;
        }
        return Object.class;
    }

    /**
     * @return type of data item
     */
    abstract public int getDtype();

    @Override
    public Class<?> elementClass() {
        return elementClass(getDtype());
    }

    @Override
    public int getElementsPerItem() {
        return getElementsPerItem(getDtype());
    }

    @Override
    public int getItemsize() {
        return getItemsize(getDtype(), getElementsPerItem());
    }

    /**
     * @param dtype
     * @return number of elements per item
     */
    public static int getElementsPerItem(final int dtype) {
        switch (dtype) {
        case ARRAYINT8:
        case ARRAYINT16:
        case ARRAYINT32:
        case ARRAYINT64:
        case ARRAYFLOAT32:
        case ARRAYFLOAT64:
            throw new UnsupportedOperationException("Multi-element type unsupported");
        case COMPLEX64:
        case COMPLEX128:
            return 2;
        }
        return 1;
    }

    /**
     * @param dtype
     * @return length of single item in bytes
     */
    public static int getItemsize(final int dtype) {
        return getItemsize(dtype, getElementsPerItem(dtype));
    }

    /**
     * @param dtype
     * @param isize
     *            number of elements in an item
     * @return length of single item in bytes
     */
    public static int getItemsize(final int dtype, final int isize) {
        int size;

        switch (dtype) {
        case BOOL:
            size = 1; // How is this defined?
            break;
        case INT8:
        case ARRAYINT8:
            size = Byte.SIZE / 8;
            break;
        case INT16:
        case ARRAYINT16:
        case RGB:
            size = Short.SIZE / 8;
            break;
        case INT32:
        case ARRAYINT32:
            size = Integer.SIZE / 8;
            break;
        case INT64:
        case ARRAYINT64:
            size = Long.SIZE / 8;
            break;
        case FLOAT32:
        case ARRAYFLOAT32:
        case COMPLEX64:
            size = Float.SIZE / 8;
            break;
        case FLOAT64:
        case ARRAYFLOAT64:
        case COMPLEX128:
            size = Double.SIZE / 8;
            break;
        default:
            size = 0;
            break;
        }

        return size * isize;
    }

    /**
     * @return name
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * @param name
     */
    @Override
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * @return number of data items in dataset
     */
    @Override
    public int getSize() {
        if (odata == null) {
            throw new NullPointerException("The data object inside the dataset has not been allocated, "
                    + "this suggests a failed or absent construction of the dataset");
        }
        return size;
    }

    /**
     * @return true if data array is contiguous, i.e. it is un-expanded or does not any reserved space left
     */
    public boolean isContiguous() {
        return shape.length <= 1 || dataShape == null;
    }

    @Override
    public int[] getShape() {
        // make a copy of the dimensions data, and put that out
        if (shape == null) {
            abstractLogger.warn("Shape is null!!!");
            return new int[] {};
        }
        return shape.clone();
    }

    @Override
    public int getRank() {
        return shape.length;
    }

    /**
     * @return number of bytes used (does not include reserved space)
     */
    public int getNbytes() {
        return getSize() * getItemsize();
    }

    /**
     * @param shape
     */
    @Override
    public void setShape(final int... shape) {
        if (dataShape != null) {
            throw new UnsupportedOperationException("Cannot set a new shape to discontiguous dataset");
        }

        int size = calcSize(shape);
        if (size != this.size) {
            throw new IllegalArgumentException("New shape (" + Arrays.toString(shape)
                    + ") is not compatible with old shape (" + Arrays.toString(this.shape) + ")");
        }

        this.shape = shape.clone();
    }

    /**
     * @return the buffer that backs the dataset
     */
    public Serializable getBuffer() {
        return odata;
    }

    /**
     * Function that uses the knowledge of the dataset to calculate the index in the data array that corresponds to the
     * n-dimensional position given by the int array. The input values <b>must</b> be inside the arrays, this should be
     * ok as this function is mainly in code which will be run inside the get and set functions
     * 
     * @param n
     *            the integer array specifying the n-D position
     * @return the index on the data array corresponding to that location
     */
    protected int get1DIndex(final int... n) {
        final int imax = n.length;
        final int rank = shape.length;
        if (imax == 0) {
            if (rank == 0)
                return 0;
            if (rank == 1 && shape[0] == 0) {
                return 0;
            }
            throw new IllegalArgumentException("One or more index parameters must be supplied");
        } else if (imax > rank) {
            throw new IllegalArgumentException("No of index parameters is different to the shape of data: " + imax
                    + " given " + rank + " required");
        }

        // once checked return the appropriate value.
        int index = n[0];
        final int sz = shape[0];
        if (index < -sz || index >= sz) {
            throw new ArrayIndexOutOfBoundsException(
                    "Index (" + index + ") out of range [-" + sz + "," + sz + ") in dimension 0");
        }
        if (index < 0) {
            index += sz;
        }

        if (rank == 1) {
            return index;
        }

        final int[] lshape = dataShape == null ? shape : dataShape;

        int i = 1;
        for (; i < imax; i++) {
            final int ni = n[i];
            final int si = shape[i];
            if (ni < -si || ni >= si) {
                throw new ArrayIndexOutOfBoundsException(
                        "Index (" + ni + ") out of range [-" + si + "," + si + ") in dimension " + i);
            }
            index = index * lshape[i] + ni;
            if (ni < 0) {
                index += si;
            }
        }
        for (; i < lshape.length; i++) {
            index *= lshape[i];
        }

        return index;
    }

    /**
     * The n-D position in the dataset of the given index in the data array
     * 
     * @param n
     *            The index in the array
     * @return the corresponding [a,b,...,n] position in the dataset
     */
    public int[] getNDPosition(final int n) {
        if (n >= size && dataShape != null && n >= dataSize) {
            throw new IllegalArgumentException(
                    "Index provided " + n + "is larger then the size of the containing array " + dataSize);
        }

        if (shape.length == 1) {
            return new int[] { n };
        }

        final int[] lshape = dataShape == null ? shape : dataShape;

        int r = lshape.length;
        int[] output = new int[r];

        int inValue = n;
        for (r--; r > 0; r--) {
            output[r] = inValue % lshape[r];
            inValue /= lshape[r];
        }
        output[0] = inValue;

        return output;
    }

    /**
     * Translate from an index value to an actual index (if dataset is discontiguous)
     * 
     * @return real index
     */
    protected int to1DIndex(final int n) {
        if (shape.length > 1 && dataShape != null) {
            return get1DIndex(getNDPosition(n));
        }
        if (n < 0 || n >= size) {
            throw new IndexOutOfBoundsException("Index out of bounds: " + n + " cf " + size);
        }
        return n;
    }

    /**
     * Check that axis is in range [-rank,rank)
     * 
     * @param axis
     * @return sanitized axis in range [0, rank)
     */
    public int checkAxis(int axis) {
        int rank = shape.length;
        if (axis < 0) {
            axis += rank;
        }

        if (axis < 0 || axis >= rank) {
            throw new IndexOutOfBoundsException("Axis " + axis + " given is out of range [0, " + rank + ")");
        }
        return axis;
    }

    /**
     * types for to string method
     */
    public static final int STRING_NORMAL = 0;
    public static final int STRING_SHAPE = 1;

    private int stringPolicy = STRING_NORMAL;

    @Override
    public String toString() {
        final int rank = shape == null ? 0 : shape.length;
        final StringBuilder out = new StringBuilder();

        if (stringPolicy == STRING_SHAPE) {
            if (name != null && name.length() > 0) {
                out.append("Dataset '");
                out.append(name);
                out.append("' has shape [");
            } else {
                out.append("Dataset shape is [");
            }

            if (rank > 0 && shape[0] > 0) {
                out.append(shape[0]);
            }
            for (int i = 1; i < rank; i++) {
                out.append(", " + shape[i]);
            }
            out.append("]");
            return out.toString();
        }

        if (size == 0) {
            return out.toString();
        }

        if (rank > 0) {
            int[] pos = new int[rank];
            final StringBuilder lead = new StringBuilder();
            printBlocks(out, lead, 0, pos);
        } else {
            out.append(getString());
        }
        return out.toString();
    }

    private final static String SEPARATOR = ",";
    private final static String SPACING = " ";
    private final static String ELLIPSES = "...";
    private final static String NEWLINE = "\n";

    /**
     * Make a line of output for last dimension of dataset
     * 
     * @param start
     * @return line
     */
    private StringBuilder makeLine(final int end, final int... start) {
        StringBuilder line = new StringBuilder();
        final int[] pos;
        if (end >= start.length) {
            pos = Arrays.copyOf(start, end + 1);
        } else {
            pos = start;
        }
        pos[end] = 0;
        line.append('[');
        line.append(getString(pos));

        final int length = shape[end];

        // trim elements printed if length exceed estimate of maximum elements
        int excess = length - MAX_STRING_LENGTH / 3; // space + number + separator
        if (excess > 0) {
            int index = (length - excess) / 2;
            for (int y = 1; y < index; y++) {
                line.append(SEPARATOR + SPACING);
                pos[end] = y;
                line.append(getString(pos));
            }
            index = (length + excess) / 2;
            for (int y = index; y < length; y++) {
                line.append(SEPARATOR + SPACING);
                pos[end] = y;
                line.append(getString(pos));
            }
        } else {
            for (int y = 1; y < length; y++) {
                line.append(SEPARATOR + SPACING);
                pos[end] = y;
                line.append(getString(pos));
            }
        }
        line.append(']');

        // trim string down to limit
        excess = line.length() - MAX_STRING_LENGTH - ELLIPSES.length() - 1;
        if (excess > 0) {
            int index = line.substring(0, (line.length() - excess) / 2).lastIndexOf(SEPARATOR) + 2;
            StringBuilder out = new StringBuilder(line.subSequence(0, index));
            out.append(ELLIPSES + SEPARATOR);
            index = line.substring((line.length() + excess) / 2).indexOf(SEPARATOR) + (line.length() + excess) / 2
                    + 1;
            out.append(line.subSequence(index, line.length()));
            return out;
        }

        return line;
    }

    /**
     * recursive method to print blocks
     */
    private void printBlocks(final StringBuilder out, final StringBuilder lead, final int level, final int[] pos) {
        if (out.length() > 0) {
            String last = out.substring(out.length() - 1);
            if (!last.equals("[")) {
                out.append(lead);
            }
        }
        final int end = getRank() - 1;
        if (level != end) {
            out.append('[');
            int length = shape[level];

            // first sub-block
            pos[level] = 0;
            StringBuilder newlead = new StringBuilder(lead);
            newlead.append(SPACING);
            printBlocks(out, newlead, level + 1, pos);
            if (length < 2) { // escape
                out.append(']');
                return;
            }

            out.append(SEPARATOR + NEWLINE);
            for (int i = level + 1; i < end; i++) {
                out.append(NEWLINE);
            }

            // middle sub-blocks
            if (length < MAX_SUBBLOCKS) {
                for (int x = 1; x < length - 1; x++) {
                    pos[level] = x;
                    printBlocks(out, newlead, level + 1, pos);
                    if (end <= level + 1) {
                        out.append(SEPARATOR + NEWLINE);
                    } else {
                        out.append(SEPARATOR + NEWLINE + NEWLINE);
                    }
                }
            } else {
                final int excess = length - MAX_SUBBLOCKS;
                int xmax = (length - excess) / 2;
                for (int x = 1; x < xmax; x++) {
                    pos[level] = x;
                    printBlocks(out, newlead, level + 1, pos);
                    if (end <= level + 1) {
                        out.append(SEPARATOR + NEWLINE);
                    } else {
                        out.append(SEPARATOR + NEWLINE + NEWLINE);
                    }
                }
                out.append(newlead);
                out.append(ELLIPSES + SEPARATOR + NEWLINE);
                xmax = (length + excess) / 2;
                for (int x = xmax; x < length - 1; x++) {
                    pos[level] = x;
                    printBlocks(out, newlead, level + 1, pos);
                    if (end <= level + 1) {
                        out.append(SEPARATOR + NEWLINE);
                    } else {
                        out.append(SEPARATOR + NEWLINE + NEWLINE);
                    }
                }
            }

            // last sub-block
            pos[level] = length - 1;
            printBlocks(out, newlead, level + 1, pos);
            out.append(']');
        } else {
            out.append(makeLine(end, pos));
        }
    }

    /**
     * This function allows anything that dirties the dataset to set stored values to null so that the other functions
     * can work correctly.
     */
    public void setDirty() {
        storedValues = null;
    }

    /**
     * Check the position given against the shape to make sure it is valid and sanitise it
     * 
     * @param pos
     * @return boolean
     */
    protected boolean isPositionInShape(final int... pos) {
        int pmax = pos.length;

        // check the dimensionality of the request
        if (pmax > shape.length) {
            throw new IllegalArgumentException();
        }

        // if it's the right size or less, check to see if it's within bounds
        for (int i = 0; i < pmax; i++) {
            final int si = shape[i];
            if (pos[i] < 0) {
                pos[i] += si;
            }
            if (pos[i] < 0) {
                throw new ArrayIndexOutOfBoundsException(
                        "Index (" + pos[i] + ") out of range [-" + si + "," + si + ") in dimension " + i);
            }
            if (pos[i] >= si) {
                if (extendible) {
                    return false;
                }
                throw new ArrayIndexOutOfBoundsException(
                        "Index (" + pos[i] + ") out of range [-" + si + "," + si + ") in dimension " + i);
            }
        }

        return true;
    }

    /**
     * Check the shape given against the reserved shape to make sure it is valid
     * 
     * @param shape
     * @return boolean
     */
    protected boolean isShapeInDataShape(final int[] shape) {

        // if it's the right size or less, check to see if it's within bounds
        for (int i = 0; i < dataShape.length; i++) {
            if (shape[i] >= dataShape[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     * Remove dimensions of 1 in shape of a dataset
     */
    @Override
    public AbstractDataset squeeze() {
        return squeeze(false);
    }

    /**
     * Remove dimensions of 1 in shape of a dataset from end only if true
     * 
     * @param onlyFromEnd
     */
    @Override
    public AbstractDataset squeeze(boolean onlyFromEnd) {
        shape = squeezeShape(shape, onlyFromEnd);
        return this;
    }

    /**
     * Remove dimensions of 1 in given shape from end only if true
     * 
     * @param oshape
     * @param onlyFromEnd
     * @return newly squeezed shape
     */
    public static int[] squeezeShape(final int[] oshape, boolean onlyFromEnd) {
        int unitDims = 0;
        int rank = oshape.length;

        if (onlyFromEnd) {
            for (int i = rank - 1; i >= 0; i--) {
                if (oshape[i] == 1) {
                    unitDims++;
                } else {
                    break;
                }
            }
        } else {
            for (int i = 0; i < rank; i++) {
                if (oshape[i] == 1) {
                    unitDims++;
                }
            }
        }

        if (unitDims == 0) {
            return oshape;
        }

        int[] newDims = new int[rank - unitDims];
        if (unitDims == rank)
            return newDims; // scalar dataset

        if (onlyFromEnd) {
            rank = newDims.length;
            for (int i = 0; i < rank; i++) {
                newDims[i] = oshape[i];
            }
        } else {
            int j = 0;
            for (int i = 0; i < rank; i++) {
                if (oshape[i] > 1) {
                    newDims[j++] = oshape[i];
                    if (j >= newDims.length)
                        break;
                }
            }
        }

        return newDims;
    }

    /**
     * Check if shapes are compatible, ignoring axes of length 1
     * 
     * @param ashape
     * @param bshape
     * @return true if they are compatible
     */
    protected static boolean areShapesCompatible(final int[] ashape, final int[] bshape) {

        List<Integer> alist = new ArrayList<Integer>();

        for (int a : ashape) {
            if (a > 1)
                alist.add(a);
        }

        final int imax = alist.size();
        int i = 0;
        for (int b : bshape) {
            if (b == 1)
                continue;
            if (i >= imax || b != alist.get(i++))
                return false;
        }

        return i == imax;
    }

    /**
     * Check if shapes are compatible but skip axis
     * 
     * @param ashape
     * @param bshape
     * @param axis
     * @return true if they are compatible
     */
    public static boolean areShapesCompatible(final int[] ashape, final int[] bshape, final int axis) {
        if (ashape.length != bshape.length) {
            return false;
        }

        final int rank = ashape.length;
        for (int i = 0; i < rank; i++) {
            if (i != axis && ashape[i] != bshape[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * This function takes a dataset and checks its shape against the current dataset. If they are both of the same
     * size, then this returns true otherwise it returns false.
     * 
     * @param g
     *            The dataset to be compared
     * @return true if shapes are compatible
     */
    public boolean isCompatibleWith(final ILazyDataset g) {
        return areShapesCompatible(shape, g.getShape());
    }

    /**
     * This function takes a dataset and checks its shape against the current dataset. If they are both of the same
     * size, then this returns with no error, if there is a problem, then an error is thrown.
     * 
     * @param g
     *            The dataset to be compared
     * @throws IllegalArgumentException
     *             This will be thrown if there is a problem with the compatibility
     */
    public void checkCompatibility(final ILazyDataset g) throws IllegalArgumentException {
        checkCompatibility(this, g);
    }

    /**
     * This function takes a dataset and checks its shape against another dataset. If they are both of the same size,
     * then this returns with no error, if there is a problem, then an error is thrown.
     * 
     * @param g
     *            The first dataset to be compared
     * @param h
     *            The second dataset to be compared
     * @throws IllegalArgumentException
     *             This will be thrown if there is a problem with the compatibility
     */
    public static void checkCompatibility(final ILazyDataset g, final ILazyDataset h)
            throws IllegalArgumentException {
        if (!areShapesCompatible(g.getShape(), h.getShape())) {
            throw new IllegalArgumentException("Shapes do not match");
        }
    }

    /**
     * Returns dataset with new shape but old data <b>Warning</b> only works for un-expanded datasets! Copy the dataset
     * first
     * 
     * @param shape
     *            new shape
     */
    public AbstractDataset reshape(final int... shape) {
        AbstractDataset a = this.getView();
        a.setShape(shape);
        return a;
    }

    /**
     * Change shape and size of dataset in-place
     * 
     * @param newShape
     */
    abstract public void resize(int... newShape);

    /**
     * Create a dataset from object (automatically detect dataset type)
     * 
     * @param obj
     *            can be a PySequence, Java array or Number
     * @return dataset
     */
    public static AbstractDataset array(final Object obj) {
        final int dtype = getDTypeFromObject(obj);
        return array(obj, dtype);
    }

    /**
     * Create a dataset from object (automatically detect dataset type)
     * 
     * @param obj
     *            can be a PySequence, Java array or Number
     * @param isUnsigned
     *            if true, interpret integer values as unsigned by increasing element bit width
     * @return dataset
     */
    public static AbstractDataset array(final Object obj, boolean isUnsigned) {
        AbstractDataset a = array(obj);
        if (isUnsigned) {
            switch (a.getDtype()) {
            case AbstractDataset.INT32:
                a = new LongDataset(a);
                DatasetUtils.unwrapUnsigned(a, 32);
                break;
            case AbstractDataset.INT16:
                a = new IntegerDataset(a);
                DatasetUtils.unwrapUnsigned(a, 16);
                break;
            case AbstractDataset.INT8:
                a = new ShortDataset(a);
                DatasetUtils.unwrapUnsigned(a, 8);
                break;
            // TODO array datasets
            }

        }
        return a;
    }

    /**
     * Create a dataset from object
     * 
     * @param obj
     *            can be a PySequence, Java array or Number
     * @param dtype
     * @return dataset
     */
    public static AbstractDataset array(final Object obj, final int dtype) {
        switch (dtype) {
        case BOOL:
            return BooleanDataset.createFromObject(obj);
        case INT8:
            return ByteDataset.createFromObject(obj);
        case INT16:
            return ShortDataset.createFromObject(obj);
        case INT32:
            return IntegerDataset.createFromObject(obj);
        case INT64:
            return LongDataset.createFromObject(obj);
        case ARRAYINT8:
            return CompoundByteDataset.createFromObject(obj);
        case ARRAYINT16:
            return CompoundShortDataset.createFromObject(obj);
        case ARRAYINT32:
            return CompoundIntegerDataset.createFromObject(obj);
        case ARRAYINT64:
            return CompoundLongDataset.createFromObject(obj);
        case FLOAT32:
            return FloatDataset.createFromObject(obj);
        case FLOAT64:
            return DoubleDataset.createFromObject(obj);
        case ARRAYFLOAT32:
            return CompoundFloatDataset.createFromObject(obj);
        case ARRAYFLOAT64:
            return CompoundDoubleDataset.createFromObject(obj);
        case COMPLEX64:
            return ComplexFloatDataset.createFromObject(obj);
        case COMPLEX128:
            return ComplexDoubleDataset.createFromObject(obj);
        case STRING:
            return StringDataset.createFromObject(obj);
        default:
            return null;
        }
    }

    /**
     * Create dataset of appropriate type from list
     * 
     * @param objectList
     * @return dataset filled with values from list
     */
    public static AbstractDataset createFromList(List<?> objectList) {
        if (objectList == null || objectList.size() == 0) {
            throw new IllegalArgumentException("No list or zero-length list given");
        }
        Object obj = objectList.get(0);
        if (obj instanceof Number || obj instanceof Complex) {
            int dtype = getDTypeFromClass(obj.getClass());
            int len = objectList.size();
            AbstractDataset result = zeros(new int[] { len }, dtype);

            int i = 0;
            for (Object object : objectList) {
                result.setObjectAbs(i++, object);
            }
            return result;
        }
        throw new IllegalArgumentException("Class of list element not supported");
    }

    /**
     * @param shape
     * @param dtype
     * @return a new dataset of given shape and type, filled with zeros
     */
    public static AbstractDataset zeros(final int[] shape, final int dtype) {
        switch (dtype) {
        case BOOL:
            return new BooleanDataset(shape);
        case INT8:
        case ARRAYINT8:
            return new ByteDataset(shape);
        case INT16:
        case ARRAYINT16:
            return new ShortDataset(shape);
        case RGB:
            return new RGBDataset(shape);
        case INT32:
        case ARRAYINT32:
            return new IntegerDataset(shape);
        case INT64:
        case ARRAYINT64:
            return new LongDataset(shape);
        case FLOAT32:
        case ARRAYFLOAT32:
            return new FloatDataset(shape);
        case FLOAT64:
        case ARRAYFLOAT64:
            return new DoubleDataset(shape);
        case COMPLEX64:
            return new ComplexFloatDataset(shape);
        case COMPLEX128:
            return new ComplexDoubleDataset(shape);
        }
        throw new IllegalArgumentException("dtype not known or unsupported");
    }

    /**
     * @param itemSize
     *            if equal to 1, then non-compound dataset is returned
     * @param shape
     * @param dtype
     * @return a new dataset of given item size, shape and type, filled with zeros
     */
    public static AbstractDataset zeros(final int itemSize, final int[] shape, final int dtype) {
        if (itemSize == 1) {
            return zeros(shape, dtype);
        }
        switch (dtype) {
        case INT8:
        case ARRAYINT8:
            return new CompoundByteDataset(itemSize, shape);
        case INT16:
        case ARRAYINT16:
            return new CompoundShortDataset(itemSize, shape);
        case RGB:
            if (itemSize != 3) {
                throw new IllegalArgumentException("Number of elements not compatible with RGB type");
            }
            return new RGBDataset(shape);
        case INT32:
        case ARRAYINT32:
            return new CompoundIntegerDataset(itemSize, shape);
        case INT64:
        case ARRAYINT64:
            return new CompoundLongDataset(itemSize, shape);
        case FLOAT32:
        case ARRAYFLOAT32:
            return new CompoundFloatDataset(itemSize, shape);
        case FLOAT64:
        case ARRAYFLOAT64:
            return new CompoundDoubleDataset(itemSize, shape);
        case COMPLEX64:
            if (itemSize != 2) {
                throw new IllegalArgumentException("Number of elements not compatible with complex type");
            }
            return new ComplexFloatDataset(shape);
        case COMPLEX128:
            if (itemSize != 2) {
                throw new IllegalArgumentException("Number of elements not compatible with complex type");
            }
            return new ComplexDoubleDataset(shape);
        }
        throw new IllegalArgumentException("dtype not a known compound type");
    }

    /**
     * @param dataset
     * @return a new dataset of same shape and type as input dataset, filled with zeros
     */
    public static AbstractDataset zeros(final AbstractDataset dataset) {
        return zeros(dataset, dataset.getDtype());
    }

    /**
     * Create a new dataset of same shape as input dataset, filled with zeros. If dtype is not
     * explicitly compound then an elemental dataset is created 
     * @param dataset
     * @param dtype
     * @return a new dataset
     */
    public static AbstractDataset zeros(final AbstractDataset dataset, final int dtype) {
        final int[] shape = dataset.shape;
        final int isize = isDTypeElemental(dtype) ? 1 : dataset.getElementsPerItem();

        return zeros(isize, shape, dtype);
    }

    /**
     * @param dataset
     * @return a new dataset of same shape and type as input dataset, filled with ones
     */
    public static AbstractDataset ones(final AbstractDataset dataset) {
        return ones(dataset, dataset.getDtype());
    }

    /**
     * Create a new dataset of same shape as input dataset, filled with ones. If dtype is not
     * explicitly compound then an elemental dataset is created
     * @param dataset
     * @param dtype
     * @return a new dataset
     */
    public static AbstractDataset ones(final AbstractDataset dataset, final int dtype) {
        final int[] shape = dataset.shape;
        final int isize = isDTypeElemental(dtype) ? 1 : dataset.getElementsPerItem();

        return ones(isize, shape, dtype);
    }

    /**
     * @param shape
     * @param dtype
     * @return a new dataset of given shape and type, filled with ones
     */
    public static AbstractDataset ones(final int[] shape, final int dtype) {
        switch (dtype) {
        case BOOL:
            return BooleanDataset.ones(shape);
        case INT8:
            return ByteDataset.ones(shape);
        case INT16:
            return ShortDataset.ones(shape);
        case RGB:
            return new RGBDataset(shape).fill(1);
        case INT32:
            return IntegerDataset.ones(shape);
        case INT64:
            return LongDataset.ones(shape);
        case FLOAT32:
            return FloatDataset.ones(shape);
        case FLOAT64:
            return DoubleDataset.ones(shape);
        case COMPLEX64:
            return ComplexFloatDataset.ones(shape);
        case COMPLEX128:
            return ComplexDoubleDataset.ones(shape);
        }
        throw new IllegalArgumentException("dtype not known");
    }

    /**
     * @param itemSize
     *            if equal to 1, then non-compound dataset is returned
     * @param shape
     * @param dtype
     * @return a new dataset of given item size, shape and type, filled with ones
     */
    public static AbstractDataset ones(final int itemSize, final int[] shape, final int dtype) {
        if (itemSize == 1) {
            return ones(shape, dtype);
        }
        switch (dtype) {
        case INT8:
        case ARRAYINT8:
            return CompoundByteDataset.ones(itemSize, shape);
        case INT16:
        case ARRAYINT16:
            return CompoundShortDataset.ones(itemSize, shape);
        case RGB:
            if (itemSize != 3) {
                throw new IllegalArgumentException("Number of elements not compatible with RGB type");
            }
            return new RGBDataset(shape).fill(1);
        case INT32:
        case ARRAYINT32:
            return CompoundIntegerDataset.ones(itemSize, shape);
        case INT64:
        case ARRAYINT64:
            return CompoundLongDataset.ones(itemSize, shape);
        case FLOAT32:
        case ARRAYFLOAT32:
            return CompoundFloatDataset.ones(itemSize, shape);
        case FLOAT64:
        case ARRAYFLOAT64:
            return CompoundDoubleDataset.ones(itemSize, shape);
        case COMPLEX64:
            if (itemSize != 2) {
                throw new IllegalArgumentException("Number of elements not compatible with complex type");
            }
            return ComplexFloatDataset.ones(shape);
        case COMPLEX128:
            if (itemSize != 2) {
                throw new IllegalArgumentException("Number of elements not compatible with complex type");
            }
            return ComplexDoubleDataset.ones(shape);
        }
        throw new IllegalArgumentException("dtype not a known compound type");
    }

    /**
     * @param stop
     * @param dtype
     * @return a new dataset of given shape and type, filled with values determined by parameters
     */
    public static AbstractDataset arange(final double stop, final int dtype) {
        return arange(0, stop, 1, dtype);
    }

    /**
     * @param start
     * @param stop
     * @param step
     * @return number of steps to take
     */
    public static int calcSteps(final double start, final double stop, final double step) {
        if (step > 0) {
            return (int) Math.ceil((stop - start) / step);
        }
        return (int) Math.ceil((stop - start) / step);
    }

    /**
     * @param start
     * @param stop
     * @param step
     * @param dtype
     * @return a new 1D dataset of given type, filled with values determined by parameters
     */
    public static AbstractDataset arange(final double start, final double stop, final double step,
            final int dtype) {
        if ((step > 0) != (start <= stop)) {
            return null;
        }

        switch (dtype) {
        case BOOL:
            break;
        case INT8:
            return ByteDataset.arange(start, stop, step);
        case INT16:
            return ShortDataset.arange(start, stop, step);
        case INT32:
            return IntegerDataset.arange(start, stop, step);
        case INT64:
            return LongDataset.arange(start, stop, step);
        case FLOAT32:
            return FloatDataset.arange(start, stop, step);
        case FLOAT64:
            return DoubleDataset.arange(start, stop, step);
        case COMPLEX64:
            return ComplexFloatDataset.arange(start, stop, step);
        case COMPLEX128:
            return ComplexDoubleDataset.arange(start, stop, step);
        }
        throw new IllegalArgumentException("dtype not known");
    }

    /**
     * @return true if dataset is complex
     */
    public boolean isComplex() {
        int type = getDtype();
        return type == COMPLEX64 || type == COMPLEX128;
    }

    /**
     * @return real part of dataset as new dataset
     */
    public AbstractDataset real() {
        return this;
    }

    /**
     * Fill dataset with number represented by given object
     * 
     * @param obj
     * @return filled dataset
     */
    abstract public AbstractDataset fill(final Object obj);

    /**
     * Get an element from given absolute index as a boolean - note this index does not take in account the item size so
     * be careful when using with multi-element items
     * 
     * @param index
     * @return element as boolean
     */
    abstract public boolean getElementBooleanAbs(final int index);

    /**
     * Get an element from given absolute index as a double - note this index does not take in account the item size so
     * be careful when using with multi-element items
     * 
     * @param index
     * @return element as double
     */
    abstract public double getElementDoubleAbs(final int index);

    /**
     * Get an element from given absolute index as a long - note this index does not take in account the item size so be
     * careful when using with multi-element items
     * 
     * @param index
     * @return element as long
     */
    abstract public long getElementLongAbs(final int index);

    /**
     * Get an item from given absolute index as an object - note this index does not take in account the item size so be
     * careful when using with multi-element items
     * 
     * @param index
     * @return item
     */
    abstract public Object getObjectAbs(final int index);

    /**
     * Get an item from given absolute index as a string - note this index does not take in account the item size so be
     * careful when using with multi-element items
     * 
     * @param index
     * @return item
     */
    abstract public String getStringAbs(final int index);

    /**
     * Set an item at absolute index from an object - note this index does not take into account the item size so be
     * careful when using with multi-element items
     * @param index
     * @param obj
     */
    abstract public void setObjectAbs(final int index, final Object obj);

    /**
     * In-place sort of dataset
     * 
     * @param axis
     *            to sort along
     * @return sorted dataset
     */
    public AbstractDataset sort(Integer axis) {
        int dtype = getDtype();
        if (dtype == BOOL || dtype == COMPLEX64 || dtype == COMPLEX128 || getElementsPerItem() != 1) {
            throw new UnsupportedOperationException("Cannot sort dataset");
        }
        if (axis == null) {
            if (dataShape != null) { // make contiguous
                AbstractDataset s = clone();
                odata = s.odata;
                setData();
                dataShape = null;
            }
            switch (dtype) {
            case INT8:
                Arrays.sort((byte[]) odata);
                break;
            case INT16:
                Arrays.sort((short[]) odata);
                break;
            case INT32:
                Arrays.sort((int[]) odata);
                break;
            case INT64:
                Arrays.sort((long[]) odata);
                break;
            case FLOAT32:
                Arrays.sort((float[]) odata);
                break;
            case FLOAT64:
                Arrays.sort((double[]) odata);
                break;
            }
        } else {
            axis = checkAxis(axis);

            AbstractDataset ads = zeros(new int[] { shape[axis] }, dtype);
            Serializable adata = ads.getBuffer();

            PositionIterator pi = getPositionIterator(axis);
            int[] pos = pi.getPos();
            boolean[] hit = pi.getOmit();
            while (pi.hasNext()) {
                copyItemsFromAxes(pos, hit, ads);
                switch (dtype) {
                case INT8:
                    Arrays.sort((byte[]) adata);
                    break;
                case INT16:
                    Arrays.sort((short[]) adata);
                    break;
                case INT32:
                    Arrays.sort((int[]) adata);
                    break;
                case INT64:
                    Arrays.sort((long[]) adata);
                    break;
                case FLOAT32:
                    Arrays.sort((float[]) adata);
                    break;
                case FLOAT64:
                    Arrays.sort((double[]) adata);
                    break;
                }
                setItemsOnAxes(pos, hit, ads.getBuffer());
            }
        }
        return this;
    }

    @Override
    abstract public AbstractDataset getSlice(final int[] start, final int[] stop, final int[] step);

    /**
     * Get a slice of the dataset. The returned dataset is a copied selection of items
     * 
     * @param iterator Slice iterator
     * @return The dataset of the sliced data
     */
    abstract public AbstractDataset getSlice(final SliceIterator iterator);

    @Override
    public AbstractDataset getSlice(Slice... slice) {
        final int rank = shape.length;
        final int[] start = new int[rank];
        final int[] stop = new int[rank];
        final int[] step = new int[rank];

        Slice.convertFromSlice(slice, shape, start, stop, step);

        AbstractDataset s = getSlice(start, stop, step);
        if (Arrays.equals(shape, s.shape)) {
            s.setName(name);
        } else {
            s.setName(name + '[' + Slice.createString(slice) + ']');
        }
        return s;
    }

    @Override
    public AbstractDataset getSlice(IMonitor monitor, Slice... slice) throws ScanFileHolderException {
        return getSlice(slice);
    }

    @Override
    public AbstractDataset getSlice(IMonitor monitor, int[] start, int[] stop, int[] step)
            throws ScanFileHolderException {
        return getSlice(start, stop, step);
    }

    /**
     * 
     * @param object
     * @param slice
     */
    public void setSlice(Object object, Slice... slice) {
        final int rank = shape.length;
        final int[] start = new int[rank];
        final int[] stop = new int[rank];
        final int[] step = new int[rank];

        Slice.convertFromSlice(slice, shape, start, stop, step);

        setSlice(object, start, stop, step);
    }

    /**
     * Populate a dataset with part of current dataset
     * 
     * @param result
     * @param iter
     *            over current dataset
     */
    abstract public void fillDataset(AbstractDataset result, IndexIterator iter);

    /**
     * Test if all items are true
     */
    public boolean all() {
        final IndexIterator iter = getIterator();
        while (iter.hasNext()) {
            if (!getElementBooleanAbs(iter.index)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param axis
     * @return dataset where items are true if all items along axis are true
     */
    public BooleanDataset all(final int axis) {
        int rank = getRank();

        int[] oshape = getShape();
        int alen = oshape[axis];
        oshape[axis] = 1;

        int[] nshape = AbstractDataset.squeezeShape(oshape, false);
        BooleanDataset all = new BooleanDataset(nshape);

        IndexIterator qiter = all.getIterator(true);
        int[] qpos = qiter.getPos();
        int[] spos = oshape;

        while (qiter.hasNext()) {
            int i = 0;
            for (; i < axis; i++) {
                spos[i] = qpos[i];
            }
            spos[i++] = 0;
            for (; i < rank; i++) {
                spos[i] = qpos[i - 1];
            }

            boolean result = true;
            for (int j = 0; j < alen; j++) {
                spos[axis] = j;
                if (getDouble(spos) == 0) {
                    result = false;
                    break;
                }
            }
            all.set(result, qpos);
        }
        return all;
    }

    /**
     * Test if any items are true
     */
    public boolean any() {
        final IndexIterator iter = getIterator();
        while (iter.hasNext()) {
            if (getElementBooleanAbs(iter.index)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param axis
     * @return dataset where items are true if any items along axis are true
     */
    public BooleanDataset any(final int axis) {
        int rank = getRank();

        int[] oshape = getShape();
        int alen = oshape[axis];
        oshape[axis] = 1;

        int[] nshape = AbstractDataset.squeezeShape(oshape, false);
        BooleanDataset all = new BooleanDataset(nshape);

        IndexIterator qiter = all.getIterator(true);
        int[] qpos = qiter.getPos();
        int[] spos = oshape;

        while (qiter.hasNext()) {
            int i = 0;
            for (; i < axis; i++) {
                spos[i] = qpos[i];
            }
            spos[i++] = 0;
            for (; i < rank; i++) {
                spos[i] = qpos[i - 1];
            }

            boolean result = false;
            for (int j = 0; j < alen; j++) {
                spos[axis] = j;
                if (getDouble(spos) != 0) {
                    result = true;
                    break;
                }
            }
            all.set(result, qpos);
        }
        return all;
    }

    /**
     * In-place addition with object o
     * 
     * @param o
     * @return sum dataset
     */
    abstract public AbstractDataset iadd(final Object o);

    /**
     * In-place subtraction with object o
     * 
     * @param o
     * @return difference dataset
     */
    abstract public AbstractDataset isubtract(final Object o);

    /**
     * In-place multiplication with object o
     * 
     * @param o
     * @return product dataset
     */
    abstract public AbstractDataset imultiply(final Object o);

    /**
     * In-place division with object o
     * 
     * @param o
     * @return dividend dataset
     */
    abstract public AbstractDataset idivide(final Object o);

    /**
     * In-place floor division with object o
     * 
     * @param o
     * @return dividend dataset
     */
    public AbstractDataset ifloorDivide(final Object o) {
        return idivide(o).ifloor();
    }

    /**
     * In-place remainder
     * 
     * @return remaindered dataset
     */
    abstract public AbstractDataset iremainder(final Object o);

    /**
     * In-place floor
     * 
     * @return floored dataset
     */
    abstract public AbstractDataset ifloor();

    /**
     * In-place raise to power of object o
     * 
     * @param o
     * @return raised dataset
     */
    abstract public AbstractDataset ipower(final Object o);

    /**
     * Calculate residual of dataset with object o
     * 
     * @param o
     * @return sum of the squares of the differences
     */
    abstract public double residual(final Object o);

    /**
     * Get value from store
     * 
     * @param key
     * @return value
     */
    protected Object getStoredValue(String key) {
        if (storedValues == null) {
            return null;
        }

        return storedValues.get(key);
    }

    /**
     * Set value in store
     * <p>
     * This is a <b>private method</b>: do not use!
     * 
     * @param key
     * @param obj
     */
    public void setStoredValue(String key, Object obj) {
        if (storedValues == null) {
            storedValues = new HashMap<String, Object>();
        }

        storedValues.put(key, obj);
    }

    /**
     * Calculate summary statistics for a dataset
     */
    protected void calculateSummaryStats() {
        final IndexIterator iter = getIterator();
        final SummaryStatistics stats = new SummaryStatistics();

        while (iter.hasNext()) {
            final double val = getElementDoubleAbs(iter.index);
            if (Double.isInfinite(val) || Double.isNaN(val)) {
                continue;
            }

            stats.addValue(val);
        }

        // now all the calculations are done, add the values into store
        setStoredValue("stats", stats);
    }

    /**
     * Calculate summary statistics for a dataset along an axis
     */
    protected void calculateSummaryStats(final int axis) {
        int rank = getRank();

        int[] oshape = getShape();
        int alen = oshape[axis];
        oshape[axis] = 1;

        int[] nshape = new int[rank - 1];
        for (int i = 0; i < axis; i++) {
            nshape[i] = oshape[i];
        }
        for (int i = axis + 1; i < rank; i++) {
            nshape[i - 1] = oshape[i];
        }

        final int dtype = getDtype();
        IntegerDataset count = new IntegerDataset(nshape);
        AbstractDataset max = zeros(nshape, dtype);
        AbstractDataset min = zeros(nshape, dtype);
        IntegerDataset maxIndex = new IntegerDataset(nshape);
        IntegerDataset minIndex = new IntegerDataset(nshape);
        AbstractDataset sum = zeros(nshape, getLargestDType(dtype));
        DoubleDataset mean = new DoubleDataset(nshape);
        DoubleDataset var = new DoubleDataset(nshape);

        IndexIterator qiter = max.getIterator(true);
        int[] qpos = qiter.getPos();
        int[] spos = oshape.clone();

        while (qiter.hasNext()) {
            int i = 0;
            for (; i < axis; i++) {
                spos[i] = qpos[i];
            }
            spos[i++] = 0;
            for (; i < rank; i++) {
                spos[i] = qpos[i - 1];
            }

            final SummaryStatistics stats = new SummaryStatistics();
            for (int j = 0; j < alen; j++) {
                spos[axis] = j;
                final double val = getDouble(spos);
                if (Double.isInfinite(val) || Double.isNaN(val)) {
                    continue;
                }

                stats.addValue(val);
            }

            count.setAbs(qiter.index, (int) stats.getN());

            final double amax = stats.getMax();
            max.setObjectAbs(qiter.index, amax);
            for (int j = 0; j < alen; j++) {
                spos[axis] = j;
                final double val = getDouble(spos);
                if (val == amax) {
                    maxIndex.setAbs(qiter.index, j);
                    break;
                }
            }
            final double amin = stats.getMin();
            min.setObjectAbs(qiter.index, amax);
            for (int j = 0; j < alen; j++) {
                spos[axis] = j;
                final double val = getDouble(spos);
                if (val == amin) {
                    minIndex.setAbs(qiter.index, j);
                    break;
                }
            }
            sum.setObjectAbs(qiter.index, stats.getSum());
            mean.setAbs(qiter.index, stats.getMean());
            var.setAbs(qiter.index, stats.getVariance());
        }
        setStoredValue("count-" + axis, count);
        storedValues.put("max-" + axis, max);
        storedValues.put("min-" + axis, min);
        storedValues.put("sum-" + axis, sum);
        storedValues.put("mean-" + axis, mean);
        storedValues.put("var-" + axis, var);
        storedValues.put("maxIndex-" + axis, maxIndex);
        storedValues.put("minIndex-" + axis, minIndex);
    }

    /**
     * Calculate minimum and maximum for a dataset
     */
    protected void calculateMaxMin() {
        IndexIterator iter = getIterator();
        double amax = Double.NEGATIVE_INFINITY;
        double amin = Double.POSITIVE_INFINITY;
        double hash = 0;
        boolean hasNans = false;
        while (iter.hasNext()) {
            if (hasNans) { // ignore rest of values once a NaN has been encountered
                hash = (hash * 19) % Integer.MAX_VALUE;
                continue;
            }
            final double val = getElementDoubleAbs(iter.index);
            if (Double.isInfinite(val) || Double.isNaN(val)) {
                hash = (hash * 19) % Integer.MAX_VALUE;
                if (Double.isNaN(val)) {
                    amax = Double.NaN;
                    amin = Double.NaN;
                    hasNans = true;
                    continue;
                }
            } else {
                hash = (hash * 19 + val) % Integer.MAX_VALUE;
            }

            if (val > amax) {
                amax = val;
            }
            if (val < amin) {
                amin = val;
            }
        }

        int ihash = ((int) hash) * 19 + getDtype() * 17 + getElementsPerItem();
        int rank = shape.length;
        for (int i = 0; i < rank; i++) {
            ihash = ihash * 17 + shape[i];
        }
        setStoredValue("max", fromDoubleToNumber(amax));
        storedValues.put("min", fromDoubleToNumber(amin));
        storedValues.put("hash", ihash);
    }

    private Number fromDoubleToNumber(double x) {
        switch (getDtype()) {
        case BOOL:
        case INT32:
            return Integer.valueOf((int) x);
        case INT8:
            return Byte.valueOf((byte) x);
        case INT16:
            return Short.valueOf((short) x);
        case INT64:
            return Long.valueOf((long) x);
        case FLOAT32:
            return Float.valueOf((float) x);
        case FLOAT64:
            return Double.valueOf(x);
        }
        return null;
    }

    // return biggest native primitive if integer (should test for 64bit?)
    private Number fromDoubleToBiggestNumber(double x) {
        switch (getDtype()) {
        case BOOL:
        case INT8:
        case INT16:
        case INT32:
            return Integer.valueOf((int) x);
        case INT64:
            return Long.valueOf((long) x);
        case FLOAT32:
            return Float.valueOf((float) x);
        case FLOAT64:
            return Double.valueOf(x);
        }
        return null;
    }

    private SummaryStatistics getStatistics() {
        SummaryStatistics stats = (SummaryStatistics) getStoredValue("stats");
        if (stats == null) {
            calculateSummaryStats();
            stats = (SummaryStatistics) getStoredValue("stats");
        }

        return stats;
    }

    private Object getMaxMin(String key) {
        Object value = getStoredValue(key);
        if (value == null) {
            calculateMaxMin();
            value = getStoredValue(key);
        }

        return value;
    }

    private Object getStatistics(int axis, String stat) {
        axis = checkAxis(axis);
        Object obj = getStoredValue(stat);

        if (obj == null) {
            calculateSummaryStats(axis);
            obj = getStoredValue(stat);
        }

        return obj;
    }

    @Override
    public Number max() {
        return (Number) getMaxMin("max");
    }

    /**
     * @return maxima along axis in dataset
     */
    public AbstractDataset max(int axis) {
        return (AbstractDataset) getStatistics(axis, "max-" + axis);
    }

    @Override
    public Number min() {
        return (Number) getMaxMin("min");
    }

    /**
     * @param axis
     * @return minima along axis in dataset
     */
    public AbstractDataset min(int axis) {
        return (AbstractDataset) getStatistics(axis, "min-" + axis);
    }

    /**
     * Find absolute index of maximum value
     * 
     * @return absolute index
     */
    public int argMax() {
        return get1DIndex(maxPos());
    }

    /**
     * Find indices of maximum values along given axis
     * 
     * @param axis
     * @return index dataset
     */
    public IntegerDataset argMax(int axis) {
        return (IntegerDataset) getStatistics(axis, "maxIndex-" + axis);
    }

    /**
     * Find absolute index of maximum value
     * 
     * @return absolute index
     */
    public int argMin() {
        return get1DIndex(minPos());
    }

    /**
     * Find indices of minimum values along given axis
     * 
     * @param axis
     * @return index dataset
     */
    public IntegerDataset argMin(int axis) {
        return (IntegerDataset) getStatistics(axis, "minIndex-" + axis);
    }

    abstract public boolean containsInfs();

    abstract public boolean containsNans();

    /**
     * @return peak-to-peak value, the difference of maximum and minimum of dataset
     */
    public Number peakToPeak() {
        return fromDoubleToNumber(max().doubleValue() - min().doubleValue());
    }

    /**
     * @param axis
     * @return peak-to-peak dataset, the difference of maxima and minima of dataset along axis
     */
    public AbstractDataset peakToPeak(int axis) {
        return Maths.subtract(max(axis), min(axis));
    }

    /**
     * @return sum over all items in dataset as a Number, array of doubles or a complex number
     */
    public Object sum() {
        return fromDoubleToBiggestNumber(getStatistics().getSum());
    }

    /**
     * @param axis
     * @return sum along axis in dataset
     */
    public AbstractDataset sum(int axis) {
        return (AbstractDataset) getStatistics(axis, "sum-" + axis);
    }

    /**
     * @return product over all items in dataset
     */
    public Object product() {
        return Stats.product(this);
    }

    /**
     * @param axis
     * @return product along axis in dataset
     */
    public AbstractDataset product(int axis) {
        return Stats.product(this, axis);
    }

    /**
     * @return mean of all items in dataset as a double, array of doubles or a complex number
     */
    public Object mean() {
        return getStatistics().getMean();
    }

    /**
     * @param axis
     * @return mean along axis in dataset
     */
    public AbstractDataset mean(int axis) {
        return (AbstractDataset) getStatistics(axis, "mean-" + axis);
    }

    /**
     * @return sample variance of whole dataset
     * @see #variance(boolean)
     */
    public Number variance() {
        return variance(false);
    }

    /**
     * The sample variance can be calculated in two ways: if the dataset is considered as the entire population then the
     * sample variance is simply the second central moment:
     * 
     * <pre>
     *    sum((x_i - m)^2)/N
     * where {x_i} are set of N population values and m is the mean
     *    m = sum(x_i)/N
     * </pre>
     * 
     * Otherwise, if the dataset is a set of samples (with replacement) from the population then
     * 
     * <pre>
     *    sum((x_i - m)^2)/(N-1)
     * where {x_i} are set of N sample values and m is the unbiased estimate of the mean
     *    m = sum(x_i)/N
     * </pre>
     * 
     * Note that the second definition is also the unbiased estimator of population variance.
     * 
     * @param isDatasetWholePopulation
     * @return sample variance
     */
    public Number variance(boolean isDatasetWholePopulation) {
        SummaryStatistics stats = getStatistics();

        if (isDatasetWholePopulation) {
            StorelessUnivariateStatistic oldVar = stats.getVarianceImpl();
            stats.setVarianceImpl(new Variance(false));
            Number var = stats.getVariance();
            stats.setVarianceImpl(oldVar);
            return var;
        }
        return stats.getVariance();
    }

    /**
     * @param axis
     * @return sample variance along axis in dataset
     * @see #variance(boolean)
     */
    public AbstractDataset variance(int axis) {
        return (AbstractDataset) getStatistics(axis, "var-" + axis);
    }

    /**
     * Standard deviation is square root of the variance
     * 
     * @return sample standard deviation of all items in dataset
     * @see #variance()
     */
    public Number stdDeviation() {
        return Math.sqrt(variance().doubleValue());
    }

    /**
     * Standard deviation is square root of the variance
     * 
     * @param isDatasetWholePopulation
     * @return sample standard deviation of all items in dataset
     * @see #variance(boolean)
     */
    public Number stdDeviation(boolean isDatasetWholePopulation) {
        return Math.sqrt(variance(isDatasetWholePopulation).doubleValue());
    }

    /**
     * @param axis
     * @return standard deviation along axis in dataset
     */
    public AbstractDataset stdDeviation(int axis) {
        final AbstractDataset v = (AbstractDataset) getStatistics(axis, "var-" + axis);
        return Maths.sqrt(v);
    }

    /**
     * @return root mean square
     */
    public Number rootMeanSquare() {
        final SummaryStatistics stats = getStatistics();
        final double mean = stats.getMean();
        return Math.sqrt(stats.getVariance() + mean * mean);
    }

    /**
     * @param axis
     * @return root mean square along axis in dataset
     */
    public AbstractDataset rootMeanSquare(int axis) {
        AbstractDataset v = (AbstractDataset) getStatistics(axis, "var-" + axis);
        AbstractDataset m = (AbstractDataset) getStatistics(axis, "mean-" + axis);
        AbstractDataset result = Maths.power(m, 2);
        return Maths.sqrt(result.iadd(v));
    }

    /**
     * @see DatasetUtils#put(AbstractDataset, int[], Object[])
     */
    public AbstractDataset put(final int[] indices, Object[] values) {
        return DatasetUtils.put(this, indices, values);
    }

    /**
     * @see DatasetUtils#take(AbstractDataset, int[], Integer)
     */
    public AbstractDataset take(final int[] indices, final Integer axis) {
        return DatasetUtils.take(this, indices, axis);
    }

    /**
     * Set item from compatible dataset in a direct and speedy way
     * 
     * @param dindex
     * @param sindex
     * @param src
     *            is the source data buffer
     */
    protected abstract void setItemDirect(final int dindex, final int sindex, final Object src);

    public int getStringPolicy() {
        return stringPolicy;
    }

    public void setStringPolicy(int stringPolicy) {
        this.stringPolicy = stringPolicy;
    }

    /*
     * Note that all error values are stored internally already squared to 
     * ease calculation time on error propagation
     */
    protected Number errorValue = 0;
    protected AbstractDataset errorData = null;

    /**
     * Sets the error for the dataset to a single value for all points in the dataset
     * @param errorValue The error value for all elements of the dataset 
     */
    public void setError(Number errorValue) {
        this.errorData = null;
        if (errorValue instanceof Integer) {
            this.errorValue = errorValue.intValue() * errorValue.intValue();
            return;
        }
        if (errorValue instanceof Float) {
            this.errorValue = errorValue.floatValue() * errorValue.floatValue();
            return;
        }
        if (errorValue instanceof Long) {
            this.errorValue = errorValue.longValue() * errorValue.longValue();
            return;
        }
        if (errorValue instanceof Short) {
            this.errorValue = errorValue.intValue() * errorValue.intValue();
            return;
        }
        if (errorValue instanceof Byte) {
            this.errorValue = errorValue.intValue() * errorValue.intValue();
            return;
        }

        // If all else fails
        this.errorValue = errorValue.doubleValue() * errorValue.doubleValue();
        return;
    }

    /**
     * Sets the error values for the dataset point by point.
     * @param errorArray The Abstract dataset which contains all the error values
     */
    public void setError(AbstractDataset errorArray) {
        if (!this.isCompatibleWith(errorArray)) {
            throw new IllegalArgumentException("Error array dataset is incompatible with this dataset");
        }
        this.errorData = Maths.square(errorArray);
        this.errorValue = null;
    }

    /**
     * Gets the error array from the dataset, or creates an error array if all 
     * values are the same
     * @return the AbstractDataset which contains the error information
     */
    public AbstractDataset getError() {
        if (errorData == null) {
            DoubleDataset dataset = new DoubleDataset(shape);
            dataset.fill(errorValue.doubleValue());
            return Maths.sqrt(dataset);
        }
        return Maths.sqrt(errorData);
    }

    /**
     * Gets the error value for a single point in the dataset
     * @param pos of the point to be referenced 
     * @return the value of the error at this point as a double
     */
    public double getErrorDouble(int... pos) {
        if (errorData == null) {
            return Math.sqrt(errorValue.doubleValue());
        }
        return Math.sqrt(errorData.getDouble(pos));
    }

    /**
     * Gets the error value for a single point in the dataset
     * @param pos of the point to be referenced 
     * @return the value of the error at this point as a float
     */
    public float getErrorFloat(int... pos) {
        if (errorData == null) {
            return errorValue.floatValue();
        }
        return (float) Math.sqrt(errorData.getFloat(pos));
    }

    protected IMetaData metadataStructure = null;

    @Override
    public void setMetadata(IMetaData metadata) {
        metadataStructure = metadata;
    }

    @Override
    public IMetaData getMetadata() {
        return metadataStructure;
    }
}