1''' 2:class:`~spectral.SpyFile` is the base class for creating objects to read 3hyperspectral data files. When a :class:`~spectral.SpyFile` object is created, 4it provides an interface to read data from a corresponding file. When an image 5is opened, the actual object returned will be a subclass of 6:class:`~spectral.SpyFile` (BipFile, BilFile, or BsqFile) corresponding to the 7interleave of the data within the image file. 8 9Let's open our sample image. 10 11.. ipython:: 12 13 In [1]: from spectral import * 14 15 In [2]: img = open_image('92AV3C.lan') 16 17 In [3]: img.__class__ 18 Out[3]: spectral.io.bilfile.BilFile 19 20 In [4]: print(img) 21 Data Source: '/Users/thomas/spectral_data/92AV3C.lan' 22 # Rows: 145 23 # Samples: 145 24 # Bands: 220 25 Interleave: BIL 26 Quantization: 16 bits 27 Data format: int16 28 29The image was not located in the working directory but it was still opened 30because it was in a directory specified by the *SPECTRAL_DATA* environment 31variable. Because the image pixel data are interleaved by line, the *image* 32function returned a *BilFile* instance. 33 34Since hyperspectral image files can be quite large, only 35metadata are read from the file when the :class:`~spectral.SpyFile` object is 36first created. Image data values are only read when specifically requested via 37:class:`~spectral.SpyFile` methods. The :class:`~spectral.SpyFile` class 38provides a subscript operator that behaves much like the numpy array subscript 39operator. The :class:`~spectral.SpyFile` object is subscripted as an *MxNxB* 40array where *M* is the number of rows in the image, *N* is the number of 41columns, and *B* is thenumber of bands. 42 43.. ipython:: 44 45 In [5]: img.shape 46 Out[5]: (145, 145, 220) 47 48 In [6]: pixel = img[50,100] 49 50 In [7]: pixel.shape 51 Out[7]: (220,) 52 53 In [8]: band6 = img[:,:,5] 54 55 In [9]: band6.shape 56 Out[9]: (145, 145, 1) 57 58The image data values were not read from the file until the subscript operator 59calls were performed. Note that since Python indices start at 0, 60``img[50,100]`` refers to the pixel at 51st row and 101st column of the image. 61Similarly, ``img[:,:,5]`` refers to all the rows and columns for the 6th band 62of the image. 63 64:class:`~spectral.SpyFile` subclass instances returned for particular image 65files will also provide the following methods: 66 67============== =============================================================== 68 Method Description 69============== =============================================================== 70read_band Reads a single band into an *MxN* array 71read_bands Reads multiple bands into an *MxNxC* array 72read_pixel Reads a single pixel into a length *B* array 73read_subregion Reads multiple bands from a rectangular sub-region of the image 74read_subimage Reads specified rows, columns, and bands 75============== =============================================================== 76 77:class:`~spectral.SpyFile` objects have a ``bands`` member, which is an 78instance of a :class:`~spectral.BandInfo` object that contains optional 79information about the images spectral bands. 80''' 81 82from __future__ import absolute_import, division, print_function, unicode_literals 83 84import array 85import numpy as np 86import os 87import warnings 88 89import spectral as spy 90from .. import SpyException 91from ..image import Image, ImageArray 92from ..utilities.errors import has_nan, NaNValueWarning 93from ..utilities.python23 import typecode, tobytes, frombytes 94 95 96class FileNotFoundError(SpyException): 97 pass 98 99class InvalidFileError(SpyException): 100 '''Raised when file contents are invalid for the exepected file type.''' 101 pass 102 103def find_file_path(filename): 104 ''' 105 Search cwd and SPECTRAL_DATA directories for the given file. 106 ''' 107 pathname = None 108 dirs = [os.curdir] 109 if 'SPECTRAL_DATA' in os.environ: 110 dirs += os.environ['SPECTRAL_DATA'].split(os.pathsep) 111 for d in dirs: 112 testpath = os.path.join(d, filename) 113 if os.path.isfile(testpath): 114 pathname = testpath 115 break 116 if not pathname: 117 msg = 'Unable to locate file "%s". If the file exists, ' \ 118 'use its full path or place its directory in the ' \ 119 'SPECTRAL_DATA environment variable.' % filename 120 raise FileNotFoundError(msg) 121 return pathname 122 123 124class SpyFile(Image): 125 '''A base class for accessing spectral image files''' 126 127 def __init__(self, params, metadata=None): 128 Image.__init__(self, params, metadata) 129 # Number by which to divide values read from file. 130 self.scale_factor = 1.0 131 132 def set_params(self, params, metadata): 133 Image.set_params(self, params, metadata) 134 135 try: 136 self.filename = params.filename 137 self.offset = params.offset 138 self.byte_order = params.byte_order 139 if spy.byte_order != self.byte_order: 140 self.swap = 1 141 else: 142 self.swap = 0 143 self.sample_size = np.dtype(params.dtype).itemsize 144 145 self.fid = open(find_file_path(self.filename), "rb") 146 147 # So that we can use this more like a Numeric array 148 self.shape = (self.nrows, self.ncols, self.nbands) 149 150 except: 151 raise 152 153 def transform(self, xform): 154 '''Returns a SpyFile image with the linear transform applied.''' 155 # This allows a LinearTransform object to take the SpyFile as an arg. 156 return transform_image(xform, self) 157 158 def __str__(self): 159 '''Prints basic parameters of the associated file.''' 160 s = '\tData Source: \'%s\'\n' % self.filename 161 s += '\t# Rows: %6d\n' % (self.nrows) 162 s += '\t# Samples: %6d\n' % (self.ncols) 163 s += '\t# Bands: %6d\n' % (self.shape[2]) 164 if self.interleave == spy.BIL: 165 interleave = 'BIL' 166 elif self.interleave == spy.BIP: 167 interleave = 'BIP' 168 else: 169 interleave = 'BSQ' 170 s += '\tInterleave: %6s\n' % (interleave) 171 s += '\tQuantization: %3d bits\n' % (self.sample_size * 8) 172 173 s += '\tData format: %8s' % np.dtype(self.dtype).name 174 return s 175 176 def load(self, **kwargs): 177 '''Loads entire image into memory in a :class:`spectral.image.ImageArray`. 178 179 Keyword Arguments: 180 181 `dtype` (numpy.dtype): 182 183 An optional dtype to which the loaded array should be cast. 184 185 `scale` (bool, default True): 186 187 Specifies whether any applicable scale factor should be applied 188 to the data after loading. 189 190 :class:`spectral.image.ImageArray` is derived from both 191 :class:`spectral.image.Image` and :class:`numpy.ndarray` so it supports the 192 full :class:`numpy.ndarray` interface. The returns object will have 193 shape `(M,N,B)`, where `M`, `N`, and `B` are the numbers of rows, 194 columns, and bands in the image. 195 ''' 196 for k in list(kwargs.keys()): 197 if k not in ('dtype', 'scale'): 198 raise ValueError('Invalid keyword %s.' % str(k)) 199 dtype = kwargs.get('dtype', ImageArray.format) 200 data = array.array(typecode('b')) 201 self.fid.seek(self.offset) 202 data.fromfile(self.fid, self.nrows * self.ncols * 203 self.nbands * self.sample_size) 204 npArray = np.frombuffer(tobytes(data), dtype=self.dtype) 205 if self.interleave == spy.BIL: 206 npArray.shape = (self.nrows, self.nbands, self.ncols) 207 npArray = npArray.transpose([0, 2, 1]) 208 elif self.interleave == spy.BSQ: 209 npArray.shape = (self.nbands, self.nrows, self.ncols) 210 npArray = npArray.transpose([1, 2, 0]) 211 else: 212 npArray.shape = (self.nrows, self.ncols, self.nbands) 213 npArray = npArray.astype(dtype) 214 if self.scale_factor != 1 and kwargs.get('scale', True): 215 npArray = npArray / float(self.scale_factor) 216 imarray = ImageArray(npArray, self) 217 if has_nan(imarray): 218 warnings.warn('Image data contains NaN values.', NaNValueWarning) 219 return imarray 220 221 def __getitem__(self, args): 222 '''Subscripting operator that provides a numpy-like interface. 223 Usage:: 224 225 x = img[i, j] 226 x = img[i, j, k] 227 228 Arguments: 229 230 `i`, `j`, `k` (int or :class:`slice` object) 231 232 Integer subscript indices or slice objects. 233 234 The subscript operator emulates the :class:`numpy.ndarray` subscript 235 operator, except data are read from the corresponding image file 236 instead of an array object in memory. For frequent access or when 237 accessing a large fraction of the image data, consider calling 238 :meth:`spectral.SpyFile.load` to load the data into an 239 :meth:`spectral.image.ImageArray` object and using its subscript operator 240 instead. 241 242 Examples: 243 244 Read the pixel at the 30th row and 51st column of the image:: 245 246 pixel = img[29, 50] 247 248 Read the 10th band:: 249 250 band = img[:, :, 9] 251 252 Read the first 30 bands for a square sub-region of the image:: 253 254 region = img[50:100, 50:100, :30] 255 ''' 256 257 atypes = [type(a) for a in args] 258 259 if len(args) < 2: 260 raise IndexError('Too few subscript indices.') 261 262 fix_negative_indices = self._fix_negative_indices 263 264 if atypes[0] == atypes[1] == int and len(args) == 2: 265 row = fix_negative_indices(args[0], 0) 266 col = fix_negative_indices(args[1], 1) 267 return self.read_pixel(row, col) 268 elif len(args) == 3 and atypes[0] == atypes[1] == atypes[2] == int: 269 row = fix_negative_indices(args[0], 0) 270 col = fix_negative_indices(args[1], 1) 271 band = fix_negative_indices(args[2], 2) 272 return self.read_datum(row, col, band) 273 else: 274 # At least one arg should be a slice 275 if atypes[0] == slice: 276 (xstart, xstop, xstep) = (args[0].start, args[0].stop, 277 args[0].step) 278 if xstart is None: 279 xstart = 0 280 if xstop is None: 281 xstop = self.nrows 282 if xstep is None: 283 xstep = 1 284 rows = list(range(xstart, xstop, xstep)) 285 else: 286 rows = [args[0]] 287 if atypes[1] == slice: 288 (ystart, ystop, ystep) = (args[1].start, args[1].stop, 289 args[1].step) 290 if ystart is None: 291 ystart = 0 292 if ystop is None: 293 ystop = self.ncols 294 if ystep is None: 295 ystep = 1 296 cols = list(range(ystart, ystop, ystep)) 297 else: 298 cols = [args[1]] 299 300 if len(args) == 2 or args[2] is None: 301 bands = None 302 elif atypes[2] == slice: 303 (zstart, zstop, zstep) = (args[2].start, args[2].stop, 304 args[2].step) 305 if zstart == zstop == zstep == None: 306 bands = None 307 else: 308 if zstart is None: 309 zstart = 0 310 if zstop is None: 311 zstop = self.nbands 312 if zstep is None: 313 zstep = 1 314 bands = list(range(zstart, zstop, zstep)) 315 elif atypes[2] == int: 316 bands = [args[2]] 317 else: 318 # Band indices should be in a list 319 bands = args[2] 320 321 if atypes[0] == slice and xstep == 1 \ 322 and atypes[1] == slice and ystep == 1 \ 323 and (bands is None or type(bands) == list): 324 xstart = fix_negative_indices(xstart, 0) 325 xstop = fix_negative_indices(xstop, 0) 326 ystart = fix_negative_indices(ystart, 0) 327 ystop = fix_negative_indices(ystop, 0) 328 bands = fix_negative_indices(bands, 2) 329 return self.read_subregion((xstart, xstop), (ystart, ystop), bands) 330 331 rows = fix_negative_indices(rows, 0) 332 cols = fix_negative_indices(cols, 1) 333 bands = fix_negative_indices(bands, 2) 334 return self.read_subimage(rows, cols, bands) 335 336 def _fix_negative_indices(self, indices, dim): 337 if not indices: 338 return indices 339 340 dim_len = self.shape[dim] 341 try: 342 return [i if i >= 0 else dim_len + i 343 for i in indices] 344 except: 345 return indices if indices >= 0 else dim_len + indices 346 347 def params(self): 348 '''Return an object containing the SpyFile parameters.''' 349 p = Image.params(self) 350 351 p.filename = self.filename 352 p.offset = self.offset 353 p.byte_order = self.byte_order 354 p.sample_size = self.sample_size 355 356 return p 357 358 def __del__(self): 359 self.fid.close() 360 361 362class SubImage(SpyFile): 363 ''' 364 Represents a rectangular sub-region of a larger SpyFile object. 365 ''' 366 def __init__(self, image, row_range, col_range): 367 '''Creates a :class:`Spectral.SubImage` for a rectangular sub-region. 368 369 Arguments: 370 371 `image` (SpyFile): 372 373 The image for which to define the sub-image. 374 375 `row_range` (2-tuple): 376 377 Integers [i, j) defining the row limits of the sub-region. 378 379 `col_range` (2-tuple): 380 381 Integers [i, j) defining the col limits of the sub-region. 382 383 Returns: 384 385 A :class:`spectral.SubImage` object providing a 386 :class:`spectral.SpyFile` interface to a sub-region of the image. 387 388 Raises: 389 390 :class:`IndexError` 391 392 Row and column ranges must be 2-tuples (i,j) where i >= 0 and i < j. 393 394 ''' 395 if row_range[0] < 0 or \ 396 row_range[1] > image.nrows or \ 397 col_range[0] < 0 or \ 398 col_range[1] > image.ncols: 399 raise IndexError('SubImage index out of range.') 400 401 p = image.params() 402 403 SpyFile.__init__(self, p, image.metadata) 404 self.parent = image 405 self.row_offset = row_range[0] 406 self.col_offset = col_range[0] 407 self.nrows = row_range[1] - row_range[0] 408 self.ncols = col_range[1] - col_range[0] 409 self.shape = (self.nrows, self.ncols, self.nbands) 410 411 def read_band(self, band): 412 '''Reads a single band from the image. 413 414 Arguments: 415 416 `band` (int): 417 418 Index of band to read. 419 420 Returns: 421 422 :class:`numpy.ndarray` 423 424 An `MxN` array of values for the specified band. 425 ''' 426 return self.parent.read_subregion([self.row_offset, 427 self.row_offset + self.nrows - 1], 428 [self.col_offset, 429 self.col_offset + self.ncols - 1], 430 [band]) 431 432 def read_bands(self, bands): 433 '''Reads multiple bands from the image. 434 435 Arguments: 436 437 `bands` (list of ints): 438 439 Indices of bands to read. 440 441 Returns: 442 443 :class:`numpy.ndarray` 444 445 An `MxNxL` array of values for the specified bands. `M` and `N` 446 are the number of rows & columns in the image and `L` equals 447 len(`bands`). 448 ''' 449 return self.parent.read_subregion([self.row_offset, 450 self.row_offset + self.nrows - 1], 451 [self.col_offset, 452 self.col_offset + self.ncols - 1], 453 bands) 454 455 def read_pixel(self, row, col): 456 '''Reads the pixel at position (row,col) from the file. 457 458 Arguments: 459 460 `row`, `col` (int): 461 462 Indices of the row & column for the pixel 463 464 Returns: 465 466 :class:`numpy.ndarray` 467 468 A length-`B` array, where `B` is the number of image bands. 469 ''' 470 return self.parent.read_pixel(row + self.row_offset, 471 col + self.col_offset) 472 473 def read_subimage(self, rows, cols, bands=[]): 474 ''' 475 Reads arbitrary rows, columns, and bands from the image. 476 477 Arguments: 478 479 `rows` (list of ints): 480 481 Indices of rows to read. 482 483 `cols` (list of ints): 484 485 Indices of columns to read. 486 487 `bands` (list of ints): 488 489 Optional list of bands to read. If not specified, all bands 490 are read. 491 492 Returns: 493 494 :class:`numpy.ndarray` 495 496 An `MxNxL` array, where `M` = len(`rows`), `N` = len(`cols`), 497 and `L` = len(bands) (or # of image bands if `bands` == None). 498 ''' 499 return self.parent.read_subimage(list(array.array(rows) \ 500 + self.row_offset), 501 list(array.array(cols) \ 502 + self.col_offset), 503 bands) 504 505 def read_subregion(self, row_bounds, col_bounds, bands=None): 506 ''' 507 Reads a contiguous rectangular sub-region from the image. 508 509 Arguments: 510 511 `row_bounds` (2-tuple of ints): 512 513 (a, b) -> Rows a through b-1 will be read. 514 515 `col_bounds` (2-tuple of ints): 516 517 (a, b) -> Columnss a through b-1 will be read. 518 519 `bands` (list of ints): 520 521 Optional list of bands to read. If not specified, all bands 522 are read. 523 524 Returns: 525 526 :class:`numpy.ndarray` 527 528 An `MxNxL` array. 529 ''' 530 return self.parent.read_subimage(list(np.array(row_bounds) \ 531 + self.row_offset), 532 list(np.array(col_bounds) \ 533 + self.col_offset), 534 bands) 535 536 537def tile_image(im, nrows, ncols): 538 ''' 539 Break an image into nrows x ncols tiles. 540 541 USAGE: tiles = tile_image(im, nrows, ncols) 542 543 ARGUMENTS: 544 im The SpyFile to tile. 545 nrows Number of tiles in the veritical direction. 546 ncols Number of tiles in the horizontal direction. 547 548 RETURN VALUE: 549 tiles A list of lists of SubImage objects. tiles 550 contains nrows lists, each of which contains 551 ncols SubImage objects. 552 ''' 553 x = (np.array(list(range(nrows + 1))) * float(im.nrows) / nrows).astype(int) 554 y = (np.array(list(range(ncols + 1))) * float(im.ncols) / ncols).astype(int) 555 x[-1] = im.nrows 556 y[-1] = im.ncols 557 558 tiles = [] 559 for r in range(len(x) - 1): 560 row = [] 561 for c in range(len(y) - 1): 562 si = SubImage(im, [x[r], x[r + 1]], [y[c], y[c + 1]]) 563 row.append(si) 564 tiles.append(row) 565 return tiles 566 567def transform_image(transform, img): 568 '''Applies a linear transform to an image. 569 570 Arguments: 571 572 `transform` (ndarray or LinearTransform): 573 574 The `CxB` linear transform to apply. 575 576 `img` (ndarray or :class:`spectral.SpyFile`): 577 578 The `MxNxB` image to be transformed. 579 580 Returns (ndarray or :class:spectral.spyfile.TransformedImage`): 581 582 The transformed image. 583 584 If `img` is an ndarray, then a `MxNxC` ndarray is returned. If `img` is 585 a :class:`spectral.SpyFile`, then a 586 :class:`spectral.spyfile.TransformedImage` is returned. 587 ''' 588 from ..algorithms.transforms import LinearTransform 589 if isinstance(img, np.ndarray): 590 if isinstance(transform, LinearTransform): 591 return transform(img) 592 ret = np.empty(img.shape[:2] + (transform.shape[0],), img.dtype) 593 for i in range(img.shape[0]): 594 for j in range(img.shape[1]): 595 ret[i, j] = np.dot(transform, img[i, j]) 596 return ret 597 else: 598 return TransformedImage(transform, img) 599 600 601class TransformedImage(Image): 602 ''' 603 An image with a linear transformation applied to each pixel spectrum. 604 The transformation is not applied until data is read from the image file. 605 ''' 606 dtype = np.dtype('f4').char 607 608 def __init__(self, transform, img): 609 from ..algorithms.transforms import LinearTransform 610 if not isinstance(img, Image): 611 raise Exception( 612 'Invalid image argument to to TransformedImage constructor.') 613 614 if isinstance(transform, np.ndarray): 615 transform = LinearTransform(transform) 616 self.transform = transform 617 618 if self.transform.dim_in not in (None, img.shape[-1]): 619 raise Exception('Number of bands in image (%d) do not match the ' 620 ' input dimension of the transform (%d).' 621 % (img.shape[-1], transform.dim_in)) 622 623 params = img.params() 624 self.set_params(params, params.metadata) 625 626 # If img is also a TransformedImage, then just modify the transform 627 if isinstance(img, TransformedImage): 628 self.transform = self.transform.chain(img.transform) 629 self.image = img.image 630 else: 631 self.image = img 632 if self.transform.dim_out is not None: 633 self.shape = self.image.shape[:2] + (self.transform.dim_out,) 634 self.nbands = self.transform.dim_out 635 else: 636 self.shape = self.image.shape 637 self.nbands = self.image.nbands 638 639 @property 640 def bands(self): 641 return self.image.bands 642 643 def __getitem__(self, args): 644 ''' 645 Get data from the image and apply the transform. 646 ''' 647 if len(args) < 2: 648 raise Exception('Must pass at least two subscript arguments') 649 650 # Note that band indices are wrt transformed features 651 if len(args) == 2 or args[2] is None: 652 bands = list(range(self.nbands)) 653 elif type(args[2]) == slice: 654 (zstart, zstop, zstep) = (args[2].start, args[2].stop, 655 args[2].step) 656 if zstart is None: 657 zstart = 0 658 if zstop is None: 659 zstop = self.nbands 660 if zstep is None: 661 zstep = 1 662 bands = list(range(zstart, zstop, zstep)) 663 elif isinstance(args[2], int): 664 bands = [args[2]] 665 else: 666 # Band indices should be in a list 667 bands = args[2] 668 669 orig = self.image.__getitem__(args[:2]) 670 if len(orig.shape) == 1: 671 orig = orig[np.newaxis, np.newaxis, :] 672 elif len(orig.shape) == 2: 673 orig = orig[np.newaxis, :] 674 transformed_xy = np.zeros(orig.shape[:2] + (self.shape[2],), 675 self.transform.dtype) 676 for i in range(transformed_xy.shape[0]): 677 for j in range(transformed_xy.shape[1]): 678 transformed_xy[i, j] = self.transform(orig[i, j]) 679 # Remove unnecessary dimensions 680 681 transformed = np.take(transformed_xy, bands, 2) 682 683 return transformed.squeeze() 684 685 def __str__(self): 686 s = '\tTransformedImage object with output dimensions:\n' 687 s += '\t# Rows: %6d\n' % (self.nrows) 688 s += '\t# Samples: %6d\n' % (self.ncols) 689 s += '\t# Bands: %6d\n\n' % (self.shape[2]) 690 s += '\tThe linear transform is applied to the following image:\n\n' 691 s += str(self.image) 692 return s 693 694 def read_pixel(self, row, col): 695 return self.transform(self.image.read_pixel(row, col)) 696 697 def load(self): 698 '''Loads all image data, transforms it, and returns an ndarray).''' 699 data = self.image.load() 700 return self.transform(data) 701 702 def read_subregion(self, row_bounds, col_bounds, bands=None): 703 ''' 704 Reads a contiguous rectangular sub-region from the image. First 705 arg is a 2-tuple specifying min and max row indices. Second arg 706 specifies column min and max. If third argument containing list 707 of band indices is not given, all bands are read. 708 ''' 709 data = self.image.read_subregion(row_bounds, col_bounds) 710 xdata = self.transform(data) 711 if bands: 712 return np.take(xdata, bands, 2) 713 else: 714 return xdata 715 716 def read_subimage(self, rows, cols, bands=None): 717 ''' 718 Reads a sub-image from a rectangular region within the image. 719 First arg is a 2-tuple specifying min and max row indices. 720 Second arg specifies column min and max. If third argument 721 containing list of band indices is not given, all bands are read. 722 ''' 723 data = self.image.read_subimage(rows, cols) 724 xdata = self.transform(data) 725 if bands: 726 return np.take(xdata, bands, 2) 727 else: 728 return xdata 729 730 def read_datum(self, i, j, k): 731 return self.read_pixel(i, j)[k] 732 733 def read_bands(self, bands): 734 shape = (self.image.nrows, self.image.ncols, len(bands)) 735 data = np.zeros(shape, float) 736 for i in range(shape[0]): 737 for j in range(shape[1]): 738 data[i, j] = self.read_pixel(i, j)[bands] 739 return data 740 741class MemmapFile(object): 742 '''Interface class for SpyFile subclasses using `numpy.memmap` objects.''' 743 744 def _disable_memmap(self): 745 '''Disables memmap and reverts to direct file reads (slower).''' 746 self._memmap = None 747 748 @property 749 def using_memmap(self): 750 '''Returns True if object is using a `numpy.memmap` to read data.''' 751 return self._memmap is not None 752 753 def open_memmap(self, **kwargs): 754 '''Returns a new `numpy.memmap` object for image file data access. 755 756 Keyword Arguments: 757 758 `interleave` (str, default 'bip'): 759 760 Specifies the shape/interleave of the returned object. Must be 761 one of ['bip', 'bil', 'bsq', 'source']. If not specified, the 762 memmap will be returned as 'bip'. If the interleave is 763 'source', the interleave of the memmap will be the same as the 764 source data file. If the number of rows, columns, and bands in 765 the file are R, C, and B, the shape of the returned memmap 766 array will be as follows: 767 768 .. table:: 769 770 ========== =========== 771 interleave array shape 772 ========== =========== 773 'bip' (R, C, B) 774 'bil' (R, B, C) 775 'bsq' (B, R, C) 776 ========== =========== 777 778 `writable` (bool, default False): 779 780 If `writable` is True, modifying values in the returned memmap 781 will result in corresponding modification to the image data 782 file. 783 ''' 784 src_inter = {spy.BIL: 'bil', 785 spy.BIP: 'bip', 786 spy.BSQ: 'bsq'}[self.interleave] 787 dst_inter = kwargs.get('interleave', 'bip').lower() 788 if dst_inter not in ['bip', 'bil', 'bsq', 'source']: 789 raise ValueError('Invalid interleave specified.') 790 if kwargs.get('writable', False) is True: 791 mode = 'r+' 792 else: 793 mode = 'r' 794 memmap = self._open_memmap(mode) 795 if dst_inter == 'source': 796 dst_inter = src_inter 797 if src_inter == dst_inter: 798 return memmap 799 else: 800 return np.transpose(memmap, interleave_transpose(src_inter, 801 dst_inter)) 802 803 def asarray(self, writable=False): 804 '''Returns an object with a standard numpy array interface. 805 806 The function returns a numpy memmap created with the 807 `open_memmap` method. 808 809 This function is for compatibility with ImageArray objects. 810 811 Keyword Arguments: 812 813 `writable` (bool, default False): 814 815 If `writable` is True, modifying values in the returned 816 memmap will result in corresponding modification to the 817 image data file. 818 ''' 819 return self.open_memmap(writable=writable) 820 821def interleave_transpose(int1, int2): 822 '''Returns the 3-tuple of indices to transpose between interleaves. 823 824 Arguments: 825 826 `int1`, `int2` (string): 827 828 The input and output interleaves. Each should be one of "bil", 829 "bip", or "bsq". 830 831 Returns: 832 833 A 3-tuple of integers that can be passed to `numpy.transpose` to 834 convert and RxCxB image between the two interleaves. 835 ''' 836 if int1.lower() not in ('bil', 'bip', 'bsq'): 837 raise ValueError('Invalid interleave: %s' % str(int1)) 838 if int2.lower() not in ('bil', 'bip', 'bsq'): 839 raise ValueError('Invalid interleave: %s' % str(int2)) 840 int1 = int1.lower() 841 int2 = int2.lower() 842 if int1 == 'bil': 843 if int2 == 'bil': 844 return (1, 1, 1) 845 elif int2 == 'bip': 846 return (0, 2, 1) 847 else: 848 return (1, 0, 2) 849 elif int1 == 'bip': 850 if int2 == 'bil': 851 return (0, 2, 1) 852 elif int2 == 'bip': 853 return (1, 1, 1) 854 else: 855 return (2, 0, 1) 856 else: # bsq 857 if int2 == 'bil': 858 return (1, 0, 2) 859 elif int2 == 'bip': 860 return (1, 2, 0) 861 else: 862 return (1, 1, 1) 863