Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.3k
GH-34882: [Python] Binding for FixedShapeTensorType#34883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d69685aa6292f81bdba1d7c395b0d27d48f8e790b464e0cd048cbeb3d9ca165d3530afe2ce8baee5d25cf5a5c0c52f9e7ef9dee9eb171d00c0ec94cf2d9fe78b5dc93570f0863dbbe20223968add8fd311ebb829b2d0453File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3076,6 +3076,115 @@ cdef class ExtensionArray(Array): | ||
| return Array._to_pandas(self.storage, options, **kwargs) | ||
| class FixedShapeTensorArray(ExtensionArray): | ||
| """ | ||
| Concrete class for fixed shape tensor extension arrays. | ||
| Examples | ||
| -------- | ||
| Define the extension type for tensor array | ||
| >>> import pyarrow as pa | ||
| >>> tensor_type = pa.fixed_shape_tensor(pa.int32(), [2, 2]) | ||
| Create an extension array | ||
| >>> arr = [[1, 2, 3, 4], [10, 20, 30, 40], [100, 200, 300, 400]] | ||
| >>> storage = pa.array(arr, pa.list_(pa.int32(), 4)) | ||
| >>> pa.ExtensionArray.from_storage(tensor_type, storage) | ||
| <pyarrow.lib.FixedShapeTensorArray object at ...> | ||
| [ | ||
| [ | ||
| 1, | ||
| 2, | ||
| 3, | ||
| 4 | ||
| ], | ||
| [ | ||
| 10, | ||
| 20, | ||
| 30, | ||
| 40 | ||
| ], | ||
| [ | ||
| 100, | ||
| 200, | ||
| 300, | ||
| 400 | ||
| ] | ||
| ] | ||
| """ | ||
| def to_numpy_ndarray(self): | ||
| """ | ||
| Convert fixed shape tensor extension array to a numpy array (with dim+1). | ||
| Note: ``permutation`` should be trivial (``None`` or ``[0, 1, ..., len(shape)-1]``). | ||
| """ | ||
| if self.type.permutation is None or self.type.permutation == list(range(len(self.type.shape))): | ||
| np_flat = np.asarray(self.storage.values) | ||
| numpy_tensor = np_flat.reshape((len(self),) + tuple(self.type.shape)) | ||
| return numpy_tensor | ||
| else: | ||
| raise ValueError( | ||
| 'Only non-permuted tensors can be converted to numpy tensors.') | ||
| @staticmethod | ||
| def from_numpy_ndarray(obj): | ||
| """ | ||
| Convert numpy tensors (ndarrays) to a fixed shape tensor extension array. | ||
AlenkaF marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| The first dimension of ndarray will become the length of the fixed | ||
| shape tensor array. | ||
| Numpy array needs to be C-contiguous in memory | ||
| (``obj.flags["C_CONTIGUOUS"]==True``). | ||
| Parameters | ||
| ---------- | ||
| obj : numpy.ndarray | ||
| Examples | ||
| -------- | ||
| >>> import pyarrow as pa | ||
| >>> import numpy as np | ||
| >>> arr = np.array( | ||
| ... [[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]], | ||
| ... dtype=np.float32) | ||
| >>> pa.FixedShapeTensorArray.from_numpy_ndarray(arr) | ||
| <pyarrow.lib.FixedShapeTensorArray object at ...> | ||
| [ | ||
| [ | ||
| 1, | ||
| 2, | ||
| 3, | ||
| 4, | ||
| 5, | ||
| 6 | ||
| ], | ||
| [ | ||
| 1, | ||
| 2, | ||
| 3, | ||
| 4, | ||
| 5, | ||
| 6 | ||
| ] | ||
| ] | ||
| """ | ||
| if not obj.flags["C_CONTIGUOUS"]: | ||
| raise ValueError('The data in the numpy array need to be in a single, ' | ||
| 'C-style contiguous segment.') | ||
| arrow_type = from_numpy_dtype(obj.dtype) | ||
| shape = obj.shape[1:] | ||
| size = obj.size / obj.shape[0] | ||
| return ExtensionArray.from_storage( | ||
| fixed_shape_tensor(arrow_type, shape), | ||
| FixedSizeListArray.from_arrays(np.ravel(obj, order='C'), size) | ||
| ) | ||
| cdef dict _array_classes = { | ||
| _Type_NA: NullArray, | ||
| _Type_BOOL: BooleanArray, | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1144,6 +1144,102 @@ def test_cpp_extension_in_python(tmpdir): | ||
| assert reconstructed_array == array | ||
| def test_tensor_type(): | ||
| tensor_type = pa.fixed_shape_tensor(pa.int8(), [2, 3]) | ||
| assert tensor_type.extension_name == "arrow.fixed_shape_tensor" | ||
| assert tensor_type.storage_type == pa.list_(pa.int8(), 6) | ||
AlenkaF marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| assert tensor_type.shape == [2, 3] | ||
| assert tensor_type.dim_names is None | ||
| assert tensor_type.permutation is None | ||
| tensor_type = pa.fixed_shape_tensor(pa.float64(), [2, 2, 3], | ||
| permutation=[0, 2, 1]) | ||
| assert tensor_type.extension_name == "arrow.fixed_shape_tensor" | ||
| assert tensor_type.storage_type == pa.list_(pa.float64(), 12) | ||
| assert tensor_type.shape == [2, 2, 3] | ||
| assert tensor_type.dim_names is None | ||
| assert tensor_type.permutation == [0, 2, 1] | ||
| tensor_type = pa.fixed_shape_tensor(pa.bool_(), [2, 2, 3], | ||
| dim_names=['C', 'H', 'W']) | ||
| assert tensor_type.extension_name == "arrow.fixed_shape_tensor" | ||
| assert tensor_type.storage_type == pa.list_(pa.bool_(), 12) | ||
| assert tensor_type.shape == [2, 2, 3] | ||
| assert tensor_type.dim_names == ['C', 'H', 'W'] | ||
| assert tensor_type.permutation is None | ||
| def test_tensor_class_methods(): | ||
| tensor_type = pa.fixed_shape_tensor(pa.float32(), [2, 3]) | ||
| storage = pa.array([[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]], | ||
| pa.list_(pa.float32(), 6)) | ||
| arr = pa.ExtensionArray.from_storage(tensor_type, storage) | ||
| expected = np.array( | ||
| [[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]], dtype=np.float32) | ||
| result = arr.to_numpy_ndarray() | ||
| np.testing.assert_array_equal(result, expected) | ||
| arr = np.array( | ||
| [[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]], | ||
| dtype=np.float32, order="C") | ||
| tensor_array_from_numpy = pa.FixedShapeTensorArray.from_numpy_ndarray(arr) | ||
| assert isinstance(tensor_array_from_numpy.type, pa.FixedShapeTensorType) | ||
| assert tensor_array_from_numpy.type.value_type == pa.float32() | ||
| assert tensor_array_from_numpy.type.shape == [2, 3] | ||
AlenkaF marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. AlenkaF marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| arr = np.array( | ||
| [[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]], | ||
| dtype=np.float32, order="F") | ||
| with pytest.raises(ValueError, match="C-style contiguous segment"): | ||
| pa.FixedShapeTensorArray.from_numpy_ndarray(arr) | ||
| tensor_type = pa.fixed_shape_tensor(pa.int8(), [2, 2, 3], permutation=[0, 2, 1]) | ||
| storage = pa.array([[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]], pa.list_(pa.int8(), 12)) | ||
| arr = pa.ExtensionArray.from_storage(tensor_type, storage) | ||
| with pytest.raises(ValueError, match="non-permuted tensors"): | ||
| arr.to_numpy_ndarray() | ||
| @pytest.mark.parametrize("tensor_type", ( | ||
| pa.fixed_shape_tensor(pa.int8(), [2, 2, 3]), | ||
| pa.fixed_shape_tensor(pa.int8(), [2, 2, 3], permutation=[0, 2, 1]), | ||
| pa.fixed_shape_tensor(pa.int8(), [2, 2, 3], dim_names=['C', 'H', 'W']) | ||
| )) | ||
| def test_tensor_type_ipc(tensor_type): | ||
| storage = pa.array([[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]], pa.list_(pa.int8(), 12)) | ||
| arr = pa.ExtensionArray.from_storage(tensor_type, storage) | ||
| batch = pa.RecordBatch.from_arrays([arr], ["ext"]) | ||
| # check the built array has exactly the expected clss | ||
| tensor_class = tensor_type.__arrow_ext_class__() | ||
| assert type(arr) == tensor_class | ||
| buf = ipc_write_batch(batch) | ||
| del batch | ||
| batch = ipc_read_batch(buf) | ||
| result = batch.column(0) | ||
| # check the deserialized array class is the expected one | ||
| assert type(result) == tensor_class | ||
| assert result.type.extension_name == "arrow.fixed_shape_tensor" | ||
| assert arr.storage.to_pylist() == [[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]] | ||
| # we get back an actual TensorType | ||
| assert isinstance(result.type, pa.FixedShapeTensorType) | ||
| assert result.type.value_type == pa.int8() | ||
| assert result.type.shape == [2, 2, 3] | ||
| def test_tensor_type_equality(): | ||
| tensor_type = pa.fixed_shape_tensor(pa.int8(), [2, 2, 3]) | ||
| assert tensor_type.extension_name == "arrow.fixed_shape_tensor" | ||
| tensor_type2 = pa.fixed_shape_tensor(pa.int8(), [2, 2, 3]) | ||
| tensor_type3 = pa.fixed_shape_tensor(pa.uint8(), [2, 2, 3]) | ||
| assert tensor_type == tensor_type2 | ||
| assert not tensor_type == tensor_type3 | ||
| @pytest.mark.pandas | ||
| def test_extension_to_pandas_storage_type(registered_period_type): | ||
| period_type, _ = registered_period_type | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.