# Copyright (c) 2023, Apple Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-3-clause license that can be
# found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
from typing import List
from coremltools.converters.mil.mil import types
from coremltools.converters.mil.mil.input_type import InputSpec, TensorInputType, TupleInputType
from coremltools.converters.mil.mil.ops.defs._op_reqs import register_op
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
expand_dims as _expand_dims_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
reshape as _reshape_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
reverse as _reverse_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
reverse_sequence as _reverse_sequence_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
slice_by_index as _slice_by_index_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
slice_by_size as _slice_by_size_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
sliding_windows as _sliding_windows_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
squeeze as _squeeze_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS15.tensor_transformation import (
transpose as _transpose_iOS15,
)
from coremltools.converters.mil.mil.ops.defs.iOS16.tensor_transformation import (
reshape_like as _reshape_like_iOS16,
)
from coremltools.converters.mil.mil.ops.defs.iOS17 import _IOS17_TARGET
[docs]
@register_op(opset_version=_IOS17_TARGET)
class reshape(_reshape_iOS15):
"""
Return a tensor that has the same values as ``x`` with shape ``shape``.
``shape`` must have the same volume (number of elements) as ``x``.
The major differences between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.reshape` are as follows:
- When the ``shape`` contains ``0``,
the restriction about ``K == rank(x)`` is no longer enforced. Each ``0`` in ``shape`` will match the
corresponding dimension in ``x.shape``, counting from the rightmost element. So ``shape[i]``
matches ``input[j]`` if ``length(shape)-i == rank(input)-j``. If a ``0`` is out of range, assign ``1``
(equivalent to ``expand_dims`` for ``x.shape``).
More specifically, when ``x.shape`` is ``[2, 50]`` and ``shape`` is ``[1, 0, -1, 0]``, it will error out
in iOS 15 or iOS 16 because ``x`` has rank ``2`` while the ``len`` of ``shape`` is ``4``. In iOS 17, the result will
have ``shape`` ``[1, 1, 2, 50]``, because the rightmost ``0`` will be changed to the rightmost dim of
``x.shape``, which is ``50``. There is no other ``0`` that has a corresponding dim in ``x.shape``, so it is set
as ``1``. Finally, the ``-1`` is calculated based on knowing dimensions that produce ``2``.
- Support more data types, including int8, uint8, int16, uint16 for ``x`` and int8, int16 for
``shape``.
Parameters
----------
x: tensor<\*?, T> (Required)
* An ``n-D`` tensor or a scalar.
* If ``x`` has a fixed rank (and possibly contains symbolic dimension),
``shape`` may contain elements that are not positive integers (see below).
* If ``x`` has a variadic rank, ``shape`` can only contain positive integers.
shape: tensor<[K], U> (Required)
A 1-D tensor, with elements from the following:
* Positive integers.
* Symbols: All but one symbol in ``shape`` must be present in ``x.shape``.
The new symbol that is not present in ``x.shape`` represents a dimension
such that the total size remains constant. Symbol is illegal
if ``x`` has a variadic rank.
* ``-1``: ``-1`` introduces a new symbol (see Symbols). Therefore, ``-1`` is
allowed if all symbols in the ``shape`` appear in ``x.shape``. ``-1`` is illegal
if ``x`` has a variadic rank.
* ``0``: It will match the corresponding dimension in ``x.shape``. See the previous
description of different behaviors with iOS 17.
Returns
-------
tensor<\*?, T>
* Tensor with shape determined by the input shape.
Attributes
----------
T: fp16, fp32, int8, uint8, int16, uint16, int32, bool
U: int8, int16, int32
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
shape=TensorInputType(type_domain="U"),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.uint8,
types.int16,
types.uint16,
types.int32,
types.bool,
),
"U": (types.int8, types.int16, types.int32),
}
@staticmethod
def replace_zeros_in_shape(from_shape: List[int], to_shape: List[int]) -> List[int]:
"""
Replaces 0s in `to_shape` by the corresponding dims in `from_shape`.
Overrides IOS15's method to demonstrate IOS17's different behaviours.
"""
if to_shape.count(0):
# To do right alignment, we reverse the input and do left alignment instead.
from_shape_reversed = from_shape[::-1]
to_shape_reversed = to_shape[::-1]
for idx, to_element in enumerate(to_shape_reversed):
if to_element == 0:
to_shape_reversed[idx] = (
from_shape_reversed[idx] if idx < len(from_shape_reversed) else 1
)
# Reverse the result back to make the right alignment.
to_shape = to_shape_reversed[::-1]
return to_shape
[docs]
@register_op(opset_version=_IOS17_TARGET)
class reshape_like(_reshape_like_iOS16):
"""
Reshape a tensor to an output shape specified by some or all dimensions of a tuple of reference tensors ``ref_tensors``.
The major difference between this version and the iOS 15 :py:class:`~.iOS16.tensor_transformation.reshape_like`
is that input ``x`` and ``ref_tensors`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<\*?, T> (Required)
* The input tensor to be reshaped.
ref_tensors: Tuple[tensor<\*?, R>] (Required)
* A tuple of tensors that define the output shape.
begins: Tuple[const<int32>] (Required)
* A tuple of integers specifying the begin index into the shape vector of the corresponding ``ref_tensor``.
ends: Tuple[const<int32>] (Required)
* A tuple of integers specifying the end index into the shape vector of the corresponding ``ref_tensor``.
end_masks: Tuple[const<bool>] (Required)
* If ``True``, select all axes from the begin index until the end of the corresponding ``ref_tensor``, as in
``ref_tensors[i].shape[begins[i]:]``.
Returns
-------
tensor<\*?, T>
* Same type as input tensor ``x``.
* Output shape is computed by ``ref_tensors``, ``begins``, ``ends``, and ``end_masks``.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
R: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
ref_tensors=TupleInputType(),
begins=TupleInputType(),
ends=TupleInputType(),
end_masks=TupleInputType(),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class expand_dims(_expand_dims_iOS15):
"""
Insert a single-dimension in a 1-D or higher tensor at each axis in axes.
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.expand_dims`
is that input ``x`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<\*?, T> (Required)
* Scalar or tensor.
axes: const tensor<[K], int32> Required
* ``K`` is the number of dimensions expanded.
* Insert single dimension at dimension index at each axes.
* Negative value to index from the end. ``-d-1 <= axis <= d``
where ``d`` is the rank of ``x``.
Returns
-------
tensor<\*(rank(x)+K), T>
* Same type as the input ``x`` with rank ``rank(x)+K``.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
axes=TensorInputType(const=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class squeeze(_squeeze_iOS15):
"""
Remove single-dimension dimensions in a 1-D or higher tensor.
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.squeeze`
is that input ``x`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<\*?,T> (Required)
* Must be at least 1-D.
axes: const<K,int32> (Optional)
* Axes to squeeze out.
* Default to remove all single-dimensions.
Returns
-------
tensor<\*(rank(x)-K),T>
* Tensor with same type as input ``x`` and rank ``rank(x)-K``.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
axes=TensorInputType(const=True, optional=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class reverse(_reverse_iOS15):
"""
Reverse the order of the input tensor ``x`` along specified ``axes`` (dimensions).
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.reverse`
is that input ``x`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<\*?, T> (Required)
* Input tensor.
axes: const<D, int32> (Optional)
* Dimension(s) to reverse. Each axis must be in the range ``[-rank(x), rank(x))``.
* Defaults to None (reverse on all dimensions).
Returns
-------
tensor<\*?, T>
* Same type and shape as the input tensor.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
axes=TensorInputType(const=True, optional=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class reverse_sequence(_reverse_sequence_iOS15):
"""
Reverse variable length slices for specified axes / dimensions of the input
tensor. This op first slices input tensor along the ``batch_axis`` dimension, then
partially reverses the elements along the ``seq_axis`` for the first ``lengths[i]``
elements.
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.reverse_sequence`
is that input supports more data types:
- ``x`` additionally supports int8, uint8, int16, uint16
- ``lengths`` additionally supports int8, int16
Parameters
----------
x: tensor<\*?, T> (Required)
* Input tensor.
lengths: tensor<L, U> (Required)
* 1-dimensional tensor of length ``x.shape[batch_axis]`` specifying the length
of the sequence to reverse.
* Values must be in range ``[0, x.shape[seq_axis]]``.
seq_axis: const<int32> (Optional)
* The dimension to reverse.
* Defaults to ``0``.
batch_axis: const<int32> (Optional)
* Dimension for slicing.
* Defaults to ``0``.
Returns
-------
tensor<\*?, T>
* Same type and shape as the input tensor.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
U: int8, int16, int32
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
lengths=TensorInputType(type_domain="U"),
seq_axis=TensorInputType(const=True, optional=True, type_domain=types.int32),
batch_axis=TensorInputType(const=True, optional=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
"U": (types.int8, types.int16, types.int32),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class sliding_windows(_sliding_windows_iOS15):
"""
Return a tensor containing all windows of ``size``, separated by stride along the
given ``axis``.
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.sliding_windows`
is that input ``x`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<[\*d0, d_axis, *dn], T>
* Input tensor.
axis: const<int32>
* Axis to perform the operation.
size: const<int32>
* Number of elements in the sliding window.
stride: const<int32> Optional
* Default to ``1``.
* The stride of the input elements in the sliding window.
Returns
-------
tensor<[\*d0, d_axis - size // stride + 1, size, \*dn], T>
* The output will be a tensor of rank ``N+1`` where ``N`` is the input tensor
rank.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
axis=TensorInputType(const=True, type_domain=types.int32),
size=TensorInputType(const=True, type_domain=types.int32),
stride=TensorInputType(const=True, optional=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class transpose(_transpose_iOS15):
"""
Permute tensor ``x`` dimensions according to ``perm``.
The major difference between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.transpose`
is that input ``x`` supports more data types: int8, uint8, int16, uint16.
Parameters
----------
x: tensor<\*?, T> (Required)
* Must be at least 1-D. ``x`` may have a symbolic shape.
perm: const<[rank(x)], i32> (Required)
* Permutation order. -rank(x) <= perm[I] < rank(x) for all perm entries.
Returns
-------
tensor<\*?,T>
* Tensor with same rank and type as ``x``.
Attributes
----------
T: fp16, fp32, int8, int16, int32, uint8, uint16, bool
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
perm=TensorInputType(const=True, type_domain=types.int32),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class slice_by_index(_slice_by_index_iOS15):
"""
Method for numpy style indexing and slicing.
With a tensor ``x``, this method achieves the following:
``result = x[begin[0]: end[0]: stride[0], begin[1]: end[1]: stride[1], ...]``
The differences between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.slice_by_index`
is that additional data types are supported for ``x``, ``begin``, ``end``, and ``stride``.
See Parameters and Attributes sections for details.
Parameters
----------
x: tensor<*?, T> (Required)
* Input tensor
begin: tensor<[rank(x)], U> (Required)
* Starting index for the dimension of slicing.
end: tensor<[rank(x)], U> (Required)
* Ending index for the dimension of slicing.
stride: tensor<[rank(x)], U> (Optional)
* Default is all ``1``.
* Stride for the dimension of slicing.
begin_mask: tensor<[rank(x)], bool> (Optional)
* Default to all ``False``.
* If ``begin_mask[i]==True``, ignores ``begin[i]``, and set ``begin[i]`` to ``0``.
end_mask: tensor<[rank(x)], bool> (Optional)
* Default to all ``False``.
* If ``end_mask[i]==True``, ignores ``end[i]``, and set ``end[i]`` to ``x.shape[i]``.
squeeze_mask: tensor<[rank(x)], bool> (Optional)
* Default to all ``False``.
* If ``squeeze_mask[i]==True``, ignores ``end[i]``, and do the pure index at ``begin[i]``.
Returns
-------
tensor<\*?, T>
- Scalar or tensor.
Attributes
----------
T: bool, fp16, fp32, int8, int16, int32, uint8, uint16
U: int8, int16, int32
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
begin=TensorInputType(type_domain="U"),
end=TensorInputType(type_domain="U"),
stride=TensorInputType(const=True, optional=True, type_domain="U"),
begin_mask=TensorInputType(const=True, optional=True, type_domain=types.bool),
end_mask=TensorInputType(const=True, optional=True, type_domain=types.bool),
squeeze_mask=TensorInputType(const=True, optional=True, type_domain=types.bool),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
"U": (types.int8, types.int16, types.int32),
}
[docs]
@register_op(opset_version=_IOS17_TARGET)
class slice_by_size(_slice_by_size_iOS15):
"""
Slice input tensor starting from the given ``begin`` index and by
the amount specified by the ``size`` input, for each dimension.
The differences between this version and the iOS 15 :py:class:`~.iOS15.tensor_transformation.slice_by_size`
is that additional data types are supported for ``x``, ``begin``, and ``size``.
See Parameters and Attributes sections for details.
Parameters
----------
x: tensor<*?, T> (Required)
* Input tensor.
begin: tensor<[rank(x)], U> Required
* The begin index for slice.
size: tensor<[rank(x)], U> Required
* The size that is to be sliced. If ``size`` is ``-1``,
all the remaining elements starting with "begin" are sliced.
Returns
-------
tensor<\*?, T>
* Scalar or tensor.
Attributes
----------
T: bool, fp16, fp32, int8, int16, int32, uint8, uint16
U: int8, int16, int32
"""
input_spec = InputSpec(
x=TensorInputType(type_domain="T"),
begin=TensorInputType(type_domain="U"),
size=TensorInputType(type_domain="U"),
)
type_domains = {
"T": (
types.fp16,
types.fp32,
types.int8,
types.int16,
types.int32,
types.uint8,
types.uint16,
types.bool,
),
"U": (types.int8, types.int16, types.int32),
}