Histogram changes from numpy float32 to tensor float32

username: einsteinxx
cooked: <p>I’ve got some images I have pre-processed to do contrast adjustment and normalization. After the pre-processing, things look okay, but I’ve seen that the images returned from the dataloader (when I convert to tensor float32) don’t usually match what I saw before they were cast to tensors. The image is size 3xHxW, where the middle channel is actual raw image with the contrasting and the other two channels are random noise versions of that channel.</p>
<p>The contrast adjustment used is:<br>
<a href="https://scikit-image.org/docs/stable/api/skimage.exposure.html#skimage.exposure.equalize_adapthist" rel="noopener nofollow ugc"><code>skimage.exposure.equalize_adapthist</code></a>(image[, …]) Contrast Limited Adaptive Histogram Equalization (CLAHE).<br>
which returns float64 output, then I normalize that and save it to a pickle file for loading later.</p>
<p>Code snippet to show histograms</p>
<pre><code class="lang-auto">img = pickle.load( open( fname, "rb" ) )
print('initial image type is ',type(img[1,0,0]))



plt.figure()
plt.hist(img[1,:,:])
plt.title('Original')

img2=img.astype(np.float32)
plt.figure()
plt.hist(img2[1,:,:])
plt.title('numpy float32')

img = torch.as_tensor(img, dtype=torch.float32) 
plt.figure()
plt.hist(img[1,:,:])
plt.title('tensor float32')
</code></pre>
<p>output:<br>
initial image type is  &lt;class ‘numpy.float64’&gt;<br>
<img src="https://discuss.pytorch.org/uploads/default/original/3X/4/a/4a897f16863d52c683daf4719cdc9f6675e26d4d.png" alt="orig" data-base62-sha1="aDnUT93SI6BzKXmcTK8Ds9PhHLT" width="375" height="264"><br>
<img src="https://discuss.pytorch.org/uploads/default/original/3X/e/8/e8db77b4456011f68fd1e7846da51477d9635fd8.png" alt="np32" data-base62-sha1="xdWYMgadoK8zWgrDvUyB7yRMMOI" width="375" height="264"><br>
<img src="https://discuss.pytorch.org/uploads/default/original/3X/f/9/f9bbe05926129467291f62c8503b0ad39a2f13d5.png" alt="tensor32" data-base62-sha1="zDfpD3MhaicRQCHFJ8e2e1vGZAF" width="375" height="264"></p>
<p>I can guess that going from float64 to float32 might have to clip some numbers, if I had any in the range above float32, but I didn’t have any of those. Since the range between numbers might be less, could things be collapsing together onto the closest number in float32? If so, why does it only change for numpy float32 to a tensor and is not readily visible from numpy float64 to float32?</p>

username: ptrblck
cooked: <p>That’s an interesting issue indeed and I don’t think the numerical format creates the difference.<br>
If you are using <code>plt.hist(img.numpy())</code> to pass the input as the expected numpy array to <code>hist</code>, you’ll get the same results, I guess internally <code>hist</code> might be treating the tensor differently than the numpy array.<br>
Also, comparing each scalar between the tensor and numpy array yields a zero difference (if the numpy array is in <code>np.float32</code>).</p>
<p>EDIT: it seems transposing the tensor yields the same result: <code>plt.hist(img[1, :, :].t())</code>, but still unsure why that’s the case (probably the order of reading values as arrays are expected?).</p>

username: tom
cooked: <p>The difficulty is that pyplot.hist special cases “numpy.array” and treats tensors as sequences of sequences and “transposes it” (see the <a href="https://github.com/matplotlib/matplotlib/blob/bba8e9e645d58e788b8a8dfead3b6fb4556487da/lib/matplotlib/axes/_axes.py#L6332-L6335" rel="noopener nofollow ugc">docstring</a>). This is in contrast to other pyplot functions.<br>
I’ve bumped into this more often than I’d like to admit, but never got around to tracking it down.</p>
<p>Best regards</p>
<p>Thomas</p>
<p>P.S.: The deed is done here: <a href="https://github.com/matplotlib/matplotlib/blob/bba8e9e645d58e788b8a8dfead3b6fb4556487da/lib/matplotlib/cbook/__init__.py#L1372" rel="noopener nofollow ugc"><code>lib/matplotlib/cbook/__init__.py</code> line 1372</a>. If PyTorch provided to_numpy or PyPlot checked for <code>X.__array__()</code> instead, it would work. Maybe they could be inclined to use that.</p>

username: ptrblck
cooked: <p>Ah OK, this would also explain why <code>plt.hist(img2.tolist())</code> yields the same results as passing the tensor directly.</p>