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}