Repository: scikit-image/scikit-image

Issue:
Build image pyramids not always working with other images
## Description
Using the *[Build image pyramids](https://scikit-image.org/docs/dev/auto_examples/transform/plot_pyramid.html)* example with a random image is not always working.

## Way to reproduce
### hand.jpg
![hand](https://user-images.githubusercontent.com/28227183/69906161-5e4ce380-13bf-11ea-9d4a-b51c54f11581.jpg)
```python
import numpy as np
import matplotlib.pyplot as plt

from skimage import data
from skimage.transform import pyramid_gaussian

import imageio as io

image = io.imread('hand.jpg') # data.astronaut()
rows, cols, dim = image.shape
pyramid = tuple(pyramid_gaussian(image, downscale=2, multichannel=True))

composite_image = np.zeros((rows, cols + cols // 2, 3), dtype=np.double)

composite_image[:rows, :cols, :] = pyramid[0]

i_row = 0
for p in pyramid[1:]:
    n_rows, n_cols = p.shape[:2]
    composite_image[i_row:i_row + n_rows, cols:cols + n_cols] = p
    i_row += n_rows

fig, ax = plt.subplots()
ax.imshow(composite_image)
plt.show()
```


## Version information
```python
3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)]
Windows-10-10.0.18362-SP0
scikit-image version: 0.16.1
numpy version: 1.17.2
```

```python
Traceback (most recent call last):
  File "D:\Vincent\Bureau\Patern recongnition and image analysis\Patern recognition and patern analysis\LAB_1\plot_pyramid.py", line 44, in <module>
    composite_image[i_row:i_row + n_rows, cols:cols + n_cols] = p
ValueError: could not broadcast input array from shape (2,2,3) into shape (1,2,3)
```
## Possible solution
I was able to make it works for the same RGB image but this code is not adapted for BW and RGBA.

```python
import numpy as np
import matplotlib.pyplot as plt

from skimage import data
from skimage.transform import pyramid_gaussian
import imageio as io

image = io.imread('hand.jpg') # data.astronaut()

rows, cols, dim = image.shape
pyramid = tuple(pyramid_gaussian(image, downscale=2, multichannel=True))

composite_image = np.zeros((rows, cols + cols // 2, dim), dtype=np.double)

composite_image[:rows, :cols, :] = pyramid[0]

i_row = 0
for p in pyramid[1:]:
    n_rows, n_cols = p.shape[:2]
    # Check the dimension before assignement
    if(composite_image[i_row:i_row + n_rows, cols:cols + n_cols].shape==p.shape):
        composite_image[i_row:i_row + n_rows, cols:cols + n_cols] = p
        i_row += n_rows
    else:
        break
    
fig, ax = plt.subplots()
ax.imshow(composite_image)
plt.show()
```
### Result
![Figure_1](https://user-images.githubusercontent.com/28227183/69906188-d915fe80-13bf-11ea-8a07-7f9d1d354897.png)




Discussion:
Thanks for the report @2010019970909! Looks like an issue with rounding image size as we approach (0,). We'll have to investigate to make sure the example stays consistent with whatever the pyramid generation function uses.
This was answered after the move to discussions. Now that we are disabling Discussions I have copied it back here:

> rfezzani on Oct 18, 2021
> The error occurs when i_rows + n_rows >= rows. A simple fix is

```Python
import numpy as np
import matplotlib.pyplot as plt

from skimage import data
from skimage.transform import pyramid_gaussian
import imageio as io

image = io.imread('hand.jpg') # data.astronaut()

rows, cols, dim = image.shape
pyramid = tuple(pyramid_gaussian(image, downscale=2, multichannel=True))

composite_image = np.zeros((rows, cols + cols // 2, dim), dtype=np.double)

composite_image[:rows, :cols, :] = pyramid[0]

i_row = 0
for p in pyramid[1:]:
    n_rows, n_cols = p.shape[:2]
    i_tmp = i_row + n_rows
    if i_tmp > rows:
        break
    composite_image[i_row:i_tmp, cols:cols + n_cols] = p
    i_row = i_tmp

fig, ax = plt.subplots()
ax.imshow(composite_image)
plt.show()
```

-----

> Vincent-Stragier
> on Oct 18, 2021
> Author
> 
> Hi @rfezzani,
> 
> Shouldn't the function `pyramid_gaussian` directly output the adequate number of images?
> 
> Regards,
> Vincent

-----

> rfezzani:
> The error occurs only for pyramid display, I don't think that the `pyramid_gaussian` function needs to take care of this by default.
> But the `max_layer` argument controls the number of images, you may use if you want to:
```python
max_rows = rows
max_layer = 0
idx = 0
while idx < max_rows:
    rows = round(rows / downscale)
    idx += rows
    max_layer += 1
rows = max_rows
```

-----

> Vincent-Stragier
> Yes, it makes sense. However, it would be nice to update the documentation (https://scikit-image.org/docs/dev/auto_examples/transform/plot_pyramid.html) with the error proof example. The error message is not clear by itself (at least for beginners).

Resolving patch:
diff --git a/doc/examples/transform/plot_pyramid.py b/doc/examples/transform/plot_pyramid.py
--- a/doc/examples/transform/plot_pyramid.py
+++ b/doc/examples/transform/plot_pyramid.py
@@ -9,6 +9,8 @@
 detection.
 
 """
+import math
+
 import numpy as np
 import matplotlib.pyplot as plt
 
@@ -20,10 +22,31 @@
 rows, cols, dim = image.shape
 pyramid = tuple(pyramid_gaussian(image, downscale=2, channel_axis=-1))
 
-composite_image = np.zeros((rows, cols + cols // 2, 3), dtype=np.double)
-
+#####################################################################
+# Generate a composite image for visualization
+# ============================================
+#
+# For visualization, we generate a composite image with the same number of rows
+# as the source image but with ``cols + pyramid[1].shape[1]`` columns. We then
+# have space to stack all of the dowsampled images to the right of the
+# original.
+#
+# Note: The sum of the number of rows in all dowsampled images in the pyramid
+# may sometimes exceed the original image size in cases when image.shape[0] is
+# not a power of two. We expand the number of rows in the composite slightly as
+# necessary to account for this. Expansion beyond the number of rows in the
+# original will also be necessary to cover cases where downscale < 2.
+
+# determine the total number of rows and columns for the composite
+composite_rows = max(rows, sum(p.shape[0] for p in pyramid[1:]))
+composite_cols = cols + pyramid[1].shape[1]
+composite_image = np.zeros((composite_rows, composite_cols, 3),
+                           dtype=np.double)
+
+# store the original to the left
 composite_image[:rows, :cols, :] = pyramid[0]
 
+# stack all downsampled images in a column to the right of the original
 i_row = 0
 for p in pyramid[1:]:
     n_rows, n_cols = p.shape[:2]


Test patch: