    001/*-
    002 * Copyright (c) 2014, 2016 Diamond Light Source Ltd.
    003 *
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the Eclipse Public License v1.0
    006 * which accompanies this distribution, and is available at
    007 * http://www.eclipse.org/legal/epl-v10.html
    008 */
    009
    010package org.eclipse.january.dataset;
    011
    012import java.util.Arrays;
    013
    014/**
    015 * The {@code SliceND} class represents a slice through all dimensions of a multi-dimensional {@link org.eclipse.january.dataset.Dataset}.<br><br>
    016 * A slice comprises a starting position array, a stopping position array (not included) and a stepping size array.<br>
    017 * If a maximum shape is specified, slicing past the original shape is supported for positive
    018 * steps otherwise it is ignored. With unlimited dimensions, extending past the original shape is only
    019 * allowed if the stopping value is given.
    020 */
    021public class SliceND {
    022        private int[] lstart;
    023        private int[] lstop;
    024        private int[] lstep;
    025        private transient int[] lshape; // latest shape
    026        private int[] oshape; // source or original shape
    027        private int[] mshape; // max shape
    028
    029        private boolean expanded;
    030
    031        /**
    032         * Construct a nD Slice for whole of shape.
    033         *
    034         * @param shape
    035         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    036         */
    037        public SliceND(final int[] shape) {
    038                final int rank = shape.length;
    039                lstart = new int[rank];
    040                lstop = shape.clone();
    041                lstep = new int[rank];
    042                Arrays.fill(lstep, 1);
    043                lshape = shape.clone();
    044                oshape = shape.clone();
    045                mshape = oshape;
    046                expanded = false;
    047        }
    048
    049        /**
    050         * Construct a nD Slice from an array of 1D slices.
    051         *
    052         * @param shape
    053         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    054         * @param slice
    055         *            Slice for each dimension of ND slice
    056         */
    057        public SliceND(final int[] shape, Slice... slice) {
    058                this(shape, null, slice);
    059        }
    060
    061        /**
    062         * Construct a nD Slice from an array of 1D slices, if the maxShape is
    063         * {@code null}, it will be set to the maximum shape of the nD Slice.
    064         *
    065         * @param shape
    066         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    067         * @param maxShape,
    068         *            may be {@code null}
    069         * @param slice
    070         *            Slice for each dimension of ND slice
    071         */
    072        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
    073                this(shape);
    074
    075                if (maxShape != null) {
    076                        initMaxShape(maxShape);
    077                }
    078
    079                if (slice != null) {
    080                        final int length = slice.length;
    081                        final int rank = shape.length;
    082                        if (length > rank) {
    083                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
    084                        }
    085                        for (int i = 0; i < length; i++) {
    086                                Slice s = slice[i];
    087                                if (s != null) {
    088                                        setSlice(i, s);
    089                                }
    090                        }
    091                }
    092        }
    093
    094        private void initMaxShape(int[] maxShape) {
    095                final int rank = oshape.length;
    096                if (maxShape.length != rank) {
    097                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
    098                }
    099                mshape = maxShape.clone();
    100                for (int i = 0; i < rank; i++) {
    101                        int m = mshape[i];
    102                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
    103                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
    104                        }
    105                }
    106        }
    107
    108        /**
    109         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
    110         * will be set to the maximum shape of the nD Slice, the start will be set
    111         * to 0, stop is by default equal to the entire size of the set, step is
    112         * defaultly set to 1.
    113         *
    114         * @param shape
    115         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    116         * @param start
    117         *            Array of starts points, may be {@code null}
    118         * @param stop
    119         *            Array of stops points, may be {@code null}
    120         * @param step
    121         *            Array of steps, may be {@code null}
    122         */
    123        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
    124                this(shape, null, start, stop, step);
    125        }
    126
    127        /**
    128         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
    129         * will be set to the maximum shape of the nD Slice, the start will be set
    130         * to 0, stop is by default equal to the entire size of the set, step is
    131         * defaultly set to 1.
    132         *
    133         * @param shape
    134         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    135         * @param maxShape
    136         *            Array of maximals shapes, may be {@code null}
    137         * @param start
    138         *            Array of starts points, may be {@code null}
    139         * @param stop
    140         *            Array of stops points, may be {@code null}
    141         * @param step
    142         *            Array of steps, may be {@code null}
    143         */
    144        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
    145                // number of steps, or new shape, taken in each dimension is
    146                // shape = (stop - start + step - 1) / step if step > 0
    147                // (stop - start + step + 1) / step if step < 0
    148                //
    149                // thus the final index in each dimension is
    150                // start + (shape-1)*step
    151
    152                int rank = shape.length;
    153
    154                if (start == null) {
    155                        lstart = new int[rank];
    156                } else {
    157                        lstart = start.clone();
    158                }
    159                if (stop == null) {
    160                        lstop = new int[rank];
    161                } else {
    162                        lstop = stop.clone();
    163                }
    164                if (step == null) {
    165                        lstep = new int[rank];
    166                        Arrays.fill(lstep, 1);
    167                } else {
    168                        lstep = step.clone();
    169                }
    170
    171                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
    172                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
    173                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
    174                }
    175
    176                lshape = new int[rank];
    177                oshape = shape.clone();
    178                if (maxShape == null) {
    179                        mshape = oshape;
    180                } else {
    181                        initMaxShape(maxShape);
    182                }
    183
    184                for (int i = 0; i < rank; i++) {
    185                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
    186                }
    187        }
    188
    189        /**
    190         * Set slice for given dimension, if the start is {@code null} it will be
    191         * set to 0, stop is by default equal to the entire size of the set.
    192         *
    193         * @param i
    194         *            dimension
    195         * @param start
    196         *            Start point, may be {@code null} to imply start of dimension
    197         * @param stop
    198         *            Stop point, may be {@code null} to imply end of dimension
    199         * @param step
    200         *            Slice step
    201         */
    202        public void setSlice(int i, Integer start, Integer stop, int step) {
    203                internalSetSlice(i, start, stop, step);
    204        }
    205
    206        /**
    207         * Set slice for given dimension, if the start is {@code null} it will be
    208         * set to 0, stop is by default equal to the entire size of the set.
    209         *
    210         * @param i
    211         *            dimension
    212         * @param start
    213         *            Start point, may be {@code null} to imply start of dimension
    214         * @param stop
    215         *            Stop point, may be {@code null} to imply end of dimension
    216         * @param step
    217         *            Slice step
    218         */
    219        public void setSlice(int i, int start, int stop, int step) {
    220                internalSetSlice(i, start, stop, step);
    221        }
    222
    223        /**
    224         * Set slice for given dimension.
    225         *
    226         * @param i
    227         *            Dimension
    228         * @param slice
    229         *            Slice with wanted properties to set
    230         * @since 2.0
    231         */
    232        public void setSlice(int i, Slice slice) {
    233                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
    234        }
    235
    236        /**
    237         * Set slice for given dimension, if the start is {@code null} it will be
    238         * set to 0, stop is by default equal to the entire size of the set.
    239         *
    240         * @param i
    241         *            dimension
    242         * @param start
    243         *            Start point, may be {@code null} to imply start of dimension
    244         * @param stop
    245         *            Stop point, may be {@code null} to imply end of dimension
    246         * @param step
    247         *            Slice step
    248         */
    249        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
    250                if (step == 0) {
    251                        throw new IllegalArgumentException("Step size must not be zero");
    252                }
    253                i = ShapeUtils.checkAxis(oshape.length, i);
    254                final int s = oshape[i];
    255                final int m = mshape[i];
    256
    257                if (start == null) {
    258                        start = step > 0 ? 0 : s - 1;
    259                } else if (start < 0) {
    260                        start += s;
    261                }
    262                if (step > 0) {
    263                        if (start < 0) {
    264                                start = 0;
    265                        } else if (start > s) {
    266                                if (m == s) {
    267                                        start = s;
    268                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
    269                                        start = m;
    270                                }
    271                        }
    272
    273                        if (stop == null) {
    274                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
    275                                        throw new IllegalArgumentException(
    276                                                        "To extend past current dimension in unlimited case, a stop value must be specified");
    277                                }
    278                                stop = s;
    279                        } else if (stop < 0) {
    280                                stop += s;
    281                        }
    282                        if (stop < 0) {
    283                                stop = 0;
    284                        } else if (stop > s) {
    285                                if (m == s) {
    286                                        stop = s;
    287                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    288                                        stop = m;
    289                                }
    290                        }
    291
    292                        if (start >= stop) {
    293                                if (start < s || m == s) {
    294                                        lstop[i] = start;
    295                                } else { // override end
    296                                        stop = start + step;
    297                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    298                                                stop = m;
    299                                        }
    300                                        lstop[i] = stop;
    301                                }
    302                        } else {
    303                                lstop[i] = stop;
    304                        }
    305
    306                        if (lstop[i] > s) {
    307                                oshape[i] = lstop[i];
    308                                expanded = true;
    309                        }
    310                } else {
    311                        if (start < 0) {
    312                                start = -1;
    313                        } else if (start >= s) {
    314                                start = s - 1;
    315                        }
    316
    317                        if (stop == null) {
    318                                stop = -1;
    319                        } else if (stop < 0) {
    320                                stop += s;
    321                        }
    322                        if (stop < -1) {
    323                                stop = -1;
    324                        } else if (stop >= s) {
    325                                stop = s - 1;
    326                        }
    327                        if (stop >= start) {
    328                                lstop[i] = start;
    329                        } else {
    330                                lstop[i] = stop;
    331                        }
    332                }
    333
    334                stop = lstop[i];
    335                if (start == stop) {
    336                        lshape[i] = 0;
    337                } else if (step > 0) {
    338                        lshape[i] = Math.max(0, (stop - start - 1) / step + 1);
    339                } else {
    340                        lshape[i] = Math.max(0, (stop - start + 1) / step + 1);
    341                }
    342                lstart[i] = start;
    343                lstep[i] = step;
    344        }
    345
    346        /**
    347         * Returns an array of shapes of the source Dataset (this can change for
    348         * dynamic Datasets).
    349         *
    350         * @return shape of source Dataset
    351         */
    352        public int[] getSourceShape() {
    353                return oshape;
    354        }
    355
    356        /**
    357         * Returns an array of maximals shapes
    358         *
    359         * @return maximum shape
    360         */
    361        public int[] getMaxShape() {
    362                return mshape;
    363        }
    364
    365        /**
    366         * Returns {@code true} if the slice makes shape larger, else {@code false}.
    367         *
    368         * @return {@code true} if slice makes shape larger, {@code false} in the
    369         *         other case
    370         */
    371        public boolean isExpanded() {
    372                return expanded;
    373        }
    374
    375        /**
    376         * Returns an array of resulting shapes (this can change if the start, stop,
    377         * step arrays are changed).
    378         *
    379         * @return resulting shape
    380         */
    381        public int[] getShape() {
    382                return lshape;
    383        }
    384
    385        /**
    386         * Returns an array of the starts values.
    387         *
    388         * @return start values
    389         */
    390        public int[] getStart() {
    391                return lstart;
    392        }
    393
    394        /**
    395         * Returns an array of stops values.
    396         * <p>
    397         * Note : stop values are clamped to -1 for <b>negative</b> steps
    398         * </p>
    399         *
    400         * @return stop values
    401         */
    402        public int[] getStop() {
    403                return lstop;
    404        }
    405
    406        /**
    407         * Returns an array of the steps values.
    408         *
    409         * @return step values
    410         */
    411        public int[] getStep() {
    412                return lstep;
    413        }
    414
    415        /**
    416         * Returns {@code true} if all of originals shapes are covered by positive
    417         * steps slices, else {@code false}.
    418         *
    419         * @return {@code true} if all of originals shapes is covered by this slice
    420         *         with positive steps, {@code false} in the other case.
    421         */
    422        public boolean isAll() {
    423                if (expanded) {
    424                        return false;
    425                }
    426
    427                boolean allData = Arrays.equals(oshape, getShape());
    428                if (allData) {
    429                        for (int i = 0; i < lshape.length; i++) {
    430                                if (lstep[i] < 0) {
    431                                        allData = false;
    432                                        break;
    433                                }
    434                        }
    435                }
    436                return allData;
    437        }
    438
    439        /**
    440         * Flips the slice direction in given dimension, this means that slice
    441         * begins at previous end point, steps in the opposite direction, and
    442         * finishes at the previous start point.
    443         *
    444         * @param i
    445         *            dimension to flip
    446         */
    447        public SliceND flip(int i) {
    448                i = ShapeUtils.checkAxis(lshape.length, i);
    449
    450                int beg = lstart[i];
    451                int end = lstop[i];
    452                int step = lstep[i];
    453                int del = lstep[i] > 0 ? 1 : -1;
    454
    455                int num = (end - beg - del) / step + 1; // number of steps
    456                lstart[i] = beg + (num - 1) * step;
    457                lstop[i] = Math.max(beg - step, -1);
    458                lstep[i] = -step;
    459
    460                return this;
    461        }
    462
    463        /**
    464         * Flips slices directions in all dimensions, this means that all slices are
    465         * beginning at previous end point, steps are in the opposite direction, and
    466         * finishes are at the previous start point.
    467         */
    468        public SliceND flip() {
    469                int orank = lshape.length;
    470                for (int i = 0; i < orank; i++) {
    471                        flip(i);
    472                }
    473
    474                return this;
    475        }
    476
    477        /**
    478         * Converts to a slice array all the Slices of the SliceND.
    479         *
    480         * @return a Slice array
    481         */
    482        public Slice[] convertToSlice() {
    483                int orank = lshape.length;
    484
    485                Slice[] slice = new Slice[orank];
    486
    487                for (int j = 0; j < orank; j++) {
    488                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
    489                }
    490
    491                return slice;
    492        }
    493
    494        /**
    495         * Creates a deep copy of the SliceND.
    496         *
    497         * @return New SliceND with the current SliceND properties
    498         */
    499        @Override
    500        public SliceND clone() {
    501                SliceND c = new SliceND(oshape);
    502                for (int i = 0; i < lshape.length; i++) {
    503                        c.lstart[i] = lstart[i];
    504                        c.lstop[i] = lstop[i];
    505                        c.lstep[i] = lstep[i];
    506                        c.lshape[i] = lshape[i];
    507                }
    508                c.expanded = expanded;
    509                return c;
    510        }
    511
    512        /**
    513         * Returns a string construction of the sliceND with the python form.
    514         *
    515         * @return Constructed String of all Slices
    516         */
    517        @Override
    518        public String toString() {
    519                final int rank = lshape.length;
    520                if (rank == 0) {
    521                        return "";
    522                }
    523                StringBuilder s = new StringBuilder();
    524                for (int i = 0; i < rank; i++) {
    525                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
    526                        s.append(',');
    527                }
    528
    529                return s.substring(0, s.length() - 1);
    530        }
    531
    532        /**
    533         * Creats SliceND from dataset.
    534         *
    535         * @param data
    536         *            ILazyDataset to treat
    537         * @param start
    538         *            Array of starts indexes
    539         * @param stop
    540         *            Array of stops indexes
    541         * @return Constructed SliceND
    542         */
    543        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
    544                return createSlice(data, start, stop, null);
    545        }
    546
    547        /**
    548         * Creating SliceND from dataset.
    549         *
    550         * @param data
    551         *            ILazyDataset to treat
    552         * @param start
    553         *            Array of starts indexes
    554         * @param stop
    555         *            Array of stops indexes
    556         * @param step
    557         *            Array of steps
    558         * @return Constructed SliceND
    559         */
    560        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
    561                if (data instanceof IDynamicDataset) {
    562                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
    563                }
    564                return new SliceND(data.getShape(), start, stop, step);
    565        }
    566}