docs: def save_image(xdata: DataAndMetadata.DataAndMetadata, file):
      """
      Saves the nparray data to the file-like object (or string) file.
      """
      # we need to create a basic DM tree suitable for an image
      # we'll try the minimum: just an data list
      # doesn't work. Do we need a ImageSourceList too?
      # and a DocumentObjectList?
  
      data = xdata.data
      data_descriptor = xdata.data_descriptor
      dimensional_calibrations = xdata.dimensional_calibrations
      intensity_calibration = xdata.intensity_calibration
      metadata = xdata.metadata
      modified = xdata.timestamp
      timezone = xdata.timezone
      timezone_offset = xdata.timezone_offset
      needs_slice = False
      is_sequence = False
  
      if len(data.shape) == 3 and data.dtype != numpy.uint8 and data_descriptor.datum_dimension_count == 1:
          data = numpy.moveaxis(data, 2, 0)
          dimensional_calibrations = (dimensional_calibrations[2],) + tuple(dimensional_calibrations[0:2])
      if len(data.shape) == 2 and data.dtype != numpy.uint8 and data_descriptor.datum_dimension_count == 1:
          is_sequence = data_descriptor.is_sequence
          data = numpy.moveaxis(data, 1, 0)
          data = numpy.expand_dims(data, axis=1)
          dimensional_calibrations = (dimensional_calibrations[1], Calibration.Calibration(), dimensional_calibrations[0])
          data_descriptor = DataAndMetadata.DataDescriptor(False, 2, 1)
          needs_slice = True
      data_dict = ndarray_to_imagedatadict(data)
      ret = {}
      ret["ImageList"] = [{"ImageData": data_dict}]
      if dimensional_calibrations and len(dimensional_calibrations) == len(data.shape):
          dimension_list = data_dict.setdefault("Calibrations", dict()).setdefault("Dimension", list())
          for dimensional_calibration in reversed(dimensional_calibrations):
              dimension = dict()
              if dimensional_calibration.scale != 0.0:
                  origin = -dimensional_calibration.offset / dimensional_calibration.scale
              else:
                  origin = 0.0
              dimension['Origin'] = origin
              dimension['Scale'] = dimensional_calibration.scale
              dimension['Units'] = dimensional_calibration.units
              dimension_list.append(dimension)
      if intensity_calibration:
          if intensity_calibration.scale != 0.0:
              origin = -intensity_calibration.offset / intensity_calibration.scale
          else:
              origin = 0.0
          brightness = data_dict.setdefault("Calibrations", dict()).setdefault("Brightness", dict())
          brightness['Origin'] = origin
          brightness['Scale'] = intensity_calibration.scale
          brightness['Units'] = intensity_calibration.units
      if modified:
          timezone_str = None
          if timezone_str is None and timezone:
              try:
                  import pytz
                  tz = pytz.timezone(timezone)
                  timezone_str = tz.tzname(modified)
              except:
                  pass
          if timezone_str is None and timezone_offset:
              timezone_str = timezone_offset
          timezone_str = " " + timezone_str if timezone_str is not None else ""
          date_str = modified.strftime("%x")
          time_str = modified.strftime("%X") + timezone_str
          ret["DataBar"] = {"Acquisition Date": date_str, "Acquisition Time": time_str}
      # I think ImageSource list creates a mapping between ImageSourceIds and Images
      ret["ImageSourceList"] = [{"ClassName": "ImageSource:Simple", "Id": [0], "ImageRef": 0}]
      # I think this lists the sources for the DocumentObjectlist. The source number is not
      # the indxe in the imagelist but is either the index in the ImageSourceList or the Id
      # from that list. We also need to set the annotation type to identify it as an data
      ret["DocumentObjectList"] = [{"ImageSource": 0, "AnnotationType": 20}]
      # finally some display options
      ret["Image Behavior"] = {"ViewDisplayID": 8}
      dm_metadata = copy.deepcopy(metadata)
      if metadata.get("hardware_source", dict()).get("signal_type", "").lower() == "eels":
          if len(data.shape) == 1 or (len(data.shape) == 2 and data.shape[0] == 1):
              dm_metadata.setdefault("Meta Data", dict())["Format"] = "Spectrum"
              dm_metadata.setdefault("Meta Data", dict())["Signal"] = "EELS"
      elif data_descriptor.collection_dimension_count == 2 and data_descriptor.datum_dimension_count == 1:
          dm_metadata.setdefault("Meta Data", dict())["Format"] = "Spectrum image"
          dm_metadata.setdefault("Meta Data", dict())["Signal"] = "EELS"
      elif data_descriptor.datum_dimension_count == 1:
          dm_metadata.setdefault("Meta Data", dict())["Format"] = "Spectrum"
      if (1 if data_descriptor.is_sequence else 0) + data_descriptor.collection_dimension_count == 1 or needs_slice:
          if data_descriptor.is_sequence or is_sequence:
              dm_metadata.setdefault("Meta Data", dict())["IsSequence"] = True
          ret["ImageSourceList"] = [{"ClassName": "ImageSource:Summed", "Do Sum": True, "Id": [0], "ImageRef": 0, "LayerEnd": 0, "LayerStart": 0, "Summed Dimension": len(data.shape) - 1}]
          if needs_slice:
              ret["DocumentObjectList"][0]["AnnotationGroupList"] = [{"AnnotationType": 23, "Name": "SICursor", "Rectangle": (0, 0, 1, 1)}]
              ret["DocumentObjectList"][0]["ImageDisplayType"] = 1  # display as an image
      if modified:
          dm_metadata["Timestamp"] = modified.isoformat()
      if timezone:
          dm_metadata["Timezone"] = timezone
      if timezone_offset:
          dm_metadata["TimezoneOffset"] = timezone_offset
      ret["ImageList"][0]["ImageTags"] = dm_metadata
      ret["InImageMode"] = True
      parse_dm3.parse_dm_header(file, ret)