class_docstr: A reader for OME-NGFF files with the .zarr extension. Supports both 'normal' and
  'HCS' storage modes.
  
  OME-NGFF files below spec version 0.4 which are stored on S3/object storage
  should have the zarr.convenience.consolidate_metadata() function run on them
  to provide optimal performance over a network.
  
  Other non-OME .zarr format images may be readable, but are not strictly supported.
code: def iterate_groups(zarr_obj):
      if (
          "plate" in zarr_obj.attrs
          and "wells" in zarr_obj.attrs["plate"]
      ):
          # This is an HCS zarr with plate metadata. Use that to find the series.
          num_fields = zarr_obj.attrs["plate"]["field_count"]
          column_names = zarr_obj.attrs["plate"]["columns"]
          row_names = zarr_obj.attrs["plate"]["rows"]
          for attribs in zarr_obj.attrs["plate"]["wells"]:
              for field_idx in range(num_fields):
                  yield [
                      zarr_obj[f"{attribs['path']}/{field_idx}"],
                      f"r{row_names[attribs['row_index']]['name']}"
                      f"c{column_names[attribs['column_index']]['name']}"
                      f"f{field_idx}",
                  ]
      elif "OME" in zarr_obj:
          # This is a zarr with OME metadata stored as a group. Grab a series list from this.
          ome_group = zarr_obj["OME"]
          if "series" in ome_group.attrs:
              series_paths = ome_group.attrs["series"]
              for idx, series_path in enumerate(series_paths):
                  target = zarr_obj[series_path]
                  yield [target, ""]
      elif "multiscales" in zarr_obj.attrs:
          # User has pointed directly to a zarr series, we can assume no child groups.
          yield [zarr_obj, ""]
      else:
          # This is a simple or older zarr. Look for an ome.xml metadata file and read that.
          xml_path = zarr_obj.store.path + "/OME/METADATA.ome.xml"
          if os.path.exists(xml_path):
              ns = "{http://www.openmicroscopy.org/Schemas/OME/2016-06}"
              tree = etree.parse(xml_path)
              root = tree.getroot()
              if ns not in root.tag:
                  raise Exception(f"Expected {ns} in XML file")
              for image in root.findall(f"{ns}Image"):
                  series_path = image.attrib["ID"].split(":")[1]
                  series_name = image.attrib.get("Name", series_path)
                  if series_path in zarr_obj:
                      yield [zarr_obj[series_path], series_name]
                  else:
                      LOGGER.warning(
                          f"Series specified in OME-XML "
                          f"was absent from the "
                          f"zarr - {series_path}"
                      )
          else:
              # No XML or zattrs = some other software made this zarr.
              # We can try scanning for groups, but this is slow.
              # Scans will return nothing if the storage backend is object-based (e.g. S3)
              LOGGER.error(
                  "Unable to find built-in or XML metadata, "
                  "will try a file scan"
              )
              for key in zarr_obj.group_keys():
                  yield [zarr_obj[key], key]
  
imports: import numpy
  import zarr
  from ..constants.image import MD_SIZE_S, MD_SIZE_C, MD_SIZE_Z, MD_SIZE_T, MD_SIZE_Y, MD_SIZE_X, MD_SERIES_NAME
  from ..constants.reader import ZARR_FILETYPE
  from ..reader import Reader
  import os
  import re
  import logging
  from lxml import etree
label_desc: Use image node_id 11 for calling the NGFFReader obj's underlying member method code with example usage: obj.iterate_groups(zarr_obj) without return types