Repository navigation
Values change with implicit conversion from mode F to L #5465
Description
Activity
Mode for fromarray is the "Mode to use". I think this is the mode to use when reading the numpy data - if so, then it makes sense that the values change when it is told to interpret the input data directly.
You might be interested in
.convert('L')instead of, mode='L',import numpy as np from PIL import Image tgt = np.ones(shape=(2, 2)) # float32 tgt_im = Image.fromarray(tgt) tgt_im_np = np.asarray(tgt_im) final_img = Image.fromarray(tgt_im_np).convert('L') print(np.asarray(final_img))
gives
[[1 1] [1 1]]Okay thanks for the workaround.
However, is it an expected behavior that random numbers appear in the matrix after doing the implicit conversion?
I assumed that thefromarraymethod either- calls the conversion script, if the input is np.float32, or,
- raises an exception if the array.dtype does not match the specified mode.
I think that the mode to use parameter is a footgun. It’s not an implicit conversion, it’s a cast. In this case, you’re interpreting an array of 32 bit floats as 8 bit ints.
I’m not sure if there is a good use case at all for the mode parameter here, as we need to be pretty sure what we’re getting from numpy to interpret the array correctly.
I had a similar issue in #5723. So, if
modeis given toImage.fromarray(), then what happens is:- the image is created interpreting the array in a predefined manner (e.g.
Fif thedtypeisnp.float32and the values all reside in the[0, 1]. - The image is then converted to the requested mode, changing the
dtypeand value range of the data array if necessary.
Is this correct?
- the image is created interpreting the array in a predefined manner (e.g.
To respond to the previous comment,
fromarraysets the mode according toLines 2877 to 2902 in d76fd93
_fromarray_typemap = { # (shape, typestr) => mode, rawmode # first two members of shape are set to one ((1, 1), "|b1"): ("1", "1;8"), ((1, 1), "|u1"): ("L", "L"), ((1, 1), "|i1"): ("I", "I;8"), ((1, 1), "<u2"): ("I", "I;16"), ((1, 1), ">u2"): ("I", "I;16B"), ((1, 1), "<i2"): ("I", "I;16S"), ((1, 1), ">i2"): ("I", "I;16BS"), ((1, 1), "<u4"): ("I", "I;32"), ((1, 1), ">u4"): ("I", "I;32B"), ((1, 1), "<i4"): ("I", "I;32S"), ((1, 1), ">i4"): ("I", "I;32BS"), ((1, 1), "<f4"): ("F", "F;32F"), ((1, 1), ">f4"): ("F", "F;32BF"), ((1, 1), "<f8"): ("F", "F;64F"), ((1, 1), ">f8"): ("F", "F;64BF"), ((1, 1, 2), "|u1"): ("LA", "LA"), ((1, 1, 3), "|u1"): ("RGB", "RGB"), ((1, 1, 4), "|u1"): ("RGBA", "RGBA"), } # shortcuts _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
So F mode is the "typestr" has "f" (if it is float, see https://numpy.org/doc/stable/reference/arrays.interface.html#object.__array_interface__ for more information)
- I wouldn't say that it is converted - the data is not changed - rather the same data is interpreted differently. This is not what you might expect.
Some notes on the original post.
- The initial post states that
tgt = np.ones(shape=(2, 2)) # float32
This is not correct.
tgt.dtypeis "float64".import numpy as np from PIL import Image tgt = np.ones(shape=(2, 2)) tgt_im = Image.fromarray(tgt) tgt_im_np = np.asarray(tgt_im)
gives
tgt_im_npof[[1. 1.] [1. 1.]]You might assume this is equal to
np.ones(shape=(2, 2)), buttgt.dtypeis "float64" andtgt_im_np.dtypeis "float32". So thedtypechanges, but I'm not convinced it's a problem that Pillow doesn't have a different mode for all of numpy's different modes.- So if we remove that roundtrip from the original code, then
import numpy as np from PIL import Image tgt = np.ones(shape=(2, 2)) final_img = Image.fromarray(tgt, mode="L") print(np.asarray(final_img))
gives
[[0 0] [0 0]]This seems more reasonable than the "random numbers" that were initially described.
import numpy as np from PIL import Image tgt = np.ones(shape=(2, 2)) final_img = Image.fromarray(tgt) print(np.asarray(final_img))
gives
[[1. 1.] [1. 1.]]Closing unless there are further questions. #2856 is also about this situation.
I've created PR #5849 to clarify this in the documentation.
Might be somewhat related to for instance #3011
What did you do?
What did you expect to happen?
I assumed that the resulting matrix values still have the value of 1 but are implictly converted to 8-bit range.
What actually happened?
>>> print(np.asarray(final_img)) [[ 0 0] [128 63]]None of the resulting values keep the original value of 1 and (for me even more surprising) they change to different values at different places in the matrix.
What are your OS, Python and Pillow versions?