New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
imshow intermediate memory usage #6952
Comments
I agree that seems to high but we probably need to do some memory profiling to see if anything can be done. Have you considered something like http://matplotlib.org/examples/event_handling/resample.html which downsamples the data and dynamically replots only the fraction needed. I have not tested this or tried extending it to image data |
Also the image code has been largely rewritten for 2.0 so it would be great if you can test 2.0 beta 3 to see how that behaves. |
Indeed, the image code has been greatly improved for v2.0, so I would see If all else fails, you might be interested in using DataShader. On Thu, Aug 18, 2016 at 5:28 AM, Jens Hedegaard Nielsen <
|
I can confirm the issue with mpl2.0b3 (profiled with memory_profiler), although only with a ~6x memory usage. One of the copies occur because matplotlib always copies the input data, which is going to be difficult to change I think. The other 4x are used in
( |
Took me a bit to do somerthing similar to what @anntzer did. This is what I’ve found, both for MPL 1.5 and 2: In matplotlib 1.5.2: Inside image.py, there is a _get_unsampled_image() method. This method calls x = self.to_rgba(…, bytes=False) at some point. To_RGBA with such parameters always returns a float64 if the input is a type of float, and since it’s RGBA [i.e., 4 dimensions] this means there is at least 4x the size of the original data required (up to +1 actually since original data also still exists, and another up to +1 for the initial copy MPL makes). It would be 8x instead of initial 4x if the original data were e.g. float32. Then there is this call, x = (x*255).astype(np.uint8) For a reason I cannot determine, the multiplication step of this doubles RAM temporarily as it runs. Tried on Windows 7 and CentOS6, via memory_profiler. Even in isolation,
Gives 3150 MB on two machines with different OS’ and different versions of NumPy, which also agrees with Task Manager on Windows . Tried separating the multiplication command from the astype, tried np.multiply, tried copy=False, etc. All give same memory usage except separate np.multiply + separate, which gives 200 MB less. Do not understand this memory usage, guess it has something to do with numpy internals? In any case, it doubles the RAM temporarily as it runs. Once this conversion to uint8 is finished, RAM goes down very significantly obviously. But even in best case, during the intermediate operations, that’s at least 9x the original data size worth of RAM necessary, and in float32 case it’s 16-18x. So if I have a 1 GB data file with float32 array, displaying that requires almost 20 GB of RAM. These 1 GB sizes do happen in astronomy. In matplotlib 2.0.0b3: Actually @anntzer covered it. What he said is identical to what I get. As before, the result of 6x more memory than original becomes 10x if the original data is float32 and not float64. Any suggestions? Both 10x and nearly 20x more memory is a really large problem for some images we have (e.g., on 1.5.2 a 512MB float32 grayscale image requires at least 9.5 GB of RAM, checked that just from the sample code I gave in the original issue description). Regarding resample for image data, I want this data to be displayed with scrollbars (i.e., it won’t all fit on the screen at the same time but it should be scrollable), so I do not know how I could use resample to help (although suggested approaches to this would be appreciated). One method that might work is to use extent + imshow to split up the drawing but it seems very hacky and complicates other parts of the code. I have a medium sized application for astronomers, the visualization bits of which are based on MPL so it would take a number of months at least to switch to some other toolkit even if one could do everything MPL does and this image aspect with less RAM + it’s ultimately packaged via PyInstaller which adds another layer of complication. Therefore I am very incentivized to continue with MPL but I really wish there was not a 10x – 20x memory issue, or I could work around it in some way. One thing I am wondering is this piece of relevant code from image.py in MPL 1.5.2,
Why in that code is to_rgba() called with bytes=False? Is it for the premultiplying the colors step (because the step after that is seemingly exactly what is done by bytes=True). The only way I can see alpha being anything but 1 (and thus premultiplying colors having any effect) is if the data is already an RGBA array, but that could be trivially checked in this method, calling bytes=False only if it’s already true. This would avoid having to call bytes=False in all non-RGBA cases, which saves a lot of RAM (experimentally I get 3.5 GB instead of 9.5 GB for a case, still too much given original data is only 512MB of float32’s but much better). I am not sure if I am not misunderstanding something completely trivial here though. |
The reason we pass an a full NxMx4 array is that we are using the Agg resampler which only works on RGBA images. In principle @anntzer is right, however it would involve changing what library is used to do the resampling under the hood. I do not understand why float32 is worse, do we upcast when we copy? As for resampling, have a look at https://github.com/bokeh/datashader/pull/200/files which hides a It is probably worth putting some logic in |
I'm a bit confused now. A quick look at the Agg (which I know nothing about) docs suggests that it supports the pixfmt_gray8 pixel format, so could we just make Agg believe that the image are grayscale during the resampling phase? |
Sort of. When calling
I think this will always obtain float64 for a float32. The docstring reads,
It is not clear to me whether the upcast from float32 to float64 is intentional or not from that description, but I lean toward not. That upcasting/call to norm() takes place prior to the code @anntzer gave, and if unsample=False (in which case code @anntzer gave will not be run) it takes place prior to the call cmap which is the second place of maximum RAM usage. So yes, there is an upcast and its carried forward to all future operations (some of which make their own copies) that compound the memory usage even further.
Is this referring to the Other sources of memory usage (aside from above paragraph and call to (1) Initial copy MPL makes on intake + initial data that still exists (2) that although _make_image runs A = self.norm(A), (3) that cmap makes a copy (maybe unavoidable). There's a call in cmap
Only saves a bit of memory though, unless making the copy altogether can be refactored somehow . |
It is the For the data usage points:
Given your use case, it may make sense to make a new image The upcast in norm looks like a bug, can you or @anntzer put in a PR changing that to float32? |
Where is this over/under/bad machinery located? I assume it doesn't come from Agg itself? |
The over/under/bad logic is in the Sigh, I even merged the float32 fix. @LevN0 can you please update to the latest master (or v2.x) branch? |
This doesn't seem to work on a (m, n, 4) array as input? Or did I read the code wrong? |
@anntzer I have lost track of which 'this' we are talking about. The pipeline (iirc) is :
Passing 2 directly into Another possible way to reduce the memory usage would be to call |
I think I need to sit down and figure this out... I'm not there yet. |
I rewrote some of my code such that it hooks scrollbars to axis limits. This resolves some memory inefficiencies in my own code, but it does nothing to address the huge memory usage by the trivial code in the original issue report above. An even worse pathological situation has been pointed out to me: an image that has a data type of byte (i.e. 0 to 255). On norming, this takes memory usage to 4x since it goes to float32. On RGBA creation, this then takes it another 4x to 16x. This is without any other steps in the above pipeline by @tacaswell. There has to be some more efficient way of doing this. Even for relatively small images, 16x is a very large memory penalty. A comment by @tacaswell above read,
However hooking scrollbars up to axis limits does not seem to affect what makes it to cmap in any way. Nor in fact does matplotlib appear to downsample itself (e.g. see original 5-liner code to display image at the top, the displayed image is downsampled however the transient memory usage is of a completely non-downsampled image). |
The scroll bars need to call What version are you testing with? The pipline I described for for 2.0+, if you are 1.x we do indeed map the full-sized image to RGBA and then down-sample to screen resolution. Optomizing imshow is in our list of google summer of code projects, lets see if we get any takers. |
Would such hooks also be valuable for integration with tools such as
datashader? Perhaps also being able to pass a netcdf variable and letting
imshow extract into memory only the portion needed?
…On Fri, Mar 10, 2017 at 1:31 PM, Thomas A Caswell ***@***.***> wrote:
The scroll bars need to call set_array + set_extents with the down
sampled / cropped image. We could probably do this internally (it is not
hard conceptually, just lots of details to get right).
What version are you testing with? The pipline I described for for 2.0+,
if you are 1.x we do indeed map the full-sized image to RGBA and then
down-sample to screen resolution.
Optomizing imshow is in our list of google summer of code projects, lets
see if we get any takers.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#6952 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AARy-N-ccNyJ4yCz0NU7YWagXvz2hVHEks5rkZcXgaJpZM4JnITk>
.
|
I am using 2.0 for testing. For clarity, the way I have it setup is that set_array and set_extent use the full array and full extent: this allows me to take advantage of numerous things MPL does for me (the app exposes to the user numerous MPL features, including different norms, colorbars, changing axis limits, upsampling or downsampling, etc). However, via xlim and ylim, only the portion of the image that can fit onto the screen is selected, and it is displayed in a canvas that is only the size of the window. So ideally the full image should never be displayed or drawn (unless user zooms out to get similar view as MPLs default result by calling imshow). The comment said,
I understood this to mean axis limits. I should note that MPL's pan/zoom tools seem to do the same thing my app does: _make_image receives, normalizes and creates an RGBA array for the full array, no matter how zoomed in/panned you are. Edit: I should say that I was aware that axis limits would not solve this issue before I did a refactor on my code. But I am still hoping that there is some way to remove a more than 16x memory multiplier on int8 data and the somewhat smaller but still quite large (at least 6x) multipliers on other data types. |
It does need to normalize for the full array so that the results are
consistent regardless of what subset is being viewed.
…On Fri, Mar 10, 2017 at 2:03 PM, LevN0 ***@***.***> wrote:
I am using 2.0 for testing. For clarity, the way I have it setup is that
set_array and set_extent use the full array and full extent: this allows me
to take advantage of numerous things MPL does for me (the app exposes to
the user numerous MPL features, including different norms, colorbars,
zooming, etc). However, via xlim and ylim, only the portion of the image
that can fit onto the screen is selected, and it is displayed in a canvas
that is only the size of the window. So ideally the full image should never
be displayed or drawn (unless user zooms out to get similar view as MPLs
default result by calling imshow).
The comment said,
this is why relying on our pan/zoom tools (or hooking the scroll bars up
to the axes limits)
I understood this to mean axis limits, not set_array or set_extent. I
should note that MPL's pan/zoom tools seem to do the same thing my app
does: _make_image receives, normalizes and creates and RGBA array for the
full array, no matter how zoomed in/panned you are.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#6952 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AARy-HIjUWsrSFG-oYd6Irb0h5bAFLVIks5rkZ6fgaJpZM4JnITk>
.
|
What norm are you using? In most cases, the RGBA is created in https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/image.py#L428 which is called with the resampled (normalized) array. If you zoom way out it will help with memory, zooming in could very well make it much worse (because we do not crop). Sorting out which positions in |
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help! |
On my machine, imshow uses 3.5 GB of RAM at peak during its plotting process for a 385 MB array, even if this array is already a float64. Nearly 10x the RAM required for just the data alone. Once the plot is finished, it goes significantly. Is this a bug, or is this just how it is?
I cannot simply downsample because I would like to show the user the image at its true size, but allow them to scroll around to different parts via the scrollbars. Is there any approach someone can suggest to do this without having to have 10x the RAM?
Here is a minimal example via pyplot that reproduces this issue exactly; I used task manager to measure the RAM usage.
`import matplotlib.pyplot as plt
import numpy as np
img = np.random.rand(3000, 16024, dtype='float64')
imgplot = plt.imshow(img)
plt.show()`
Python 2.7 and 3.4
matplotlib 1.5.1 (installed via pip)
Using tkagg backend.
Windows 7
The text was updated successfully, but these errors were encountered: