class_docstr: Update the dtypes of output vars of the main block to match the dtypes
  provided in ``prog.main_output_types``, which in turn is populated by the
  ``outputs`` argument provided by the user in the ``coremltools.convert()`` API.
  This graph pass assumes that the list of outputs in ``prog.main_output_types`` (if not ``None``),
  are in the same order as the output vars.
code: def apply(self, prog):
      user_provided_output_types = prog.main_output_types
      main_func = prog.functions["main"]
      output_vars = main_func.outputs
      input_vars = list(main_func.inputs.values())
      if (
          user_provided_output_types is None
          or len(user_provided_output_types) == 0
      ):
          return
      if len(output_vars) != len(user_provided_output_types):
          msg = (
              "Number of outputs provided by the user, which is {}, "
              "does not match the number of outputs generated by the model, which is {}"
          )
          raise ValueError(
              msg.format(
                  len(user_provided_output_types), len(output_vars)
              )
          )
  
      new_outputs = []
      for i, output_type in enumerate(user_provided_output_types):
          required_output_dtype = output_type.dtype
          output_var = output_vars[i]
          if (
              required_output_dtype is None
              or not (
                  types.is_tensor(output_var.sym_type)
                  or types.is_scalar(output_var.sym_type)
              )
              or required_output_dtype == output_var.dtype
          ):
              # no need to update the output var's dtype in this case
              new_outputs.append(output_var)
          elif output_var in input_vars:
              # Here is this rare special case, that the program input is also an output
              # For this case, we don't do anything, and throw a warning message
              new_outputs.append(output_var)
              logger.warning(
                  f"Output var '{output_var.name}' is also an input var, hence the "
                  f"dtype cannot be changed: output var '{output_var.name}' remains "
                  f"dtype {types.builtin_to_string(output_var.dtype)}"
              )
          else:
              output_var_name = output_var.name
              output_var.set_name(
                  output_var_name
                  + "_type_"
                  + types.builtin_to_string(output_var.dtype)
              )
              with main_func:
                  output_var = mb.cast(
                      x=output_var,
                      dtype=types.builtin_to_string(
                          required_output_dtype
                      ),
                  )
                  output_var.set_name(output_var_name)
              new_outputs.append(output_var)
  
      main_func.set_outputs(new_outputs)
  
imports: import re
  import warnings
  from collections import OrderedDict
  from coremltools import _logger
  from coremltools.converters.mil.input_types import EnumeratedShapes, ImageType, Shape
  from coremltools.converters.mil.mil import Builder
  from coremltools.converters.mil.mil import Function, types
  from coremltools.converters.mil.mil.passes.graph_pass import AbstractGraphPass
  from coremltools.converters.mil.mil.passes.pass_registry import register_pass
label_desc: Use image node_id 1 for calling the update_output_dtypes obj's underlying member method code with example usage: obj.apply(prog) without return types