001/*- 002 ******************************************************************************* 003 * Copyright (c) 2011, 2016 Diamond Light Source Ltd. 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 * Contributors: 010 * Peter Chang - initial API and implementation and/or initial documentation 011 *******************************************************************************/ 012 013package org.eclipse.january.dataset; 014 015import java.io.Serializable; 016 017/** 018 * Class to represent a slice through a single dimension of a multi-dimensional dataset. A slice 019 * comprises a starting position, a stopping position (not included) and a stepping size. 020 */ 021public class Slice implements Cloneable, Serializable { 022 023 /** 024 * 025 */ 026 private static final long serialVersionUID = 3714928852236201310L; 027 private Integer start; 028 private Integer stop; 029 private int step; 030 031 private int length; // max length of dimension 032 033 public Slice() { 034 this(null, null, 1); 035 } 036 037 /** 038 * Default to starting at 0 with steps of 1 039 * @param stop if null, then default to whatever shape is when converted 040 */ 041 public Slice(final Integer stop) { 042 this(null, stop, 1); 043 } 044 045 /** 046 * Default to steps of 1 047 * @param start if null, then default to 0 048 * @param stop if null, then default to whatever shape is when converted 049 */ 050 public Slice(final Integer start, final Integer stop) { 051 this(start, stop, 1); 052 } 053 054 /** 055 * 056 * @param start if null, then default to bound 057 * @param stop if null, then default to bound 058 * @param step if null, then default to 1 059 */ 060 public Slice(final Integer start, final Integer stop, final Integer step) { 061 this.start = start; 062 this.stop = stop; 063 this.step = step == null ? 1 : step; 064 length = -1; 065 } 066 067 /** 068 * Copy another slice 069 * @param other 070 */ 071 private Slice(final Slice other) { 072 start = other.start; 073 stop = other.stop; 074 step = other.step; 075 length = other.length; 076 } 077 078 @Override 079 public Slice clone() { 080 return new Slice(this); 081 } 082 083 /** 084 * Set maximum value of slice 085 * @param length 086 * @return this slice 087 */ 088 public Slice setLength(int length) { 089 if (stop != null && step > 0 && length < stop) { 090 throw new IllegalArgumentException("Length must be greater than or equal to stop"); 091 } 092 if (start != null && step < 0 && length < start) { 093 throw new IllegalArgumentException("Length must be greater than or equal to start"); 094 } 095 this.length = length; 096 return this; 097 } 098 099 /** 100 * @return true if slice represents complete dimension 101 */ 102 public boolean isSliceComplete() { 103 if (start == null && stop == null && (step == 1 || step == -1)) 104 return true; 105 if (length > 0) { 106 return getNumSteps() == length; 107 } 108 109 return true; 110 } 111 112 /** 113 * @return maximum value of slice 114 */ 115 public int getLength() { 116 return length; 117 } 118 119 /** 120 * @return starting position of slice 121 */ 122 public Integer getStart() { 123 return start; 124 } 125 126 /** 127 * @return stopping position of slice 128 */ 129 public Integer getStop() { 130 return stop; 131 } 132 133 /** 134 * @return step size of slice 135 */ 136 public int getStep() { 137 return step; 138 } 139 140 /** 141 * Set starting position of slice 142 * @param start (can be null) 143 */ 144 public void setStart(Integer start) { 145 if (start != null && length > 0) { 146 if (step > 0) { 147 int end = stop == null ? length : stop; 148 if (start >= end) { 149 throw new IllegalArgumentException("Non-null start must be less than end"); 150 } 151 } else { 152 int end = stop == null ? -1 : stop; 153 if (start < end) { 154 throw new IllegalArgumentException("Non-null start must be greater than end for negative step"); 155 } 156 } 157 } 158 this.start = start; 159 } 160 161 /** 162 * Set stopping position of slice 163 * @param stop (can be null) 164 */ 165 public void setStop(Integer stop) { 166 if (stop != null && length > 0) { 167 if (step > 0) { 168 int beg = start == null ? 0 : start; 169 if (stop < beg) { 170 throw new IllegalArgumentException("Non-null stop must be greater than or equal to beginning"); 171 } 172 } else { 173 int beg = start == null ? length - 1 : start; 174 if (stop >= beg) { 175 throw new IllegalArgumentException("Non-null stop must be less than beginning for negative step"); 176 } 177 } 178 if (stop > length) 179 stop = length; 180 } 181 this.stop = stop; 182 } 183 184 /** 185 * Set start and end from implied number of steps. I.e. shift start to position given by 186 * parameter whilst keeping size of slice fixed 187 * @param beg 188 * @return true if end reached 189 */ 190 public boolean setPosition(int beg) { 191 boolean end = false; 192 int len = getNumSteps(); 193 int max = getNumSteps(beg, length, step); 194 if (len > max) { 195 len = max; 196 end = true; 197 } 198 start = beg; 199 stop = start + (len-1) * step + 1; 200 return end; 201 } 202 203 /** 204 * Get position of n-th step in slice 205 * @param n 206 * @return position 207 */ 208 public int getPosition(int n) { 209 if (n < 0) 210 throw new IllegalArgumentException("Given n-th step should be non-negative"); 211 if (n >= getNumSteps()) 212 throw new IllegalArgumentException("N-th step exceeds extent of slice"); 213 int beg; 214 if (start == null) { 215 if (step < 0) { 216 if (length < 0) { 217 if (stop == null) { 218 throw new IllegalStateException("Length or stop should be set"); 219 } 220 beg = stop - 1; 221 } else { 222 beg = length - 1; 223 } 224 } else { 225 beg = 0; 226 } 227 } else { 228 beg = start; 229 } 230 return beg + step*n; 231 } 232 233 /** 234 * Set step size of slice 235 * @param step 236 */ 237 public void setStep(int step) { 238 if (step == 0) { 239 throw new IllegalArgumentException("Step must not be zero"); 240 } 241 this.step = step; 242 } 243 244 /** 245 * Append string representation of slice 246 * @param s 247 * @param len 248 * @param beg 249 * @param end 250 * @param del 251 */ 252 public static void appendSliceToString(final StringBuilder s, final int len, final int beg, final int end, final int del) { 253 int o = s.length(); 254 if (del > 0) { 255 if (beg != 0) 256 s.append(beg); 257 } else { 258 if (beg != len-1) 259 s.append(beg); 260 } 261 262 int n = getNumSteps(beg, end, del); 263 if (n == 1) { 264 if (s.length() == o) { 265 s.append(beg); 266 } 267 return; 268 } 269 270 s.append(':'); 271 272 if (del > 0) { 273 if (end != len) 274 s.append(end); 275 } else { 276 if (end != -1) 277 s.append(end); 278 } 279 280 if (del != 1) { 281 s.append(':'); 282 s.append(del); 283 } 284 } 285 286 @Override 287 public String toString() { 288 StringBuilder s = new StringBuilder(); 289 appendSliceToString(s, length, start != null ? start : (step > 0 ? 0 : length - 1), stop != null ? stop : (step > 0 ? length : -1), step); 290 return s.toString(); 291 } 292 293 /** 294 * @return number of steps in slice 295 */ 296 public int getNumSteps() { 297 if (length < 0) { 298 if (stop == null) 299 throw new IllegalStateException("Slice is underspecified - stop is null and length is negative"); 300 int beg = start == null ? (step > 0 ? 0: stop-1) : start; 301 if (step > 0 && stop <= beg) 302 return 0; 303 if (step < 0 && stop > beg) 304 return 0; 305 return getNumSteps(beg, stop, step); 306 } 307 int beg = start == null ? (step > 0 ? 0: length-1) : start; 308 int end = stop == null ? (step > 0 ? length : -1) : stop; 309 return getNumSteps(beg, end, step); 310 } 311 312 /** 313 * @param beg 314 * @param end (exclusive) 315 * @return number of steps between limits 316 */ 317 public int getNumSteps(int beg, int end) { 318 return getNumSteps(beg, end, step); 319 } 320 321 private static int getNumSteps(int beg, int end, int step) { 322 int del = step > 0 ? 1 : -1; 323 return Math.max(0, (end - beg - del) / step + 1); 324 } 325 326 /** 327 * @return last value in slice (< stop if step > 0, > stop if step < 0) 328 */ 329 public int getEnd() { 330 int n = getNumSteps() - 1; 331 if (n < 0) 332 throw new IllegalStateException("End is not defined"); 333 334 return getPosition(n); 335 } 336 337 /** 338 * Flip slice direction so slice begins at previous end point, steps 339 * in the opposite direction, and finishes at the previous start point . 340 * <p> 341 * Note the stop value may not be preserved across two flips 342 */ 343 public Slice flip() { 344 if (length < 0) { 345 Integer tmp = start == null ? null : start - step; 346 start = stop == null ? null : getEnd(); 347 stop = tmp; 348 } else { 349 Integer tstart = start; 350 start = stop == null ? null : getEnd(); 351 stop = tstart == null ? null : tstart - step; 352 } 353 step = -step; 354 355 return this; 356 } 357 358 /** 359 * Populate given start, stop, step arrays from given slice array 360 * @param shape 361 * @param start 362 * @param stop 363 * @param step 364 * @param slice 365 */ 366 public static void convertFromSlice(final Slice[] slice, final int[] shape, final int[] start, final int[] stop, final int[] step) { 367 final int rank = shape.length; 368 final int length = slice == null ? 0 : slice.length; 369 370 int i = 0; 371 for (; i < length; i++) { 372 if (length > rank) 373 break; 374 375 Slice s = slice[i]; 376 if (s == null) { 377 start[i] = 0; 378 stop[i] = shape[i]; 379 step[i] = 1; 380 continue; 381 } 382 int n; 383 if (s.start == null) { 384 start[i] = s.step > 0 ? 0 : shape[i] - 1; 385 } else { 386 n = s.start; 387 if (n < 0) 388 n += shape[i]; 389 if (n < 0 || n >= shape[i]) { 390 throw new IllegalArgumentException(String.format("Start is out of bounds: %d is not in [%d,%d)", 391 n, s.start, shape[i])); 392 } 393 start[i] = n; 394 } 395 396 if (s.stop == null) { 397 stop[i] = s.step > 0 ? shape[i] : -1; 398 } else { 399 n = s.stop; 400 if (n < 0) 401 n += shape[i]; 402 if (n < 0 || n > shape[i]) { 403 throw new IllegalArgumentException(String.format("Stop is out of bounds: %d is not in [%d,%d)", 404 n, s.stop, shape[i])); 405 } 406 stop[i] = n; 407 } 408 409 n = s.step; 410 if (n == 0) { 411 throw new IllegalArgumentException("Step cannot be zero"); 412 } 413 if (n > 0) { 414 if (start[i] > stop[i]) 415 throw new IllegalArgumentException("Start must be less than stop for positive steps"); 416 } else { 417 if (start[i] < stop[i]) 418 throw new IllegalArgumentException("Start must be greater than stop for negative steps"); 419 } 420 step[i] = n; 421 } 422 for (; i < rank; i++) { 423 start[i] = 0; 424 stop[i] = shape[i]; 425 step[i] = 1; 426 } 427 } 428 429 /** 430 * Convert from a set of integer arrays to a slice array 431 * @param start 432 * @param stop 433 * @param step 434 * @return a slice array 435 */ 436 public static Slice[] convertToSlice(final int[] start, final int[] stop, final int[] step) { 437 int orank = start.length; 438 439 if (stop.length != orank || step.length != orank) { 440 throw new IllegalArgumentException("All arrays must be same length"); 441 } 442 443 Slice[] slice = new Slice[orank]; 444 445 for (int j = 0; j < orank; j++) { 446 slice[j] = new Slice(start[j], stop[j], step[j]); 447 } 448 449 return slice; 450 } 451 452 /** 453 * Convert a string to a slice array 454 * 455 * @param sliceString 456 * @return a slice array 457 */ 458 public static Slice[] convertFromString(String sliceString) { 459 460 String clean = sliceString.replace("[", ""); 461 clean = clean.replace("]", ""); 462 463 String[] sub = clean.split(","); 464 465 Slice[] slices = new Slice[sub.length]; 466 467 for (int i = 0; i < sub.length; i++) { 468 String s = sub[i]; 469 470 Slice slice = new Slice(); 471 slices[i] = slice; 472 473 int idx0 = s.indexOf(":"); 474 475 int n = 0; 476 if (idx0 == -1) { 477 n = Integer.parseInt(s); 478 slice.setStart(n); 479 slice.setStop(n + 1); 480 continue; 481 } else if (idx0 != 0) { 482 n = Integer.parseInt(s.substring(0, idx0)); 483 } 484 slice.setStart(n); 485 486 idx0++; 487 int idx1 = s.indexOf(":", idx0); 488 if (idx1 == -1) { 489 String t = s.substring(idx0).trim(); 490 if (t.length() == 0) 491 continue; 492 493 slice.setStop(Integer.parseInt(t)); 494 continue; 495 } else if (idx1 != idx0) { 496 slice.setStop(Integer.parseInt(s.substring(idx0, idx1))); 497 } 498 499 String t = s.substring(idx1 + 1).trim(); 500 if (t.length() > 0) 501 slice.setStep(Integer.parseInt(t)); 502 } 503 504 return slices; 505 } 506 507 /** 508 * Create a string representing the slice taken from given shape 509 * @param shape 510 * @param start 511 * @param stop 512 * @param step 513 * @return string representation 514 */ 515 public static String createString(final int[] shape, final int[] start, final int[] stop, final int[] step) { 516 final int rank = shape.length; 517 if (rank == 0) { 518 return ""; 519 } 520 StringBuilder s = new StringBuilder(); 521 for (int i = 0; i < rank; i++) { 522 int l = shape[i]; 523 int d = step == null ? 1 : step[i]; 524 int b = start == null ? (d > 0 ? 0 : l - 1) : start[i]; 525 int e = stop == null ? (d > 0 ? l : -1) : stop[i]; 526 527 appendSliceToString(s, l, b, e, d); 528 s.append(','); 529 } 530 531 return s.substring(0, s.length()-1); 532 } 533 534 /** 535 * @param slices 536 * @return string specifying slices 537 */ 538 public static String createString(Slice... slices) { 539 if (slices == null || slices.length == 0) { 540 return ""; 541 } 542 543 StringBuilder t = new StringBuilder(); 544 for (Slice s : slices) { 545 t.append(s != null ? s.toString() : ':'); 546 t.append(','); 547 } 548 549 return t.substring(0, t.length() - 1); 550 } 551}