Repository: InsightSoftwareConsortium/ITK (language: python)

Issue:
Inconsistent conversion of VectorImage to numpy
In itk Python, if I have a VectorImage of size (10,10) with 2 channels, then converting it to numpy gives me an array of shape (10,10,2). But if I have a VectorImage with 1 channel, it squeezes that channel and gives me a numpy array of (10,10). Shouldn't the expected behavior be (10,10,1). Or is there a way to ensure this?

```python
import itk
import numpy as np

arr = np.ones((10,10,2))
img = itk.image_from_array(arr, is_vector=True)
arr2 = itk.array_from_image(img)
print(arr2.shape) # (10,10,2)

arr = np.ones((10,10,1))
img = itk.image_from_array(arr, is_vector=True)
arr2 = itk.array_from_image(img)
print(arr2.shape) # (10,10)
```



Discussion / hints:
Thank you for contributing an issue! 🙏

**Welcome to the ITK community!** 🤗👋☀️

We are glad you are here and appreciate your contribution. Please keep in mind our [community participation guidelines](https://github.com/InsightSoftwareConsortium/ITK/blob/master/CODE_OF_CONDUCT.md). 📜
Also, please check [existing open issues](https://github.com/InsightSoftwareConsortium/ITK/issues) and consider discussion on the [ITK Discourse](https://discourse.itk.org). 📖

This is an automatic message. Allow for time for the ITK community to be able to read the issue and comment on it.

Fix patch:
diff --git a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in
index 4d81e2ae63a..3257e06c252 100644
--- a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in
+++ b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in
@@ -9,6 +9,7 @@
         i.e. k,j,i versus i,j,k. However C-order indexing is expected by most
         algorithms in NumPy / SciPy.
         """
+        import itk
 
         if image.GetBufferPointer() is None:
             return None
@@ -24,7 +25,7 @@
         dim     = len(itksize)
         shape   = [int(itksize[idx]) for idx in range(dim)]
 
-        if(image.GetNumberOfComponentsPerPixel() > 1):
+        if image.GetNumberOfComponentsPerPixel() > 1 or isinstance(image, itk.VectorImage):
             shape = [image.GetNumberOfComponentsPerPixel(), ] + shape
 
         if keep_axes == False:
diff --git a/Wrapping/Generators/Python/PyBase/pyBase.i b/Wrapping/Generators/Python/PyBase/pyBase.i
index 28fe5c7847f..83938703f92 100644
--- a/Wrapping/Generators/Python/PyBase/pyBase.i
+++ b/Wrapping/Generators/Python/PyBase/pyBase.i
@@ -514,8 +514,10 @@ str = str
             def ndim(self):
                 """Equivalent to the np.ndarray ndim attribute when converted
                 to an image with itk.array_view_from_image."""
+                import itk
+
                 spatial_dims = self.GetImageDimension()
-                if self.GetNumberOfComponentsPerPixel() > 1:
+                if self.GetNumberOfComponentsPerPixel() > 1 or isinstance(self, itk.VectorImage):
                     return spatial_dims + 1
                 else:
                     return spatial_dims
@@ -524,11 +526,13 @@ str = str
             def shape(self):
                 """Equivalent to the np.ndarray shape attribute when converted
                 to an image with itk.array_view_from_image."""
+                import itk
+
                 itksize = self.GetLargestPossibleRegion().GetSize()
                 dim = len(itksize)
                 result = [int(itksize[idx]) for idx in range(dim)]
 
-                if(self.GetNumberOfComponentsPerPixel() > 1):
+                if self.GetNumberOfComponentsPerPixel() > 1 or isinstance(self, itk.VectorImage):
                     result = [self.GetNumberOfComponentsPerPixel(), ] + result
                 # ITK is C-order. The shape needs to be reversed unless we are a view on
                 # a NumPy array that is Fortran-order.


Test patch:
diff --git a/Modules/Bridge/NumPy/wrapping/test/itkPyBufferTest.py b/Modules/Bridge/NumPy/wrapping/test/itkPyBufferTest.py
index 6e41bf48413..e22418992c3 100644
--- a/Modules/Bridge/NumPy/wrapping/test/itkPyBufferTest.py
+++ b/Modules/Bridge/NumPy/wrapping/test/itkPyBufferTest.py
@@ -197,6 +197,19 @@ def test_NumPyBridge_itkVectorImage(self):
         self.assertEqual(vectorndarr.dtype, convertedvectorImage.dtype)
         self.assertTupleEqual(vectorndarr.shape, convertedvectorImage.shape)
 
+        vectorImage = VectorImageType.New()
+        vectorImage.SetRegions(region)
+        vectorImage.SetNumberOfComponentsPerPixel(1)
+        vectorImage.Allocate()
+        vectorndarr = itk.PyBuffer[VectorImageType].GetArrayViewFromImage(vectorImage)
+
+        convertedvectorImage = itk.PyBuffer[VectorImageType].GetImageViewFromArray(
+            vectorndarr, is_vector=True
+        )
+        self.assertEqual(vectorndarr.ndim, convertedvectorImage.ndim)
+        self.assertEqual(vectorndarr.dtype, convertedvectorImage.dtype)
+        self.assertTupleEqual(vectorndarr.shape, convertedvectorImage.shape)
+
     def test_NumPyBridge_itkRGBImage(self):
         "Try to convert an RGB ITK image to NumPy array view"