    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 * Class to represent a slice through all dimensions of a multi-dimensional dataset. A slice
    016 * comprises a starting position array, a stopping position array (not included) and a stepping size array.
    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 ND slice for whole of shape
    033         * @param shape
    034         */
    035        public SliceND(final int[] shape) {
    036                final int rank = shape.length;
    037                lstart = new int[rank];
    038                lstop  = shape.clone();
    039                lstep  = new int[rank];
    040                Arrays.fill(lstep, 1);
    041                lshape = shape.clone();
    042                oshape = shape.clone();
    043                mshape = oshape;
    044                expanded = false;
    045        }
    046
    047        /**
    048         * Construct ND slice from an array of 1D slices
    049         * @param shape
    050         * @param slice
    051         */
    052        public SliceND(final int[] shape, Slice... slice) {
    053                this(shape, null, slice);
    054        }
    055
    056        /**
    057         * Construct ND slice from an array of 1D slices
    058         * @param shape
    059         * @param maxShape can be null
    060         * @param slice
    061         */
    062        public SliceND(final int[] shape, final int[] maxShape, Slice... slice) {
    063                this(shape);
    064
    065                if (maxShape != null) {
    066                        initMaxShape(maxShape);
    067                }
    068
    069                if (slice != null) {
    070                        final int length = slice.length;
    071                        final int rank = shape.length;
    072                        if (length > rank) {
    073                                throw new IllegalArgumentException("More slices have been specified than rank of shape");
    074                        }
    075                        for (int i = 0; i < length; i++) {
    076                                Slice s = slice[i];
    077                                if (s != null) {
    078                                        setSlice(i, s);
    079                                }
    080                        }
    081                }
    082        }
    083
    084        private void initMaxShape(int[] maxShape) {
    085                final int rank = oshape.length;
    086                if (maxShape.length != rank) {
    087                        throw new IllegalArgumentException("Maximum shape must have same rank as shape");
    088                }
    089                mshape = maxShape.clone();
    090                for (int i = 0; i < rank; i++) {
    091                        int m = mshape[i];
    092                        if (m != ILazyWriteableDataset.UNLIMITED && m < oshape[i]) {
    093                                throw new IllegalArgumentException("Maximum shape must be greater than or equal to shape");
    094                        }
    095                }
    096        }
    097
    098        /**
    099         * Construct ND slice parameters
    100         *
    101         * @param shape
    102         * @param start
    103         *            can be null
    104         * @param stop
    105         *            can be null
    106         * @param step
    107         *            can be null
    108         */
    109        public SliceND(final int[] shape, final int[] start, final int[] stop, final int[] step) {
    110                this(shape, null, start, stop, step);
    111        }
    112
    113        /**
    114         * Construct ND slice parameters
    115         *
    116         * @param shape
    117         * @param maxShape can be null
    118         * @param start
    119         *            can be null
    120         * @param stop
    121         *            can be null
    122         * @param step
    123         *            can be null
    124         */
    125        public SliceND(final int[] shape, final int[] maxShape, final int[] start, final int[] stop, final int[] step) {
    126                // number of steps, or new shape, taken in each dimension is
    127                // shape = (stop - start + step - 1) / step if step > 0
    128                // (stop - start + step + 1) / step if step < 0
    129                //
    130                // thus the final index in each dimension is
    131                // start + (shape-1)*step
    132
    133                int rank = shape.length;
    134
    135                if (start == null) {
    136                        lstart = new int[rank];
    137                } else {
    138                        lstart = start.clone();
    139                }
    140                if (stop == null) {
    141                        lstop = new int[rank];
    142                } else {
    143                        lstop = stop.clone();
    144                }
    145                if (step == null) {
    146                        lstep = new int[rank];
    147                        Arrays.fill(lstep, 1);
    148                } else {
    149                        lstep = step.clone();
    150                }
    151
    152                if (lstart.length != rank || lstop.length != rank || lstep.length != rank) {
    153                        throw new IllegalArgumentException("No of indexes does not match data dimensions: you passed it start="
    154                                        + lstart.length + ", stop=" + lstop.length + ", step=" + lstep.length + ", and it needs " + rank);
    155                }
    156
    157                lshape = new int[rank];
    158                oshape = shape.clone();
    159                if (maxShape == null) {
    160                        mshape = oshape;
    161                } else {
    162                        initMaxShape(maxShape);
    163                }
    164
    165                for (int i = 0; i < rank; i++) {
    166                        internalSetSlice(i, start == null ? null : lstart[i], stop == null ? null : lstop[i], lstep[i]);
    167                }
    168        }
    169
    170        /**
    171         * Set slice for given dimension
    172         * @param i dimension
    173         * @param start can be null to imply start of dimension
    174         * @param stop can be null to imply end of dimension
    175         * @param step
    176         */
    177        public void setSlice(int i, Integer start, Integer stop, int step) {
    178                internalSetSlice(i, start, stop, step);
    179        }
    180
    181        /**
    182         * Set slice for given dimension
    183         * @param i dimension
    184         * @param start
    185         * @param stop
    186         * @param step
    187         */
    188        public void setSlice(int i, int start, int stop, int step) {
    189                internalSetSlice(i, start, stop, step);
    190        }
    191
    192        /**
    193         * Set slice for given dimension
    194         * @param i dimension
    195         * @param slice
    196         * @since 2.0
    197         */
    198        public void setSlice(int i, Slice slice) {
    199                internalSetSlice(i, slice.getStart(), slice.getStop(), slice.getStep());
    200        }
    201
    202        /**
    203         * Set slice for given dimension
    204         * @param i dimension
    205         * @param start
    206         * @param stop
    207         * @param step
    208         */
    209        private void internalSetSlice(int i, Integer start, Integer stop, int step) {
    210                if (step == 0) {
    211                        throw new IllegalArgumentException("Step size must not be zero");
    212                }
    213                final int s = oshape[i];
    214                final int m = mshape[i];
    215
    216                if (start == null) {
    217                        start = step > 0 ? 0 : s - 1;
    218                } else if (start < 0) {
    219                        start += s;
    220                }
    221                if (step > 0) {
    222                        if (start < 0) {
    223                                start = 0;
    224                        } else if (start > s) {
    225                                if (m == s) {
    226                                        start = s;
    227                                } else if (m != ILazyWriteableDataset.UNLIMITED && start > m) {
    228                                        start = m;
    229                                }
    230                        }
    231
    232                        if (stop == null) {
    233                                if (start >= s && m == ILazyWriteableDataset.UNLIMITED) {
    234                                        throw new IllegalArgumentException("To extend past current dimension in unlimited case, a stop value must be specified");
    235                                }
    236                                stop = s;
    237                        } else if (stop < 0) {
    238                                stop += s;
    239                        }
    240                        if (stop < 0) {
    241                                stop = 0;
    242                        } else if (stop > s) {
    243                                if (m == s) {
    244                                        stop = s;
    245                                } else if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    246                                        stop = m;
    247                                }
    248                        }
    249
    250                        if (start >= stop) {
    251                                if (start < s || m == s) {
    252                                        lstop[i] = start;
    253                                } else { // override end
    254                                        stop = start + step;
    255                                        if (m != ILazyWriteableDataset.UNLIMITED && stop > m) {
    256                                                stop = m;
    257                                        }
    258                                        lstop[i] = stop;
    259                                }
    260                        } else {
    261                                lstop[i] = stop;
    262                        }
    263
    264                        if (lstop[i] > s) {
    265                                oshape[i] = lstop[i];
    266                                expanded = true;
    267                        }
    268                } else {
    269                        if (start < 0) {
    270                                start = -1;
    271                        } else if (start >= s) {
    272                                start = s - 1;
    273                        }
    274
    275                        if (stop == null) {
    276                                stop = -1;
    277                        } else if (stop < 0) {
    278                                stop += s;
    279                        }
    280                        if (stop < -1) {
    281                                stop = -1;
    282                        } else if (stop >= s) {
    283                                stop = s - 1;
    284                        }
    285                        if (stop >= start) {
    286                                lstop[i] = start;
    287                        } else {
    288                                lstop[i] = stop;
    289                        }
    290                }
    291
    292                stop = lstop[i];
    293                if (start == stop) {
    294                        lshape[i] = 0;
    295                } else if (step > 0) {
    296                        lshape[i] = Math.max(0, (stop - start - 1) / step + 1);
    297                } else {
    298                        lshape[i] = Math.max(0, (stop - start + 1) / step + 1);
    299                }
    300                lstart[i] = start;
    301                lstep[i] = step;
    302        }
    303
    304        /**
    305         * @return shape of source dataset (this can change for dynamic datasets)
    306         */
    307        public int[] getSourceShape() {
    308                return oshape;
    309        }
    310
    311        /**
    312         * @return maximum shape
    313         */
    314        public int[] getMaxShape() {
    315                return mshape;
    316        }
    317
    318        /**
    319         * @return true if slice makes shape larger
    320         */
    321        public boolean isExpanded() {
    322                return expanded;
    323        }
    324
    325        /**
    326         * @return resulting shape (this can change if the start, stop, step arrays are changed)
    327         */
    328        public int[] getShape() {
    329                return lshape;
    330        }
    331
    332        /**
    333         * @return start values
    334         */
    335        public int[] getStart() {
    336                return lstart;
    337        }
    338
    339        /**
    340         * Note stop values are clamped to -1 for <b>negative</b> steps
    341         * @return stop values
    342         */
    343        public int[] getStop() {
    344                return lstop;
    345        }
    346
    347        /**
    348         * @return step values
    349         */
    350        public int[] getStep() {
    351                return lstep;
    352        }
    353
    354        /**
    355         * @return true if all of original shape is covered by this slice with positive steps
    356         */
    357        public boolean isAll() {
    358                if (expanded) {
    359                        return false;
    360                }
    361
    362                boolean allData = Arrays.equals(oshape, getShape());
    363                if (allData) {
    364                        for (int i = 0; i < lshape.length; i++) {
    365                                if (lstep[i] < 0) {
    366                                        allData = false;
    367                                        break;
    368                                }
    369                        }
    370                }
    371                return allData;
    372        }
    373
    374        /**
    375         * Flip slice direction in given dimension so slice begins at previous end point,
    376         * steps in the opposite direction, and finishes at the previous start point
    377         * @param i dimension to flip
    378         */
    379        public SliceND flip(int i) {
    380                if (i < 0 || i >= lshape.length) {
    381                        throw new IllegalArgumentException("Given dimension is less than zero or greater than last dimension");
    382                }
    383
    384                int beg = lstart[i];
    385                int end = lstop[i];
    386                int step = lstep[i];
    387                int del = lstep[i] > 0 ? 1 : -1;
    388
    389                int num = (end - beg - del) / step + 1; // number of steps
    390                lstart[i] = beg + (num - 1) * step;
    391                lstop[i] = Math.max(beg - step, -1);
    392                lstep[i] = -step;
    393
    394                return this;
    395        }
    396
    397        /**
    398         * Flip slice direction in all dimensions so slice begins at previous end point,
    399         * steps in the opposite direction, and finishes at the previous start point
    400         */
    401        public SliceND flip() {
    402                int orank = lshape.length;
    403                for (int i = 0; i < orank; i++) {
    404                        flip(i);
    405                }
    406
    407                return this;
    408        }
    409
    410        /**
    411         * Convert to a slice array
    412         * @return a slice array
    413         */
    414        public Slice[] convertToSlice() {
    415                int orank = lshape.length;
    416
    417                Slice[] slice = new Slice[orank];
    418
    419                for (int j = 0; j < orank; j++) {
    420                        slice[j] = new Slice(lstart[j], lstop[j], lstep[j]);
    421                }
    422
    423                return slice;
    424        }
    425
    426        @Override
    427        public SliceND clone() {
    428                SliceND c = new SliceND(oshape);
    429                for (int i = 0; i < lshape.length; i++) {
    430                        c.lstart[i] = lstart[i];
    431                        c.lstop[i] = lstop[i];
    432                        c.lstep[i] = lstep[i];
    433                        c.lshape[i] = lshape[i];
    434                }
    435                c.expanded = expanded;
    436                return c;
    437        }
    438
    439        @Override
    440        public String toString() {
    441                final int rank = lshape.length;
    442                if (rank == 0) {
    443                        return "";
    444                }
    445                StringBuilder s = new StringBuilder();
    446                for (int i = 0; i < rank; i++) {
    447                        Slice.appendSliceToString(s, oshape[i], lstart[i], lstop[i], lstep[i]);
    448                        s.append(',');
    449                }
    450
    451                return s.substring(0, s.length()-1);
    452        }
    453
    454        /**
    455         * Creating slice from dataset
    456         * @param data
    457         * @param start
    458         * @param stop
    459         * @return slice
    460         */
    461        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop) {
    462                return createSlice(data, start, stop, null);
    463        }
    464
    465        /**
    466         * Creating slice from dataset
    467         * @param data
    468         * @param start
    469         * @param stop
    470         * @param step
    471         * @return slice
    472         */
    473        public static SliceND createSlice(ILazyDataset data, int[] start, int[] stop, int[] step) {
    474                if (data instanceof IDynamicDataset) {
    475                        return new SliceND(data.getShape(), ((IDynamicDataset) data).getMaxShape(), start, stop, step);
    476                }
    477                return new SliceND(data.getShape(), start, stop, step);
    478        }
    479}