docs: def asarray(self, out=None, squeeze=True, lock=None, reopen=True,
                  maxsize=None, maxworkers=None, validate=True):
          """Read image data from file and return as numpy array.
  
          Raise ValueError if format is unsupported.
  
          Parameters
          ----------
          out : numpy.ndarray, str, or file-like object
              Buffer where image data will be saved.
              If None (default), a new array will be created.
              If numpy.ndarray, a writable array of compatible dtype and shape.
              If 'memmap', directly memory-map the image data in the TIFF file
              if possible; else create a memory-mapped array in a temporary file.
              If str or open file, the file name or file object used to
              create a memory-map to an array stored in a binary file on disk.
          squeeze : bool
              If True (default), all length-1 dimensions (except X and Y) are
              squeezed out from the array.
              If False, the shape of the returned array might be different from
              the page.shape.
          lock : {RLock, NullContext}
              A reentrant lock used to syncronize reads from file.
              If None (default), the lock of the parent's filehandle is used.
          reopen : bool
              If True (default) and the parent file handle is closed, the file
              is temporarily re-opened and closed if no exception occurs.
          maxsize: int
              Maximum size of data before a ValueError is raised.
              Can be used to catch DOS. Default: 16 TB.
          maxworkers : int or None
              Maximum number of threads to concurrently decode tile data.
              If None (default), up to half the CPU cores are used for
              compressed tiles.
              See remarks in TiffFile.asarray.
          validate : bool
              If True (default), validate various parameters.
              If None, only validate parameters and return None.
  
          Returns
          -------
          numpy.ndarray
              Numpy array of decompressed, depredicted, and unpacked image data
              read from Strip/Tile Offsets/ByteCounts, formatted according to
              shape and dtype metadata found in tags and parameters.
              Photometric conversion, pre-multiplied alpha, orientation, and
              colorimetry corrections are not applied. Specifically, CMYK images
              are not converted to RGB, MinIsWhite images are not inverted,
              and color palettes are not applied.
  
          """
          # properties from TiffPage or TiffFrame
          fh = self.parent.filehandle
          byteorder = self.parent.tiff.byteorder
          offsets, bytecounts = self._offsetscounts
          self_ = self
          self = self.keyframe  # self or keyframe
  
          if not self._shape or product(self._shape) == 0:
              return None
  
          tags = self.tags
  
          if validate or validate is None:
              if maxsize is None:
                  maxsize = 2**44
              if maxsize and product(self._shape) > maxsize:
                  raise ValueError('data are too large %s' % str(self._shape))
              if self.dtype is None:
                  raise ValueError('data type not supported: %s%i' % (
                      self.sampleformat, self.bitspersample))
              if self.compression not in TIFF.DECOMPESSORS:
                  raise ValueError(
                      'cannot decompress %s' % self.compression.name)
              if 'SampleFormat' in tags:
                  tag = tags['SampleFormat']
                  if tag.count != 1 and any((i-tag.value[0] for i in tag.value)):
                      raise ValueError(
                          'sample formats do not match %s' % tag.value)
              if self.is_subsampled and (self.compression not in (6, 7) or
                                         self.planarconfig == 2):
                  raise NotImplementedError('chroma subsampling not supported')
              if validate is None:
                  return None
  
          lock = fh.lock if lock is None else lock
          with lock:
              closed = fh.closed
              if closed:
                  if reopen:
                      fh.open()
                  else:
                      raise IOError('file handle is closed')
  
          dtype = self._dtype
          shape = self._shape
          imagewidth = self.imagewidth
          imagelength = self.imagelength
          imagedepth = self.imagedepth
          bitspersample = self.bitspersample
          typecode = byteorder + dtype.char
          lsb2msb = self.fillorder == 2
          istiled = self.is_tiled
  
          if istiled:
              tilewidth = self.tilewidth
              tilelength = self.tilelength
              tiledepth = self.tiledepth
              tw = (imagewidth + tilewidth - 1) // tilewidth
              tl = (imagelength + tilelength - 1) // tilelength
              td = (imagedepth + tiledepth - 1) // tiledepth
              tiledshape = (td, tl, tw)
              tileshape = (tiledepth, tilelength, tilewidth, shape[-1])
              runlen = tilewidth
          else:
              runlen = imagewidth
  
          if self.planarconfig == 1:
              runlen *= self.samplesperpixel
  
          if isinstance(out, str) and out == 'memmap' and self.is_memmappable:
              # direct memory map array in file
              with lock:
                  result = fh.memmap_array(typecode, shape, offset=offsets[0])
          elif self.is_contiguous:
              # read contiguous bytes to array
              if out is not None:
                  out = create_output(out, shape, dtype)
              with lock:
                  fh.seek(offsets[0])
                  result = fh.read_array(typecode, product(shape), out=out)
              if lsb2msb:
                  bitorder_decode(result, out=result)
          else:
              # decompress, unpack,... individual strips or tiles
              result = create_output(out, shape, dtype)
  
              decompress = TIFF.DECOMPESSORS[self.compression]
  
              if self.compression in (6, 7):  # COMPRESSION.JPEG
                  colorspace = None
                  outcolorspace = None
                  jpegtables = None
                  if lsb2msb:
                      log.warning('TiffPage.asarray: disabling LSB2MSB for JPEG')
                      lsb2msb = False
                  if 'JPEGTables' in tags:
                      # load JPEGTables from TiffFrame
                      jpegtables = self_._gettags({347}, lock=lock)[0][1].value
                  # TODO: obtain table from OJPEG tags
                  # elif ('JPEGInterchangeFormat' in tags and
                  #       'JPEGInterchangeFormatLength' in tags and
                  #       tags['JPEGInterchangeFormat'].value != offsets[0]):
                  #     fh.seek(tags['JPEGInterchangeFormat'].value)
                  #     fh.read(tags['JPEGInterchangeFormatLength'].value)
                  if 'ExtraSamples' in tags:
                      pass
                  elif self.photometric == 6:
                      # YCBCR -> RGB
                      outcolorspace = 'RGB'
                  elif self.photometric == 2:
                      if self.planarconfig == 2:
                          # TODO: decode JPEG to planar RGB
                          raise NotImplementedError(
                              'cannot decode JPEG to planar RGB')
                      colorspace = outcolorspace = 'RGB'
                  else:
                      outcolorspace = TIFF.PHOTOMETRIC(self.photometric).name
                  if istiled:
                      heightwidth = tilelength, tilewidth
                  else:
                      heightwidth = imagelength, imagewidth
  
                  def decompress(data, bitspersample=bitspersample,
                                 jpegtables=jpegtables, colorspace=colorspace,
                                 outcolorspace=outcolorspace, shape=heightwidth,
                                 out=None, _decompress=decompress):
                      return _decompress(data, bitspersample, jpegtables,
                                         colorspace, outcolorspace, shape, out)
  
                  def unpack(data):
                      return data.reshape(-1)
  
              elif bitspersample in (8, 16, 32, 64, 128):
                  if (bitspersample * runlen) % 8:
                      raise ValueError('data and sample size mismatch')
                  if self.predictor == 3:  # PREDICTOR.FLOATINGPOINT
                      # the floating-point horizontal differencing decoder
                      # needs the raw byte order
                      typecode = dtype.char
  
                  def unpack(data, typecode=typecode, out=None):
                      try:
                          # read only numpy array
                          return numpy.frombuffer(data, typecode)
                      except ValueError:
                          # strips may be missing EOI
                          # log.warning('TiffPage.asarray: ...')
                          bps = bitspersample // 8
                          xlen = (len(data) // bps) * bps
                          return numpy.frombuffer(data[:xlen], typecode)
  
              elif isinstance(bitspersample, tuple):
  
                  def unpack(data, out=None):
                      return unpack_rgb(data, typecode, bitspersample)
  
              else:
  
                  def unpack(data, out=None):
                      return packints_decode(data, typecode, bitspersample,
                                             runlen)
  
              # TODO: store decode function for future use
              # TODO: unify tile and strip decoding
              if istiled:
                  unpredict = TIFF.UNPREDICTORS[self.predictor]
  
                  def decode(tile, tileindex):
                      return tile_decode(tile, tileindex, tileshape, tiledshape,
                                         lsb2msb, decompress, unpack, unpredict,
                                         result[0])
  
                  tileiter = buffered_read(fh, lock, offsets, bytecounts)
                  if maxworkers is None:
                      maxworkers = 0 if self.compression > 1 else 1
                  if maxworkers == 0:
                      import multiprocessing  # noqa: delay import
                      maxworkers = multiprocessing.cpu_count() // 2
                  if maxworkers < 2:
                      for i, tile in enumerate(tileiter):
                          decode(tile, i)
                  else:
                      # decode first tile un-threaded to catch exceptions
                      decode(next(tileiter), 0)
                      with ThreadPoolExecutor(maxworkers) as executor:
                          executor.map(decode, tileiter, range(1, len(offsets)))
  
              else:
                  stripsize = self.rowsperstrip * self.imagewidth
                  if self.planarconfig == 1:
                      stripsize *= self.samplesperpixel
                  outsize = stripsize * self.dtype.itemsize
                  result = result.reshape(-1)
                  index = 0
                  for strip in buffered_read(fh, lock, offsets, bytecounts):
                      if lsb2msb:
                          strip = bitorder_decode(strip, out=strip)
                      strip = decompress(strip, out=outsize)
                      strip = unpack(strip)
                      size = min(result.size, strip.size, stripsize,
                                 result.size - index)
                      result[index:index+size] = strip[:size]
                      del strip
                      index += size
  
          result.shape = self._shape
  
          if self.predictor != 1 and not (istiled and not self.is_contiguous):
              unpredict = TIFF.UNPREDICTORS[self.predictor]
              result = unpredict(result, axis=-2, out=result)
  
          if squeeze:
              try:
                  result.shape = self.shape
              except ValueError:
                  log.warning('TiffPage.asarray: failed to reshape %s to %s',
                              result.shape, self.shape)
  
          if closed:
              # TODO: file should remain open if an exception occurred above
              fh.close()
          return result