    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                lshape[i] = calcLength(start, stop, step);
    336                lstart[i] = start;
    337                lstep[i] = step;
    338        }
    339
    340        private static int calcLength(int start, int stop, int step) {
    341                int l;
    342                if (start == stop) {
    343                        l = 0;
    344                } else if (step > 0) {
    345                        l = Math.max(0, (stop - start - 1) / step + 1);
    346                } else {
    347                        l = Math.max(0, (stop - start + 1) / step + 1);
    348                }
    349                return l;
    350        }
    351
    352        /**
    353         * Returns an array of shapes of the source Dataset (this can change for
    354         * dynamic Datasets).
    355         *
    356         * @return shape of source Dataset
    357         */
    358        public int[] getSourceShape() {
    359                return oshape;
    360        }
    361
    362        /**
    363         * Returns an array of maximals shapes
    364         *
    365         * @return maximum shape
    366         */
    367        public int[] getMaxShape() {
    368                return mshape;
    369        }
    370
    371        /**
    372         * Returns {@code true} if the slice makes shape larger, else {@code false}.
    373         *
    374         * @return {@code true} if slice makes shape larger, {@code false} in the
    375         *         other case
    376         */
    377        public boolean isExpanded() {
    378                return expanded;
    379        }
    380
    381        /**
    382         * Returns an array of resulting shapes (this can change if the start, stop,
    383         * step arrays are changed).
    384         *
    385         * @return resulting shape
    386         */
    387        public int[] getShape() {
    388                return lshape;
    389        }
    390
    391        /**
    392         * Returns an array of the starts values.
    393         *
    394         * @return start values
    395         */
    396        public int[] getStart() {
    397                return lstart;
    398        }
    399
    400        /**
    401         * Returns an array of stops values.
    402         * <p>
    403         * Note : stop values are clamped to -1 for <b>negative</b> steps
    404         * </p>
    405         *
    406         * @return stop values
    407         */
    408        public int[] getStop() {
    409                return lstop;
    410        }
    411
    412        /**
    413         * Returns an array of the steps values.
    414         *
    415         * @return step values
    416         */
    417        public int[] getStep() {
    418                return lstep;
    419        }
    420
    421        /**
    422         * Returns {@code true} if all of originals shapes are covered by positive
    423         * steps slices, else {@code false}.
    424         *
    425         * @return {@code true} if all of originals shapes is covered by this slice
    426         *         with positive steps, {@code false} in the other case.
    427         */
    428        public boolean isAll() {
    429                if (expanded) {
    430                        return false;
    431                }
    432
    433                boolean allData = Arrays.equals(oshape, getShape());
    434                if (allData) {
    435                        for (int i = 0; i < lshape.length; i++) {
    436                                if (lstep[i] < 0) {
    437                                        allData = false;
    438                                        break;
    439                                }
    440                        }
    441                }
    442                return allData;
    443        }
    444
    445        /**
    446         * Flips the slice direction in given dimension, this means that slice
    447         * begins at previous end point, steps in the opposite direction, and
    448         * finishes at the previous start point.
    449         *
    450         * @param i
    451         *            dimension to flip
    452         */
    453        public SliceND flip(int i) {
    454                i = ShapeUtils.checkAxis(lshape.length, i);
    455
    456                int beg = lstart[i];
    457                int end = lstop[i];
    458                int step = lstep[i];
    459                int del = lstep[i] > 0 ? 1 : -1;
    460
    461                int num = (end - beg - del) / step + 1; // number of steps
    462                lstart[i] = beg + (num - 1) * step;
    463                lstop[i] = Math.max(beg - step, -1);
    464                lstep[i] = -step;
    465
    466                return this;
    467        }
    468
    469        /**
    470         * Flips slices directions in all dimensions, this means that all slices are
    471         * beginning at previous end point, steps are in the opposite direction, and
    472         * finishes are at the previous start point.
    473         */
    474        public SliceND flip() {
    475                int orank = lshape.length;
    476                for (int i = 0; i < orank; i++) {
    477                        flip(i);
    478                }
    479
    480                return this;
    481        }
    482
    483        /**
    484         * Converts to a slice array all the Slices of the SliceND.
    485         *
    486         * @return a Slice array
    487         */
    488        public Slice[] convertToSlice() {
    489                int orank = lshape.length;
    490
    491                Slice[] slice = new Slice[orank];
    492
    493                for (int j = 0; j < orank; j++) {
    494                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
    495                }
    496
    497                return slice;
    498        }
    499
    500        /**
    501         * Creates a deep copy of the SliceND.
    502         *
    503         * @return New SliceND with the current SliceND properties
    504         */
    505        @Override
    506        public SliceND clone() {
    507                SliceND c = new SliceND(oshape);
    508                for (int i = 0; i < lshape.length; i++) {
    509                        c.lstart[i] = lstart[i];
    510                        c.lstop[i] = lstop[i];
    511                        c.lstep[i] = lstep[i];
    512                        c.lshape[i] = lshape[i];
    513                }
    514                c.expanded = expanded;
    515                return c;
    516        }
    517
    518        /**
    519         * Returns a string construction of the sliceND with the python form.
    520         *
    521         * @return Constructed String of all Slices
    522         */
    523        @Override
    524        public String toString() {
    525                final int rank = lshape.length;
    526                if (rank == 0) {
    527                        return "";
    528                }
    529                StringBuilder s = new StringBuilder();
    530                for (int i = 0; i < rank; i++) {
    531                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
    532                        s.append(',');
    533                }
    534
    535                return s.substring(0, s.length() - 1);
    536        }
    537
    538        /**
    539         * Creats SliceND from dataset.
    540         *
    541         * @param data
    542         *            ILazyDataset to treat
    543         * @param start
    544         *            Array of starts indexes
    545         * @param stop
    546         *            Array of stops indexes
    547         * @return Constructed SliceND
    548         */
    549        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
    550                return createSlice(data, start, stop, null);
    551        }
    552
    553        /**
    554         * Creating SliceND from dataset.
    555         *
    556         * @param data
    557         *            ILazyDataset to treat
    558         * @param start
    559         *            Array of starts indexes
    560         * @param stop
    561         *            Array of stops indexes
    562         * @param step
    563         *            Array of steps
    564         * @return Constructed SliceND
    565         */
    566        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
    567                if (data instanceof IDynamicDataset) {
    568                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
    569                }
    570                return new SliceND(data.getShape(), start, stop, step);
    571        }
    572
    573        /**
    574         * Create SliceND without sanity checks on start, stop and step
    575         * @param shape
    576         * @param maxShape
    577         * @param start
    578         * @param stop
    579         * @param step
    580         * @return slice
    581         */
    582        static SliceND createSlice(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
    583                if (shape == null) {
    584                        throw new IllegalArgumentException("Shape must not be null");
    585                }
    586                int rank = shape.length;
    587
    588                if (maxShape != null && maxShape.length != rank) {
    589                        throw new IllegalArgumentException("Max shape must have same rank as shape");
    590                }
    591                if (start.length != rank || stop.length != rank || step.length != rank) {
    592                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
    593                                        + start.length + ", stop=" + stop.length + ", step=" + step.length + ", and it needs " + rank);
    594                }
    595
    596                SliceND s = new SliceND(shape);
    597                if (maxShape != null) {
    598                        s.initMaxShape(maxShape);
    599                }
    600                s.lstart = start;
    601                s.lstop  = stop;
    602                s.lstep  = step;
    603
    604                for (int i = 0; i < rank; i++) {
    605                        int e = stop[i];
    606                        s.lshape[i] = calcLength(start[i], e, step[i]);
    607                        if (e > shape[i]) {
    608                                s.oshape[i] = e;
    609                                s.expanded = true;
    610                        }
    611                }
    612                return s;
    613        }
    614
    615}