[Scipy-tickets] [SciPy] #1828: Several misc.im* functions incorrectly handle 3 or 4-channeled (RGB, RGBA) images, depending on image dimensions

SciPy Trac scipy-tickets@scipy....
Mon Jan 28 18:45:48 CST 2013


#1828: Several misc.im* functions incorrectly handle 3 or 4-channeled (RGB, RGBA)
images, depending on image dimensions
------------------------------------------------------------------------------------------+
 Reporter:  erickim555                                                                    |       Owner:  rgommers   
     Type:  defect                                                                        |      Status:  new        
 Priority:  normal                                                                        |   Milestone:  Unscheduled
Component:  scipy.misc                                                                    |     Version:  devel      
 Keywords:  toimage, multi-channels, image, imsave, imrotate, imshow, imresize, imfilter  |  
------------------------------------------------------------------------------------------+
 Functions Affected: misc.imsave, misc.imrotate, misc.imshow,
 misc.imresize, misc.imfilter, misc.toimage

 ==== Bug Summary ====

 If an RGB or RGB+Alpha image array (3 or 4-channel array respectively) I
 is passed to the above functions with more than one 3 (or 4) in its
 dimensions (I.shape), then the image will always be interpreted
 incorrectly.

 Assume that an RGB image is an array of the shape MxNx3, and an RGB+Alpha
 image has shape MxNx4.

 An RGB image with shape (4, 100, 3) will correctly be considered as an RGB
 image of size 100x4 (width, height).

 However, an RGB image with shape (3, 100, 3) will be incorrectly
 considered as an RGB image of size 3x100, along with incorrect colors.

 Similarly, an RGBA image with shape (4, 100, 4) will be incorrectly
 considered as an RGBA image of size 4x100, along with incorrect colors.

 ==== Short Example ====

 Here's a concrete example, illustrating the 3-channel case:

 {{{
 import numpy as np, scipy.misc
 B = np.zeros((3, 100, 3), dtype='uint8')
 B[1:2, :, :] = 0.0 # Add a black stripe to RGB channels
 print "B.shape:", B.shape
 scipy.misc.imsave("B.png", B)
 B_cpy = scipy.misc.imread("B.png")
 print "B_cpy.shape:", B_cpy.shape
 print "    B.shape == B_cpy.shape:", B.shape == B_cpy.shape
 }}}

 Output:

 {{{
 B.shape: (3, 100, 3)
 B_cpy.shape: (100, 3, 3)
     B.shape == B_cpy.shape: False
 }}}

 ==== Cause of Bug ====

 The root cause is in the 'misc.toimage' function, which all of the above
 functions invoke.

 In the 'misc.toimage' function, if the keyword arg 'channel_axis' is not
 given, then 'toimage' infers the axis on which the 3/4-channeled image
 stores its RGB/RGBA data.

 'toimage' infers this by choosing the first axis on which a 3 (or 4)
 occurs:

 {{{
 def toimage(...):
     ...
     # if here then 3-d array with a 3 or a 4 in the shape length.
     # Check for 3 in datacube shape --- 'RGB' or 'YCbCr'
     if channel_axis is None:
         if (3 in shape):
             ca = numpy.flatnonzero(asarray(shape) == 3)[0]
         else:
             ca = numpy.flatnonzero(asarray(shape) == 4)
             if len(ca):
                 ca = ca[0]
             else:
                 raise ValueError("Could not find channel dimension.")
     ...
 }}}

 Note that the above affected functions all call 'toimage' without passing
 in an argument for 'channel_axis' - thus, the incorrect inference for
 'channel_axis' is performed for certain image input dimensions - e.g. the
 shape (3, 100, 3) from earlier.

 ==== Discussion/Proposed Fix ====

 It looks like 'misc.toimage' is attempting to be flexible about how to
 interpret 3/4-channeled images, hence the 'channel_axis' keyword argument,
 so I don't think that the 'channel_axis' inference is particularly
 incorrect. In theory a user could pass in the 'correct' axis for
 'channel_axis' to avoid this ambiguity.

 However, the documentation for the misc.im* functions is very clear that
 the input images will be interpreted as MxN (grayscale), MxNx3 (RGB), or
 MxNx4 (RGB+Alpha) images:

 http://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imsave.html

 I think each affected function should pass in 'channel_axis=2' for
 3/4-channeled images when calling 'toimage', to avoid images being
 interpreted incorrectly.

 ==== Versions ====

 I'm currently running:

 OS:
     Linux 3.2.0-36-generic #57-Ubuntu SMP Tue Jan 8 21:44:52 UTC 2013
 x86_64 x86_64 x86_64 GNU/Linux

 Python:
     2.7.3 (64 bit)

 numpy: 1.6.1
 scipy: 0.9.0
 PIL: 1.1.7

 Note: Looking at the latest dev branch at:

           https://github.com/scipy/scipy/blob/master/scipy/misc/pilutil.py

       I see that the problem is still present in the source code (as of
 Jan. 28, 2013)

 ==== (Long) Example ====

 {{{
 import numpy as np, scipy.misc

 """ A, A_alpha are example images: a single black stripe. """
 A = np.zeros((6, 100, 3), dtype='uint8')
 A[1:2,:,:] = 0.0
 print "A.shape:", A.shape
 scipy.misc.imsave("A.png", A)
 A_cpy = scipy.misc.imread("A.png")
 print "A_cpy.shape:", A_cpy.shape
 print "    A.shape == A_cpy.shape:", A.shape == A_cpy.shape

 A_alpha = np.zeros((6, 100, 4), dtype='uint8')
 A_alpha[1:2,:,0:3] = 0.0 # Add a black-stripe to RGB channels
 A_alpha[:,:,3] = 0.0     # Set Alpha channel to no transparency
 print "A_alpha.shape:", A_alpha.shape
 scipy.misc.imsave("A_alpha.png", A_alpha)
 A_alpha_cpy = scipy.misc.imread("A_alpha.png")
 print "A_alpha_cpy.shape:", A_alpha_cpy.shape
 print "    A_alpha.shape == A_alpha_cpy.shape:", A_alpha.shape ==
 A_alpha_cpy.shape

 """
 1.) misc.toimage mistakenly interprets B as having its RGB channels
     along the first axis (rather than the last axis), e.g. B is
     interpreted as a 3xMxN image, rather than as an MxNx3 image.
 """
 B = np.zeros((3, 100, 3), dtype='uint8')
 B[1:2, :, :] = 0.0 # Add a black stripe to RGB channels
 print "B.shape:", B.shape
 scipy.misc.imsave("B.png", B)
 B_cpy = scipy.misc.imread("B.png")
 print "B_cpy.shape:", B_cpy.shape
 print "    B.shape == B_cpy.shape:", B.shape == B_cpy.shape

 """
 2.) misc.toimage mistakenly interprets C as having its RGBA channels
     along the first axis (rather than the last axis), e.g. B is
     interpreted as a 4xMxN image, rather than as an MxNx4 image.
 """
 C = np.zeros((4, 100, 4), dtype='uint8')
 C[1:2,:,0:3] = 0.0 # Add a black stripe to RGB channels
 C[:,:,3] = 0       # Set Alpha channel to no transparency

 print "C.shape:", C.shape
 scipy.misc.imsave("C.png", C)
 C_cpy = scipy.misc.imread("C.png")
 print "C_cpy.shape:", C_cpy.shape
 print "    C.shape == C_cpy.shape:", C.shape == C_cpy.shape
 }}}

-- 
Ticket URL: <http://projects.scipy.org/scipy/ticket/1828>
SciPy <http://www.scipy.org>
SciPy is open-source software for mathematics, science, and engineering.


More information about the Scipy-tickets mailing list