    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        private static int[] copy(int[] in) {
    032                return in == null ? null : in.clone();
    033        }
    034
    035        /**
    036         * Construct a nD Slice for whole of shape.
    037         *
    038         * @param shape
    039         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    040         */
    041        public SliceND(final int[] shape) {
    042                final int rank = shape == null ? 0 : shape.length;
    043                lstart = new int[rank];
    044                lstop = copy(shape);
    045                lstep = new int[rank];
    046                Arrays.fill(lstep, 1);
    047                lshape = copy(shape);
    048                oshape = copy(shape);
    049                mshape = oshape;
    050                expanded = false;
    051        }
    052
    053        /**
    054         * Construct a nD Slice from an array of 1D slices.
    055         *
    056         * @param shape
    057         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    058         * @param slice
    059         *            Slice for each dimension of ND slice
    060         */
    061        public SliceND(final int[] shape, Slice... slice) {
    062                this(shape, null, slice);
    063        }
    064
    065        /**
    066         * Construct a nD Slice from an array of 1D slices, if the maxShape is
    067         * {@code null}, it will be set to the maximum shape of the nD Slice.
    068         *
    069         * @param shape
    070         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    071         * @param maxShape,
    072         *            may be {@code null}
    073         * @param slice
    074         *            Slice for each dimension of ND slice
    075         */
    076        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
    077                this(shape);
    078
    079                if (maxShape != null) {
    080                        initMaxShape(maxShape);
    081                }
    082
    083                if (slice != null) {
    084                        final int length = slice.length;
    085                        final int rank = shape.length;
    086                        if (length > rank) {
    087                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
    088                        }
    089                        for (int i = 0; i < length; i++) {
    090                                Slice s = slice[i];
    091                                if (s != null) {
    092                                        setSlice(i, s);
    093                                }
    094                        }
    095                }
    096        }
    097
    098        private void initMaxShape(int[] maxShape) {
    099                final int rank = oshape.length;
    100                if (maxShape.length != rank) {
    101                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
    102                }
    103                mshape = maxShape.clone();
    104                for (int i = 0; i < rank; i++) {
    105                        int m = mshape[i];
    106                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
    107                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
    108                        }
    109                }
    110        }
    111
    112        /**
    113         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
    114         * will be set to the maximum shape of the nD Slice, the start will be set
    115         * to 0, stop is by default equal to the entire size of the set, step is
    116         * defaultly set to 1.
    117         *
    118         * @param shape
    119         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    120         * @param start
    121         *            Array of starts points, may be {@code null}
    122         * @param stop
    123         *            Array of stops points, may be {@code null}
    124         * @param step
    125         *            Array of steps, may be {@code null}
    126         */
    127        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
    128                this(shape, null, start, stop, step);
    129        }
    130
    131        /**
    132         * Construct a nD Slice from parameters, if the maxShape is {@code null}, it
    133         * will be set to the maximum shape of the nD Slice, the start will be set
    134         * to 0, stop is by default equal to the entire size of the set, step is
    135         * defaultly set to 1.
    136         *
    137         * @param shape
    138         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    139         * @param maxShape
    140         *            Array of maximals shapes, may be {@code null}
    141         * @param start
    142         *            Array of starts points, may be {@code null}
    143         * @param stop
    144         *            Array of stops points, may be {@code null}
    145         * @param step
    146         *            Array of steps, may be {@code null}
    147         */
    148        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
    149                // number of steps, or new shape, taken in each dimension is
    150                // shape = (stop - start + step - 1) / step if step > 0
    151                // (stop - start + step + 1) / step if step < 0
    152                //
    153                // thus the final index in each dimension is
    154                // start + (shape-1)*step
    155                final int rank = shape == null ? 0 : shape.length;
    156
    157                if (start == null) {
    158                        lstart = new int[rank];
    159                } else {
    160                        lstart = start.clone();
    161                }
    162                if (stop == null) {
    163                        lstop = new int[rank];
    164                } else {
    165                        lstop = stop.clone();
    166                }
    167                if (step == null) {
    168                        lstep = new int[rank];
    169                        Arrays.fill(lstep, 1);
    170                } else {
    171                        lstep = step.clone();
    172                }
    173
    174                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
    175                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
    176                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
    177                }
    178
    179                lshape = new int[rank];
    180                oshape = copy(shape);
    181                if (maxShape == null) {
    182                        mshape = oshape;
    183                } else {
    184                        initMaxShape(maxShape);
    185                }
    186
    187                for (int i = 0; i < rank; i++) {
    188                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
    189                }
    190        }
    191
    192        /**
    193         * Set slice for given dimension, if the start is {@code null} it will be
    194         * set to 0, stop is by default equal to the entire size of the set.
    195         *
    196         * @param i
    197         *            dimension
    198         * @param start
    199         *            Start point, may be {@code null} to imply start of dimension
    200         * @param stop
    201         *            Stop point, may be {@code null} to imply end of dimension
    202         * @param step
    203         *            Slice step
    204         */
    205        public void setSlice(int i, Integer start, Integer stop, int step) {
    206                internalSetSlice(i, start, stop, step);
    207        }
    208
    209        /**
    210         * Set slice for given dimension, if the start is {@code null} it will be
    211         * set to 0, stop is by default equal to the entire size of the set.
    212         *
    213         * @param i
    214         *            dimension
    215         * @param start
    216         *            Start point, may be {@code null} to imply start of dimension
    217         * @param stop
    218         *            Stop point, may be {@code null} to imply end of dimension
    219         * @param step
    220         *            Slice step
    221         */
    222        public void setSlice(int i, int start, int stop, int step) {
    223                internalSetSlice(i, start, stop, step);
    224        }
    225
    226        /**
    227         * Set slice for given dimension.
    228         *
    229         * @param i
    230         *            Dimension
    231         * @param slice
    232         *            Slice with wanted properties to set
    233         * @since 2.0
    234         */
    235        public void setSlice(int i, Slice slice) {
    236                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
    237        }
    238
    239        /**
    240         * Set slice for given dimension, if the start is {@code null} it will be
    241         * set to 0, stop is by default equal to the entire size of the set.
    242         *
    243         * @param i
    244         *            dimension
    245         * @param start
    246         *            Start point, may be {@code null} to imply start of dimension
    247         * @param stop
    248         *            Stop point, may be {@code null} to imply end of dimension
    249         * @param step
    250         *            Slice step
    251         */
    252        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
    253                if (oshape == null) {
    254                        throw new IllegalArgumentException("Cannot set slice on null dataset");
    255                }
    256                if (step == 0) {
    257                        throw new IllegalArgumentException("Step size must not be zero");
    258                }
    259                i = ShapeUtils.checkAxis(oshape.length, i);
    260                final int s = oshape[i];
    261                final int m = mshape[i];
    262
    263                if (start == null) {
    264                        start = step > 0 ? 0 : s - 1;
    265                } else if (start < 0) {
    266                        start += s;
    267                }
    268                if (step > 0) {
    269                        if (start < 0) {
    270                                start = 0;
    271                        } else if (start > s) {
    272                                if (m == s) {
    273                                        start = s;
    274                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
    275                                        start = m;
    276                                }
    277                        }
    278
    279                        if (stop == null) {
    280                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
    281                                        throw new IllegalArgumentException(
    282                                                        "To extend past current dimension in unlimited case, a stop value must be specified");
    283                                }
    284                                stop = s;
    285                        } else if (stop < 0) {
    286                                stop += s;
    287                        }
    288                        if (stop < 0) {
    289                                stop = 0;
    290                        } else if (stop > s) {
    291                                if (m == s) {
    292                                        stop = s;
    293                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    294                                        stop = m;
    295                                }
    296                        }
    297
    298                        if (start >= stop) {
    299                                if (start < s || m == s) {
    300                                        lstop[i] = start;
    301                                } else { // override end
    302                                        stop = start + step;
    303                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    304                                                stop = m;
    305                                        }
    306                                        lstop[i] = stop;
    307                                }
    308                        } else {
    309                                lstop[i] = stop;
    310                        }
    311
    312                        if (lstop[i] > s) {
    313                                oshape[i] = lstop[i];
    314                                expanded = true;
    315                        }
    316                } else {
    317                        if (start < 0) {
    318                                start = -1;
    319                        } else if (start >= s) {
    320                                start = s - 1;
    321                        }
    322
    323                        if (stop == null) {
    324                                stop = -1;
    325                        } else if (stop < 0) {
    326                                stop += s;
    327                        }
    328                        if (stop < -1) {
    329                                stop = -1;
    330                        } else if (stop >= s) {
    331                                stop = s - 1;
    332                        }
    333                        if (stop >= start) {
    334                                lstop[i] = start;
    335                        } else {
    336                                lstop[i] = stop;
    337                        }
    338                }
    339
    340                stop = lstop[i];
    341                lshape[i] = calcLength(start, stop, step);
    342                lstart[i] = start;
    343                lstep[i] = step;
    344        }
    345
    346        private static int calcLength(int start, int stop, int step) {
    347                int l;
    348                if (start == stop) {
    349                        l = 0;
    350                } else if (step > 0) {
    351                        l = Math.max(0, (stop - start - 1) / step + 1);
    352                } else {
    353                        l = Math.max(0, (stop - start + 1) / step + 1);
    354                }
    355                return l;
    356        }
    357
    358        /**
    359         * Return an array of shapes of the source dataset (this can change for
    360         * dynamic datasets).
    361         *
    362         * @return shape of source Dataset
    363         */
    364        public int[] getSourceShape() {
    365                return oshape;
    366        }
    367
    368        /**
    369         * Return an array of maximum shape
    370         *
    371         * @return maximum shape
    372         */
    373        public int[] getMaxShape() {
    374                return mshape;
    375        }
    376
    377        /**
    378         * Return {@code true} if the slice makes shape larger, else {@code false}.
    379         *
    380         * @return {@code true} if slice makes shape larger, {@code false} in the
    381         *         other case
    382         */
    383        public boolean isExpanded() {
    384                return expanded;
    385        }
    386
    387        /**
    388         * Return an array of resulting shapes (this can change if the start, stop,
    389         * step arrays are changed).
    390         *
    391         * @return resulting shape
    392         */
    393        public int[] getShape() {
    394                return lshape;
    395        }
    396
    397        /**
    398         * Return an array of the starts values.
    399         *
    400         * @return start values
    401         */
    402        public int[] getStart() {
    403                return lstart;
    404        }
    405
    406        /**
    407         * Return an array of stops values.
    408         * <p>
    409         * Note : stop values are clamped to -1 for <b>negative</b> steps
    410         * </p>
    411         *
    412         * @return stop values
    413         */
    414        public int[] getStop() {
    415                return lstop;
    416        }
    417
    418        /**
    419         * Return an array of the steps values.
    420         *
    421         * @return step values
    422         */
    423        public int[] getStep() {
    424                return lstep;
    425        }
    426
    427        /**
    428         * Update source shape (for use with dynamic datasets)
    429         * @param shape source shape
    430         * @since 2.3
    431         */
    432        public void updateSourceShape(int... shape) {
    433                if (shape != null) {
    434                        if (oshape != null) {
    435                                if (shape.length != oshape.length) {
    436                                        throw new IllegalArgumentException("Updated source shape must have same rank");
    437                                }
    438                                for (int i = 0; i < shape.length; i++) {
    439                                        int s = shape[i];
    440                                        if ((s > 1 || s < lshape[i]) && !isSliceWithinShape(s, lstart[i], lstop[i], lstep[i])) {
    441                                                throw new IllegalArgumentException("Updated source shape must be outside latest slice");
    442                                        }
    443                                }
    444                                System.arraycopy(shape, 0, oshape, 0, shape.length);
    445                        } else {
    446                                throw new IllegalArgumentException("Cannot update null source shape with non-null");
    447                        }
    448                } else if (oshape != null) {
    449                        throw new IllegalArgumentException("Cannot update non-null source shape with null");
    450                }
    451        }
    452
    453        /**
    454         * Check slice configure
    455         * @param l length of dimension
    456         * @param b beginning
    457         * @param e end
    458         * @param d delta
    459         * @return true if okay
    460         */
    461        static boolean isSliceWithinShape(int l, int b, int e, int d) {
    462                if (l == 0) {
    463                        return b == 0 && e == 0;
    464                }
    465                if (d > 0) {
    466                        return b >= 0 && b <= e && b < l && e <= l;
    467                }
    468                return b >= 0 && b >= e && b < l && e >= -1;
    469        }
    470
    471        /**
    472         * Return {@code true} if all of originals shapes are covered by positive
    473         * steps slices, else {@code false}.
    474         *
    475         * @return {@code true} if all of originals shapes is covered by this slice
    476         *         with positive steps, {@code false} in the other case.
    477         */
    478        public boolean isAll() {
    479                if (expanded) {
    480                        return false;
    481                }
    482
    483                if (oshape == null) {
    484                        return true;
    485                }
    486
    487                boolean allData = Arrays.equals(oshape, lshape) && ShapeUtils.calcSize(oshape) > 0;
    488                if (allData) {
    489                        for (int i = 0; i < lshape.length; i++) {
    490                                if (lstep[i] < 0) {
    491                                        allData = false;
    492                                        break;
    493                                }
    494                        }
    495                }
    496                return allData;
    497        }
    498
    499        /**
    500         * Flip the slice direction in given dimension, this means that slice
    501         * begins at previous end point, steps in the opposite direction, and
    502         * finishes at the previous start point.
    503         *
    504         * @param i
    505         *            dimension to flip
    506         * @return this
    507         */
    508        public SliceND flip(int i) {
    509                if (lshape == null) {
    510                        return this;
    511                }
    512
    513                i = ShapeUtils.checkAxis(lshape.length, i);
    514
    515                int beg = lstart[i];
    516                int end = lstop[i];
    517                int step = lstep[i];
    518                int del = lstep[i] > 0 ? 1 : -1;
    519
    520                int num = (end - beg - del) / step + 1; // number of steps
    521                lstart[i] = beg + (num - 1) * step;
    522                lstop[i] = Math.max(beg - step, -1);
    523                lstep[i] = -step;
    524
    525                return this;
    526        }
    527
    528        /**
    529         * Flip slices directions in all dimensions, this means that all slices are
    530         * beginning at previous end point, steps are in the opposite direction, and
    531         * finishes are at the previous start point.
    532         * @return this
    533         */
    534        public SliceND flip() {
    535                if (lshape == null) {
    536                        return this;
    537                }
    538                int orank = lshape.length;
    539                for (int i = 0; i < orank; i++) {
    540                        flip(i);
    541                }
    542
    543                return this;
    544        }
    545
    546        /**
    547         * Convert to a slice array all the Slices of the SliceND
    548         *
    549         * @return a Slice array
    550         */
    551        public Slice[] convertToSlice() {
    552                if (lshape == null) {
    553                        return null;
    554                }
    555
    556                int orank = lshape.length;
    557
    558                Slice[] slice = new Slice[orank];
    559
    560                for (int j = 0; j < orank; j++) {
    561                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
    562                }
    563
    564                return slice;
    565        }
    566
    567        /**
    568         * Create a deep copy of the SliceND
    569         *
    570         * @return New SliceND with the current SliceND properties
    571         */
    572        @Override
    573        public SliceND clone() {
    574                SliceND c = new SliceND(oshape);
    575                if (lshape != null) {
    576                        for (int i = 0; i < lshape.length; i++) {
    577                                c.lstart[i] = lstart[i];
    578                                c.lstop[i] = lstop[i];
    579                                c.lstep[i] = lstep[i];
    580                                c.lshape[i] = lshape[i];
    581                        }
    582                }
    583                c.expanded = expanded;
    584                return c;
    585        }
    586
    587        /**
    588         * Returns a string construction of the sliceND with the python form.
    589         *
    590         * @return Constructed String of all Slices
    591         */
    592        @Override
    593        public String toString() {
    594                final int rank = lshape == null ? 0 : lshape.length;
    595                if (rank == 0) {
    596                        return "";
    597                }
    598                StringBuilder s = new StringBuilder();
    599                for (int i = 0; i < rank; i++) {
    600                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
    601                        s.append(',');
    602                }
    603
    604                return s.substring(0, s.length() - 1);
    605        }
    606
    607        /**
    608         * Create SliceND from dataset
    609         *
    610         * @param data
    611         *            ILazyDataset to treat
    612         * @param start
    613         *            Array of starts indexes
    614         * @param stop
    615         *            Array of stops indexes
    616         * @return Constructed SliceND
    617         */
    618        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
    619                return createSlice(data, start, stop, null);
    620        }
    621
    622        /**
    623         * Create SliceND from dataset
    624         *
    625         * @param data
    626         *            ILazyDataset to treat
    627         * @param start
    628         *            Array of starts indexes
    629         * @param stop
    630         *            Array of stops indexes
    631         * @param step
    632         *            Array of steps
    633         * @return Constructed SliceND
    634         */
    635        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
    636                if (data instanceof IDynamicDataset) {
    637                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
    638                }
    639                return new SliceND(data.getShape(), start, stop, step);
    640        }
    641
    642        /**
    643         * Create SliceND without sanity checks on start, stop and step
    644         * @param shape
    645         *            Shape of the dataset, see {@link ILazyDataset#getShape()}
    646         * @param maxShape
    647         *            Array of maximals shapes, may be {@code null}
    648         * @param start
    649         *            Array of starts points, may be {@code null}
    650         * @param stop
    651         *            Array of stops points, may be {@code null}
    652         * @param step
    653         *            Array of steps, may be {@code null}
    654         * @return slice
    655         */
    656        static SliceND createSlice(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
    657                if (shape == null) {
    658                        throw new IllegalArgumentException("Shape must not be null");
    659                }
    660                int rank = shape.length;
    661
    662                if (maxShape != null && maxShape.length != rank) {
    663                        throw new IllegalArgumentException("Max shape must have same rank as shape");
    664                }
    665                if (start.length != rank || stop.length != rank || step.length != rank) {
    666                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
    667                                        + start.length + ", stop=" + stop.length + ", step=" + step.length + ", and it needs " + rank);
    668                }
    669
    670                SliceND s = new SliceND(shape);
    671                if (maxShape != null) {
    672                        s.initMaxShape(maxShape);
    673                }
    674                s.lstart = start;
    675                s.lstop  = stop;
    676                s.lstep  = step;
    677
    678                for (int i = 0; i < rank; i++) {
    679                        int e = stop[i];
    680                        s.lshape[i] = calcLength(start[i], e, step[i]);
    681                        if (e > shape[i]) {
    682                                s.oshape[i] = e;
    683                                s.expanded = true;
    684                        }
    685                }
    686                return s;
    687        }
    688
    689        /**
    690         * Check slice is compatible with given shapes
    691         * @param dShape dataset shape
    692         * @param dMShape dataset maximum shape (can be null)
    693         * @since 2.3
    694         */
    695        public void checkShapes(int[] dShape, int[] dMShape) {
    696                if (oshape == null) {
    697                        if (dShape == null) {
    698                                return;
    699                        }
    700                        throw new IllegalArgumentException("Dataset shape must be null with source shape null");
    701                }
    702                int r = oshape.length;
    703                if (r != dShape.length) {
    704                        throw new IllegalArgumentException("Dataset shape must be equal in length to source shape");
    705                }
    706                if (Arrays.equals(oshape, dShape)) {
    707                        return;
    708                }
    709
    710                if (dMShape == null) {
    711                        for (int i = 0; i < r; i++) {
    712                                int o = oshape[i];
    713                                if (o == 0) {
    714                                        throw new IllegalArgumentException("Slice shape has zero length");
    715                                }
    716                                if (o > dShape[i]) {
    717                                        throw new IllegalArgumentException("Slice shape is greater than dataset shape");
    718                                }
    719                        }
    720                } else {
    721                        for (int i = 0; i < r; i++) {
    722                                int o = oshape[i];
    723                                if (o == 0) {
    724                                        throw new IllegalArgumentException("Slice shape has zero length");
    725                                }
    726                                int m = dMShape[i];
    727                                if (m != ILazyWriteableDataset.UNLIMITED) {
    728                                        if (o > m) {
    729                                                throw new IllegalArgumentException("Slice shape is greater than max dataset shape");
    730                                        }
    731                                }
    732                        }
    733                }
    734        }
    735}