docs: def _series_ome(self):
          """Return image series in OME-TIFF file(s)."""
          from xml.etree import cElementTree as etree  # delayed import
          omexml = self.pages[0].description
          try:
              root = etree.fromstring(omexml)
          except etree.ParseError as exc:
              # TODO: test badly encoded OME-XML
              log.warning('OME series: %s: %s', exc.__class__.__name__, exc)
              try:
                  # might work on Python 2
                  omexml = omexml.decode('utf-8', 'ignore').encode('utf-8')
                  root = etree.fromstring(omexml)
              except Exception:
                  return None
  
          self.pages.cache = True
          self.pages.useframes = True
          self.pages.keyframe = 0
          self.pages._load(keyframe=None)
  
          uuid = root.attrib.get('UUID', None)
          self._files = {uuid: self}
          dirname = self._fh.dirname
          modulo = {}
          series = []
          for element in root:
              if element.tag.endswith('BinaryOnly'):
                  # TODO: load OME-XML from master or companion file
                  log.warning('OME series: not an ome-tiff master file')
                  break
              if element.tag.endswith('StructuredAnnotations'):
                  for annot in element:
                      if not annot.attrib.get('Namespace',
                                              '').endswith('modulo'):
                          continue
                      for value in annot:
                          for modul in value:
                              for along in modul:
                                  if not along.tag[:-1].endswith('Along'):
                                      continue
                                  axis = along.tag[-1]
                                  newaxis = along.attrib.get('Type', 'other')
                                  newaxis = TIFF.AXES_LABELS[newaxis]
                                  if 'Start' in along.attrib:
                                      step = float(along.attrib.get('Step', 1))
                                      start = float(along.attrib['Start'])
                                      stop = float(along.attrib['End']) + step
                                      labels = numpy.arange(start, stop, step)
                                  else:
                                      labels = [label.text for label in along
                                                if label.tag.endswith('Label')]
                                  modulo[axis] = (newaxis, labels)
  
              if not element.tag.endswith('Image'):
                  continue
  
              attr = element.attrib
              name = attr.get('Name', None)
  
              for pixels in element:
                  if not pixels.tag.endswith('Pixels'):
                      continue
                  attr = pixels.attrib
                  # dtype = attr.get('PixelType', None)
                  axes = ''.join(reversed(attr['DimensionOrder']))
                  shape = idxshape = list(int(attr['Size'+ax]) for ax in axes)
                  size = product(shape[:-2])
                  ifds = None
                  spp = 1  # samples per pixel
                  for data in pixels:
                      if data.tag.endswith('Channel'):
                          attr = data.attrib
                          if ifds is None:
                              spp = int(attr.get('SamplesPerPixel', spp))
                              ifds = [None] * (size // spp)
                              if spp > 1:
                                  # correct channel dimension for spp
                                  idxshape = list((shape[i] // spp if ax == 'C'
                                                   else shape[i])
                                                  for i, ax in enumerate(axes))
                          elif int(attr.get('SamplesPerPixel', 1)) != spp:
                              raise ValueError(
                                  'cannot handle differing SamplesPerPixel')
                          continue
                      if ifds is None:
                          ifds = [None] * (size // spp)
                      if not data.tag.endswith('TiffData'):
                          continue
                      attr = data.attrib
                      ifd = int(attr.get('IFD', 0))
                      num = int(attr.get('NumPlanes', 1 if 'IFD' in attr else 0))
                      num = int(attr.get('PlaneCount', num))
                      idx = [int(attr.get('First'+ax, 0)) for ax in axes[:-2]]
                      try:
                          idx = numpy.ravel_multi_index(idx, idxshape[:-2])
                      except ValueError:
                          # ImageJ produces invalid ome-xml when cropping
                          log.warning('OME series: invalid TiffData index')
                          continue
                      for uuid in data:
                          if not uuid.tag.endswith('UUID'):
                              continue
                          if uuid.text not in self._files:
                              if not self._multifile:
                                  # abort reading multifile OME series
                                  # and fall back to generic series
                                  return []
                              fname = uuid.attrib['FileName']
                              try:
                                  tif = TiffFile(os.path.join(dirname, fname))
                                  tif.pages.cache = True
                                  tif.pages.useframes = True
                                  tif.pages.keyframe = 0
                                  tif.pages._load(keyframe=None)
                              except (IOError, FileNotFoundError, ValueError):
                                  log.warning("OME series: failed to read '%s'",
                                              fname)
                                  break
                              self._files[uuid.text] = tif
                              tif.close()
                          pages = self._files[uuid.text].pages
                          try:
                              for i in range(num if num else len(pages)):
                                  ifds[idx + i] = pages[ifd + i]
                          except IndexError:
                              log.warning('OME series: index out of range')
                          # only process first UUID
                          break
                      else:
                          pages = self.pages
                          try:
                              for i in range(num if num else
                                             min(len(pages), len(ifds))):
                                  ifds[idx + i] = pages[ifd + i]
                          except IndexError:
                              log.warning('OME series: index out of range')
  
                  if all(i is None for i in ifds):
                      # skip images without data
                      continue
  
                  # find a keyframe
                  keyframe = None
                  for i in ifds:
                      # try find a TiffPage
                      if i and i == i.keyframe:
                          keyframe = i
                          break
                  if keyframe is None:
                      # reload a TiffPage from file
                      for i, keyframe in enumerate(ifds):
                          if keyframe:
                              keyframe.parent.pages.keyframe = keyframe.index
                              keyframe = keyframe.parent.pages[keyframe.index]
                              ifds[i] = keyframe
                              break
  
                  # move channel axis to match PlanarConfiguration storage
                  # TODO: is this a bug or a inconsistency in the OME spec?
                  if spp > 1:
                      if keyframe.planarconfig == 1 and axes[-1] != 'C':
                          i = axes.index('C')
                          axes = axes[:i] + axes[i+1:] + axes[i:i+1]
                          shape = shape[:i] + shape[i+1:] + shape[i:i+1]
  
                  # FIXME: this implementation assumes the last dimensions are
                  # stored in TIFF pages. Apparently that is not always the case.
                  # For now, verify that shapes of keyframe and series match
                  # If not, skip series.
                  if keyframe.shape != tuple(shape[-len(keyframe.shape):]):
                      log.warning('OME series: incompatible page shape %s; '
                                  'expected %s', keyframe.shape,
                                  tuple(shape[-len(keyframe.shape):]))
                      del ifds
                      continue
  
                  # set a keyframe on all IFDs
                  for i in ifds:
                      if i is not None:
                          i.keyframe = keyframe
  
                  series.append(
                      TiffPageSeries(ifds, shape, keyframe.dtype, axes,
                                     parent=self, name=name, kind='OME'))
                  del ifds
  
          for serie in series:
              shape = list(serie.shape)
              for axis, (newaxis, labels) in modulo.items():
                  i = serie.axes.index(axis)
                  size = len(labels)
                  if shape[i] == size:
                      serie.axes = serie.axes.replace(axis, newaxis, 1)
                  else:
                      shape[i] //= size
                      shape.insert(i+1, size)
                      serie.axes = serie.axes.replace(axis, axis+newaxis, 1)
              serie.shape = tuple(shape)
  
          # squeeze dimensions
          for serie in series:
              serie.shape, serie.axes = squeeze_axes(serie.shape, serie.axes)
          self.is_uniform = len(series) == 1
          return series