def get_func_input(self, func: mil.Function) -> List["proto.Model_pb2.FeatureDescription"]:
        """
        Utils to get function input feature description.
        """
        input_types = func.input_types

        input_features = []
        image_input_names = {}  # these are the model inputs marked as image by the user
        input_shape_map = {}

        for input_type in input_types:
            if isinstance(input_type, mil_input_types.ImageType):
                image_input_names[input_type.name] = input_type
                # error checking for input(s) marked as images
                if input_type.name not in list(func.inputs.keys()):
                    raise ValueError(
                        f"Provided image input '{input_type.name}' is not one of the inputs of the MIL program"
                    )
            if input_type.name is None:
                raise ValueError(
                    'Fail to auto-determine the input name. Please specify the "name" '
                    'parameter when use "inputs" in ct.convert().'
                )
            input_shape_map[input_type.name] = input_type

        for name, var in func.inputs.items():
            input_feature_type = proto.FeatureTypes_pb2.FeatureType()
            is_input_shape_symbolic = False

            # error checking for input(s) marked as images
            # an image input must be of type tensor in program proto
            # (since an image type does not exist in MIL program)
            if name in image_input_names and not types.is_tensor(var.sym_type):
                raise ValueError(
                    "For the image input, '{}', its type in the MIL program must be tensor. "
                    "Instead it is {}.".format(name, var.sym_type.__type_info__())
                )

            if types.is_tensor(var.sym_type):
                shape = var.sym_type.get_shape()
                if any_variadic(shape):
                    raise ValueError("Variable rank model inputs are not supported!")
                if any_symbolic(shape):
                    is_input_shape_symbolic = True
                    # We extract the default input shape given by user first
                    if name in input_shape_map:
                        shape = input_shape_map[name].shape.default
                    else:
                        logger.warning(
                            "Input shape not fully specified by enumerated shapes or range dim! 1 will be used for dimension not specified instead."
                        )
                    # If no input shape is provided (ex. auto conversion of -1 in Tensorflow)
                    shape = [1 if is_symbolic(d) else d for d in shape]

                if name not in image_input_names:
                    # make a feature type of Type "multiArrayType"
                    array_type = proto.FeatureTypes_pb2.ArrayFeatureType(
                        shape=shape, dataType=cast_to_framework_io_dtype(var, False)
                    )
                    input_feature_type.multiArrayType.CopyFrom(array_type)
                else:
                    # make a feature type of Type "imageType"
                    input_type = image_input_names[name]
                    _validate_image_input_output_shapes(
                        input_type.color_layout, shape, name, is_input=True
                    )
                    if not input_type.channel_first:
                        raise ValueError(
                            "Image input, '{}', must be in the channel_first format".format(name)
                        )
                    clr_space = _get_colorspace_enum(input_type.color_layout)
                    image_type = proto.FeatureTypes_pb2.ImageFeatureType(
                        width=shape[-1], height=shape[-2], colorSpace=clr_space
                    )
                    input_feature_type.imageType.CopyFrom(image_type)

                input_features.append(
                    proto.Model_pb2.FeatureDescription(name=name, type=input_feature_type)
                )
            elif types.is_scalar(var.sym_type):
                array_type = proto.FeatureTypes_pb2.ArrayFeatureType(
                    shape=[1], dataType=cast_to_framework_io_dtype(var, False)
                )
                input_feature_type.multiArrayType.CopyFrom(array_type)
                input_features.append(
                    proto.Model_pb2.FeatureDescription(name=var.name, type=input_feature_type)
                )
            elif types.is_state(var.sym_type):
                # shape for state input cannot be symbolic
                shape = var.sym_type.wrapped_type().get_shape()
                if any_variadic(shape):
                    raise ValueError("Variable rank model states are not supported!")
                if any_symbolic(shape):
                    raise ValueError("Flexible shape model states are not supported!")

                # Core ML only support fp16 for state
                if not var.dtype == types.fp16:
                    raise ValueError(
                        f"State only support fp16 dtype. Got input var {var.name} with dtype {types.builtin_to_string(var.dtype)}."
                    )

                # create the input feature type
                array_type = proto.FeatureTypes_pb2.ArrayFeatureType(
                    shape=shape, dataType=cast_to_framework_io_dtype(var, False)
                )

                state_feature_type = proto.FeatureTypes_pb2.StateFeatureType()
                state_feature_type.arrayType.CopyFrom(array_type)

                input_feature_type = proto.FeatureTypes_pb2.FeatureType()
                input_feature_type.stateType.CopyFrom(state_feature_type)

                # append feature to the input features list
                input_features.append(
                    proto.Model_pb2.FeatureDescription(name=var.name, type=input_feature_type)
                )
            else:
                raise NotImplementedError(f"Unsupported input type {var.sym_type}.")

            if not is_input_shape_symbolic:
                continue

            # Set symbolic shapes
            default_lower_bound = 1
            default_upper_bound = default_lower_bound + 1 if self.convert_to == "mlprogram" else -1
            default_bound_used = False
            input_type = input_shape_map.get(name, None)

            if isinstance(input_type, mil_input_types.ImageType):
                if isinstance(input_type.shape, mil_input_types.EnumeratedShapes):
                    enumerated_shapes = []
                    for s in input_type.shape.shapes:
                        enumerated_shapes.append(
                            NeuralNetworkImageSize(height=s.shape[-2], width=s.shape[-1])
                        )
                    flexible_shape_utils._add_enumerated_image_sizes_for_feature(
                        input_features[-1], sizes=enumerated_shapes
                    )
                else:
                    img_range = NeuralNetworkImageSizeRange()
                    H = input_type.shape.shape[-2]
                    W = input_type.shape.shape[-1]

                    if isinstance(H, mil_input_types.RangeDim):
                        img_range.add_height_range((H.lower_bound, H.upper_bound))
                    elif is_symbolic(H):
                        img_range.add_height_range((default_lower_bound, default_upper_bound))
                        default_bound_used = True
                    else:
                        img_range.add_height_range((H, H))
                    if isinstance(W, mil_input_types.RangeDim):
                        img_range.add_width_range((W.lower_bound, W.upper_bound))
                    elif is_symbolic(W):
                        img_range.add_width_range((default_lower_bound, default_upper_bound))
                        default_bound_used = True
                    else:
                        img_range.add_width_range((W, W))

                    flexible_shape_utils._update_image_size_range_for_feature(
                        input_features[-1], img_range
                    )
            elif isinstance(input_type, mil_input_types.TensorType):
                if isinstance(input_type.shape, mil_input_types.EnumeratedShapes):
                    flexible_shape_utils._add_multiarray_ndshape_enumeration_for_feature(
                        input_features[-1], [tuple(s.shape) for s in input_type.shape.shapes]
                    )
                else:
                    lb = []
                    ub = []
                    for s in input_type.shape.shape:
                        if isinstance(s, mil_input_types.RangeDim):
                            lb.append(s.lower_bound)
                            ub.append(s.upper_bound)
                        elif is_symbolic(s):
                            lb.append(default_lower_bound)
                            ub.append(default_upper_bound)
                            default_bound_used = True
                        else:
                            lb.append(s)
                            ub.append(s)
                    flexible_shape_utils._set_multiarray_ndshape_range_for_feature(
                        input_features[-1], lower_bounds=lb, upper_bounds=ub
                    )
            elif input_type is None:
                sym_type = func.inputs[name].sym_type
                lb = []
                ub = []
                for s in sym_type.get_shape():
                    if is_symbolic(s):
                        lb.append(default_lower_bound)
                        ub.append(default_upper_bound)
                        default_bound_used = True
                    else:
                        lb.append(s)
                        ub.append(s)
                flexible_shape_utils._set_multiarray_ndshape_range_for_feature(
                    input_features[-1], lower_bounds=lb, upper_bounds=ub
                )

            if default_bound_used and self.convert_to == "mlprogram":
                warnings.warn(
                    "Some dimensions in the input shape are unknown, hence they are set to flexible ranges "
                    f"with lower bound and default value = {default_lower_bound}, and upper bound = "
                    f"{default_upper_bound}. To set different values for the default shape and upper bound, "
                    "please use the ct.mil_input_types.RangeDim() method as described here: "
                    "https://coremltools.readme.io/docs/flexible-inputs#set-the-range-for-each-dimension.",
                    UserWarning,
                )
                convert_from = self.convert_from
                if convert_from is not None and convert_from.startswith("tensorflow"):
                    warnings.warn(
                        'There is "None" dim in TF input placeholder. Please consider specifying '
                        'input shapes by using the "inputs" param in ct.convert().'
                    )
        return input_features

        Utils to get function input feature description.
        
coremltools/converters/mil/backend/mil/load.py