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}