docs: def _series_shaped(self):
          """Return image series in "shaped" file."""
          pages = self.pages
          pages.useframes = True
          lenpages = len(pages)
  
          def append_series(series, pages, axes, shape, reshape, name,
                            truncated):
              page = pages[0]
              if not axes:
                  shape = page.shape
                  axes = page.axes
                  if len(pages) > 1:
                      shape = (len(pages),) + shape
                      axes = 'Q' + axes
              size = product(shape)
              resize = product(reshape)
              if page.is_contiguous and resize > size and resize % size == 0:
                  if truncated is None:
                      truncated = True
                  axes = 'Q' + axes
                  shape = (resize // size,) + shape
              try:
                  axes = reshape_axes(axes, shape, reshape)
                  shape = reshape
              except ValueError as exc:
                  log.warning('Shaped series: %s: %s',
                              exc.__class__.__name__, exc)
              series.append(
                  TiffPageSeries(pages, shape, page.dtype, axes, name=name,
                                 kind='Shaped', truncated=truncated))
  
          keyframe = axes = shape = reshape = name = None
          series = []
          index = 0
          while True:
              if index >= lenpages:
                  break
              # new keyframe; start of new series
              pages.keyframe = index
              keyframe = pages.keyframe
              if not keyframe.is_shaped:
                  log.warning(
                      'Shaped series: invalid metadata or corrupted file')
                  return None
              # read metadata
              axes = None
              shape = None
              metadata = json_description_metadata(keyframe.is_shaped)
              name = metadata.get('name', '')
              reshape = metadata['shape']
              truncated = metadata.get('truncated', None)
              if 'axes' in metadata:
                  axes = metadata['axes']
                  if len(axes) == len(reshape):
                      shape = reshape
                  else:
                      axes = ''
                      log.warning('Shaped series: axes do not match shape')
              # skip pages if possible
              spages = [keyframe]
              size = product(reshape)
              npages, mod = divmod(size, product(keyframe.shape))
              if mod:
                  log.warning(
                      'Shaped series: series shape does not match page shape')
                  return None
              if 1 < npages <= lenpages - index:
                  size *= keyframe._dtype.itemsize
                  if truncated:
                      npages = 1
                  elif (keyframe.is_final and
                        keyframe.offset + size < pages[index+1].offset):
                      truncated = False
                  else:
                      # need to read all pages for series
                      truncated = False
                      for j in range(index+1, index+npages):
                          page = pages[j]
                          page.keyframe = keyframe
                          spages.append(page)
              append_series(series, spages, axes, shape, reshape, name,
                            truncated)
              index += npages
  
          self.is_uniform = len(series) == 1
  
          return series