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.isZeroSize(oshape); 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}