Repository: docarray/docarray

Issue:
Document Array of `Image`s cannot be stacked twice when tensor_type=NdArray
**Describe the bug**
Document Array of Images cannot be stacked twice

**To Reproduce**
script.py
```python
from docarray import DocumentArray
from docarray.documents import Image

BATCH_SIZE: int = 1024
da_image: DocumentArray[Image] = DocumentArray[Image]([Image() for _ in range(BATCH_SIZE)])

print("Stacking once")
da_image.stack()
print("Stacking twice")
da_image.stack()
print("Done!")
```

Output
```console
Stacking once
Stacking twice
Traceback (most recent call last):
  File "rabbit.py", line 12, in <module>
    da_image.stack()
  File "/home/jackmin/Documents/da-testing/docarray/docarray/array/array.py", line 204, in stack
    return DocumentArrayStacked.__class_getitem__(self.document_type)(self)
  File "/home/jackmin/Documents/da-testing/docarray/docarray/array/array_stacked.py", line 66, in __init__
    self.from_document_array(docs)
  File "/home/jackmin/Documents/da-testing/docarray/docarray/array/array_stacked.py", line 71, in from_document_array
    self._columns = self._create_columns(docs, tensor_type=self.tensor_type)
  File "/home/jackmin/Documents/da-testing/docarray/docarray/array/array_stacked.py", line 150, in _create_columns
    type_.get_comp_backend().empty(
  File "/home/jackmin/Documents/da-testing/docarray/docarray/computation/numpy_backend.py", line 75, in empty
    raise NotImplementedError('Numpy does not support devices (GPU).')
NotImplementedError: Numpy does not support devices (GPU).
```

**Expected behavior**
```console
Stacking once
Stacking twice
Done!
```



Discussion:

Resolving patch:
diff --git a/docarray/array/array_stacked.py b/docarray/array/array_stacked.py
--- a/docarray/array/array_stacked.py
+++ b/docarray/array/array_stacked.py
@@ -171,7 +171,18 @@ def _create_columns(
                         val = tensor_type.get_comp_backend().none_value()
 
                     cast(AbstractTensor, tensor_columns[field])[i] = val
-                    setattr(doc, field, tensor_columns[field][i])
+
+                    # If the stacked tensor is rank 1, the individual tensors are
+                    # rank 0 (scalar)
+                    # This is problematic because indexing a rank 1 tensor in numpy
+                    # returns a value instead of a tensor
+                    # We thus chose to convert the individual rank 0 tensors to rank 1
+                    # This does mean that stacking rank 0 tensors will transform them
+                    # to rank 1
+                    if tensor_columns[field].ndim == 1:
+                        setattr(doc, field, tensor_columns[field][i : i + 1])
+                    else:
+                        setattr(doc, field, tensor_columns[field][i])
                     del val
 
             elif issubclass(type_, BaseDocument):
diff --git a/docarray/typing/tensor/abstract_tensor.py b/docarray/typing/tensor/abstract_tensor.py
--- a/docarray/typing/tensor/abstract_tensor.py
+++ b/docarray/typing/tensor/abstract_tensor.py
@@ -265,3 +265,9 @@ def _docarray_to_json_compatible(self):
         :return: a representation of the tensor compatible with orjson
         """
         ...
+
+    @property
+    @abc.abstractmethod
+    def ndim(self) -> int:
+        """The number of dimensions / rank of this tensor."""
+        ...


Test patch:
diff --git a/tests/units/array/test_array_stacked.py b/tests/units/array/test_array_stacked.py
--- a/tests/units/array/test_array_stacked.py
+++ b/tests/units/array/test_array_stacked.py
@@ -365,3 +365,86 @@ class MyDoc(BaseDocument):
     da = da.stack()
     assert da[0].tensor.dtype == np.int32
     assert da.tensor.dtype == np.int32
+
+
+def test_np_scalar():
+    class MyDoc(BaseDocument):
+        scalar: NdArray
+
+    da = DocumentArray[MyDoc](
+        [MyDoc(scalar=np.array(2.0)) for _ in range(3)], tensor_type=NdArray
+    )
+    assert all(doc.scalar.ndim == 0 for doc in da)
+    assert all(doc.scalar == 2.0 for doc in da)
+
+    stacked_da = da.stack()
+    assert type(stacked_da.scalar) == NdArray
+
+    assert all(type(doc.scalar) == NdArray for doc in da)
+    assert all(doc.scalar.ndim == 1 for doc in da)
+    assert all(doc.scalar == 2.0 for doc in da)
+
+    # Make sure they share memory
+    stacked_da.scalar[0] = 3.0
+    assert da[0].scalar == 3.0
+
+
+def test_torch_scalar():
+    class MyDoc(BaseDocument):
+        scalar: TorchTensor
+
+    da = DocumentArray[MyDoc](
+        [MyDoc(scalar=torch.tensor(2.0)) for _ in range(3)], tensor_type=TorchTensor
+    )
+    assert all(doc.scalar.ndim == 0 for doc in da)
+    assert all(doc.scalar == 2.0 for doc in da)
+    stacked_da = da.stack()
+    assert type(stacked_da.scalar) == TorchTensor
+
+    assert all(type(doc.scalar) == TorchTensor for doc in da)
+    assert all(doc.scalar.ndim == 1 for doc in da)
+    assert all(doc.scalar == 2.0 for doc in da)
+
+    # Make sure they share memory
+    stacked_da.scalar[0] = 3.0
+    assert da[0].scalar == 3.0
+
+
+def test_np_nan():
+    class MyDoc(BaseDocument):
+        scalar: Optional[NdArray]
+
+    da = DocumentArray[MyDoc]([MyDoc() for _ in range(3)], tensor_type=NdArray)
+    assert all(doc.scalar is None for doc in da)
+    assert all(doc.scalar == doc.scalar for doc in da)
+    stacked_da = da.stack()
+    assert type(stacked_da.scalar) == NdArray
+
+    assert all(type(doc.scalar) == NdArray for doc in da)
+    # Stacking them turns them into np.nan
+    assert all(doc.scalar.ndim == 1 for doc in da)
+    assert all(doc.scalar != doc.scalar for doc in da)  # Test for nan
+
+    # Make sure they share memory
+    stacked_da.scalar[0] = 3.0
+    assert da[0].scalar == 3.0
+
+
+def test_torch_nan():
+    class MyDoc(BaseDocument):
+        scalar: Optional[TorchTensor]
+
+    da = DocumentArray[MyDoc]([MyDoc() for _ in range(3)], tensor_type=TorchTensor)
+    assert all(doc.scalar is None for doc in da)
+    assert all(doc.scalar == doc.scalar for doc in da)
+    stacked_da = da.stack()
+    assert type(stacked_da.scalar) == TorchTensor
+
+    assert all(type(doc.scalar) == TorchTensor for doc in da)
+    # Stacking them turns them into torch.nan
+    assert all(doc.scalar.ndim == 1 for doc in da)
+    assert all(doc.scalar != doc.scalar for doc in da)  # Test for nan
+
+    # Make sure they share memory
+    stacked_da.scalar[0] = 3.0
+    assert da[0].scalar == 3.0