def __init__(
        self, tfssa, inputs=None, outputs=None, opset_version=None, use_default_fp16_io=False
    ):
        """
        tfssa: TensorFlow IR.
        inputs: list of TensorType or ImageType, optional, defaults to None.
        outputs: list[ct.InputType] or None
            list of either ct.TensorTypes or ct.ImageTypes (both of which are child classes of InputType)
            This is the value of the "outputs" argument, passed on by the user in "coremltools.convert" API.
        opset_version: An int represents the Core ML opset version.
        use_default_fp16_io (optional): bool. Defaults to False.
            When minimum_deployment_target set >= ct.target.iOS16 (the same as ct.target.macOS13),
            and the compute precision set to fp16, this flag is True.
            When True, fp32 i/o defaults to fp16.
        """
        self.tfssa = tfssa
        self.global_type = {}
        self.inputs = None
        self.main_output_types = outputs
        self.opset_version = _target(opset_version) if opset_version is not None else None
        self.use_default_fp16_io = use_default_fp16_io
        output_names = get_output_names(outputs)

        main_func = tfssa.functions["main"]
        graph = main_func.graph

        # Get inputs dtype and shape defined in the tf graph
        tf_placeholder_names = [n for n in graph if graph[n].op == "Placeholder"]
        tf_input_dtype = {}
        tf_input_shape = {}
        image_input_names = []
        inputs_with_defined_shape = []

        if inputs is not None:
            # Special case: if there's only 1 input and 1 placeholder, we match them.
            if len(tf_placeholder_names) == 1 and len(inputs) == 1:
                if inputs[0].name is None:
                    inputs[0].name = tf_placeholder_names[0]
            for val in inputs:
                if isinstance(val, ImageType):
                    image_input_names.append(val.name)
                if val.shape is not None:
                    inputs_with_defined_shape.append(val.name)

        for inp in main_func.inputs:
            node = graph[inp]

            # Parse dtype from the tf graph
            dtype = node.attr["dtype"]
            if use_default_fp16_io and dtype == types.fp32 and inp not in image_input_names:
                dtype = types.fp16

            tf_input_dtype[inp] = dtype

            # Parse shape from the tf graph
            if inp not in inputs_with_defined_shape:
                shape = self._get_placeholder_shape_from_tf_graph(tfgraph=graph, name=inp)
                shape = [get_new_symbol() if s is None or s == -1 else s for s in shape]
                shape = _get_shaping_class(shape)
                tf_input_shape[inp] = shape

        # Filter the inputs to only Placeholder names
        missing_placeholder_names = []
        if inputs is not None:
            # Check inputs format
            if not isinstance(inputs, (list, tuple)):
                raise ValueError(
                    "Type of inputs should be list or tuple, got {} instead.".format(
                        type(inputs)
                    )
                )
            if not all(isinstance(i, InputType) for i in inputs):
                raise ValueError(
                    "Type of inputs should be list or tuple of TensorType or ImageType, got {} instead.".format(
                        [type(i) for i in inputs]
                    )
                )

            for inp in inputs:
                # Check inputs existence
                if inp.name is None:
                    raise ValueError(
                        "Multiple inputs are found in graph, but no input name was provided"
                    )
                if inp.name not in tf_placeholder_names:
                    raise ValueError(
                        "Input ({}) provided is not found in given tensorflow graph. Placeholders in graph are: {}".format(
                            inp.name, tf_placeholder_names
                        )
                    )
                # We fill in shapes and dtypes for user-specified input that doesn't set
                if inp.shape is None:
                    inp.shape = tf_input_shape[inp.name]
                if inp.dtype is None:
                    inp.dtype = tf_input_dtype[inp.name]

            # Extract placeholders that users didn't specify.
            user_input_names = [inp.name for inp in inputs]
            for name in tf_placeholder_names:
                if name not in user_input_names:
                    missing_placeholder_names.append(name)
        else:
            inputs = []
            missing_placeholder_names = tf_placeholder_names

        # name -> (shape, mil_type) mapping. shape has type list[int]
        added_inputs = {}
        for inp in main_func.inputs:
            if inp not in missing_placeholder_names:
                continue
            shape, dtype = tf_input_shape[inp], tf_input_dtype[inp]
            inputs.append(TensorType(name=inp, shape=shape, dtype=dtype))
            added_inputs[inp] = (shape, dtype)

        if len(added_inputs) > 0:
            logger.info(
                "Adding Input not specified by users: '{}'".format(
                    added_inputs)
            )

        for idx, inp in enumerate(inputs):
            # We set the default image format in TF as NHWC, since NHWC is used
            # for TF unless GPU is specified as device.
            if isinstance(inp, ImageType) and inputs[idx].channel_first is None:
                inputs[idx].channel_first = False
        self.inputs = tuple(inputs)

        for inputtype in self.inputs:
            if not isinstance(inputtype.shape, InputShape):
                continue
            if any(isinstance(s, RangeDim) for s in inputtype.shape.shape):
                continue
            if inputtype.name not in graph:
                raise ValueError(
                    f"The input {inputtype.name} provided is not in graph."
                )
            node = graph[inputtype.name]
            shape = [-1 if is_symbolic(s) else s for s in inputtype.shape.shape]
            node.attr["_output_shapes"] = [shape]  # list of length 1

        # infer outputs if not provided
        self._validate_outputs(tfssa, output_names)
        output_names = main_func.outputs if output_names is None else output_names
        output_names = output_names if isinstance(output_names, (tuple, list)) else [output_names]
        output_names = [x if isinstance(x, str) else x.name for x in output_names]
        self.output_names = output_names

        # We would like a stack so that we run conversion sequentially.
        self.graph_stack = self._get_stack(tfssa, root="main")
        self.context = TranscriptionContext()

        tfssa: TensorFlow IR.
        inputs: list of TensorType or ImageType, optional, defaults to None.
        outputs: list[ct.InputType] or None
            list of either ct.TensorTypes or ct.ImageTypes (both of which are child classes of InputType)
            This is the value of the "outputs" argument, passed on by the user in "coremltools.convert" API.
        opset_version: An int represents the Core ML opset version.
        use_default_fp16_io (optional): bool. Defaults to False.
            When minimum_deployment_target set >= ct.target.iOS16 (the same as ct.target.macOS13),
            and the compute precision set to fp16, this flag is True.
            When True, fp32 i/o defaults to fp16.
        
coremltools/converters/mil/frontend/tensorflow/converter.py