Fitting image to ImageView

1,599 views
Skip to first unread message

Nicola Vitucci

unread,
May 28, 2014, 5:57:58 AM5/28/14
to pyqt...@googlegroups.com
Hi all,

I've been trying this for a while but unfortunately to no avail. I have an image (say 800x600 pixels) and I'm trying to fit it completely in an ImageView object, i.e. I'm trying to show all of it with no scaling and no outside padding (no "black borders" around the image). It is important for the image to be visualized correctly because I'm using the RectMode on drag to be able to zoom into an area, but each time the exact pixels need to be retrieved as the image is recalculated based on the zoom; in other words, selecting an area from a "black border" is not correct because they are not the image pixels.

I have looked into scaling, autoRange, setRange and so on but I couldn't find a way to do it. How can I achieve this? Maybe the ImageView is not the best widget at all?

Thanks,

Nicola

Luke Campagnola

unread,
May 29, 2014, 8:47:53 PM5/29/14
to pyqt...@googlegroups.com
Hi Nicola,

Here's an example, if I understand correctly:

import pyqtgraph as pg
v = pg.image(pg.np.random.normal(size=(100,100)))
v.view.setAspectLocked(False)
v.view.setRange(xRange=[0,100], yRange=[0,100], padding=0)

However I am not convinced you need the image to be scaled exactly--you just need to make sure that you map correctly from the coordinate system of the mouse input to the coordinate system of the image.

Nicola Vitucci

unread,
May 30, 2014, 4:44:48 AM5/30/14
to pyqt...@googlegroups.com

Hi Luke,

thanks for the answer - the view.setAspectLocked(False) mostly did the trick. There is one more thing though: doing this hides some pixels on the right, which are covered by the level widget and the ROI/Norm buttons. Is there a way to fit the image only to the "visible" area of the ViewBox?

As for your question, the main problem is that padding would make zooming into the image a bit confusing, in that you'd be allowed to drag your zoom box over pixels which are out of the image. Moreover, I'm sticking to the ViewBox because, as I understand it, this will make it easier to add other widgets to the application such as spins, text boxes and so on.

If you have any other suggestions on how to do this in a better way, or if you need a more detailed example, please let me know.

Nicola

Luke Campagnola

unread,
May 30, 2014, 8:29:44 AM5/30/14
to pyqt...@googlegroups.com
On Fri, May 30, 2014 at 4:44 AM, Nicola Vitucci <nicola....@gmail.com> wrote:
Il giorno venerdì 30 maggio 2014 02:47:53 UTC+2, Luke Campagnola ha scritto:
On Wed, May 28, 2014 at 5:57 AM, Nicola Vitucci <nicola....@gmail.com> wrote:
Hi all,

I've been trying this for a while but unfortunately to no avail. I have an image (say 800x600 pixels) and I'm trying to fit it completely in an ImageView object, i.e. I'm trying to show all of it with no scaling and no outside padding (no "black borders" around the image). It is important for the image to be visualized correctly because I'm using the RectMode on drag to be able to zoom into an area, but each time the exact pixels need to be retrieved as the image is recalculated based on the zoom; in other words, selecting an area from a "black border" is not correct because they are not the image pixels.

I have looked into scaling, autoRange, setRange and so on but I couldn't find a way to do it. How can I achieve this? Maybe the ImageView is not the best widget at all?

Hi Nicola,

Here's an example, if I understand correctly:

import pyqtgraph as pg
v = pg.image(pg.np.random.normal(size=(100,100)))
v.view.setAspectLocked(False)
v.view.setRange(xRange=[0,100], yRange=[0,100], padding=0)

However I am not convinced you need the image to be scaled exactly--you just need to make sure that you map correctly from the coordinate system of the mouse input to the coordinate system of the image.

Hi Luke,

thanks for the answer - the view.setAspectLocked(False) mostly did the trick. There is one more thing though: doing this hides some pixels on the right, which are covered by the level widget and the ROI/Norm buttons. Is there a way to fit the image only to the "visible" area of the ViewBox?

That is not the case on my system--if I make the edge pixels darker in the example above, then I can see they perfectly align to the visible area. Can you provide an example where this does not work as you expect?


As for your question, the main problem is that padding would make zooming into the image a bit confusing, in that you'd be allowed to drag your zoom box over pixels which are out of the image.

With a little effort, you could restrict the selection box to only the area over the image. Another option is to limit the range of the ViewBox such that the user cannot zoom out bast the edge of the image. For example:

import pyqtgraph as pg
pg.mkQApp()

# random image with dark borders
data = pg.np.random.normal(size=(100, 100))
data[1:-1] += 5
data[:, 1:-1] += 5

win = pg.GraphicsWindow()
vb = win.addViewBox()
img = pg.ImageItem(data)
vb.addItem(img)
vb.setLimits(xMin=0, xMax=img.width(), yMin=0, yMax=img.height())
vb.setRange(xRange=[0, img.width()], yRange=[0, img.height()])


Moreover, I'm sticking to the ViewBox because, as I understand it, this will make it easier to add other widgets to the application such as spins, text boxes and so on.

Ultimately that should not make any difference -- the original example I gave only uses `pg.image()` as a convenient way to create a ViewBox, and the ImageView widget can be embedded within a larger GUI as well. The code for setting the range of the ViewBox will be the same in either case.


Luke

Nicola Vitucci

unread,
May 30, 2014, 9:54:02 AM5/30/14
to pyqt...@googlegroups.com


Il giorno venerdì 30 maggio 2014 14:29:44 UTC+2, Luke Campagnola ha scritto:
On Fri, May 30, 2014 at 4:44 AM, Nicola Vitucci <nicola....@gmail.com> wrote:
Il giorno venerdì 30 maggio 2014 02:47:53 UTC+2, Luke Campagnola ha scritto:
On Wed, May 28, 2014 at 5:57 AM, Nicola Vitucci <nicola....@gmail.com> wrote:
Hi all,

I've been trying this for a while but unfortunately to no avail. I have an image (say 800x600 pixels) and I'm trying to fit it completely in an ImageView object, i.e. I'm trying to show all of it with no scaling and no outside padding (no "black borders" around the image). It is important for the image to be visualized correctly because I'm using the RectMode on drag to be able to zoom into an area, but each time the exact pixels need to be retrieved as the image is recalculated based on the zoom; in other words, selecting an area from a "black border" is not correct because they are not the image pixels.

I have looked into scaling, autoRange, setRange and so on but I couldn't find a way to do it. How can I achieve this? Maybe the ImageView is not the best widget at all?

Hi Nicola,

Here's an example, if I understand correctly:

import pyqtgraph as pg
v = pg.image(pg.np.random.normal(size=(100,100)))
v.view.setAspectLocked(False)
v.view.setRange(xRange=[0,100], yRange=[0,100], padding=0)

However I am not convinced you need the image to be scaled exactly--you just need to make sure that you map correctly from the coordinate system of the mouse input to the coordinate system of the image.

Hi Luke,

thanks for the answer - the view.setAspectLocked(False) mostly did the trick. There is one more thing though: doing this hides some pixels on the right, which are covered by the level widget and the ROI/Norm buttons. Is there a way to fit the image only to the "visible" area of the ViewBox?

That is not the case on my system--if I make the edge pixels darker in the example above, then I can see they perfectly align to the visible area. Can you provide an example where this does not work as you expect?

I tried coloring the pixels on the right edge and in fact it works. Thus, I guess I'm not picking up the right coordinates within the redefined mouseDragEvent() (minimal example):
 
    def drag(ev):
      # imv is an ImageView(), v is its ViewBox()
      global imv, v, s_pos, e_pos

      pg.ViewBox.mouseDragEvent(v, ev)
      ev.accept()

      if ev.isStart():
        s_pos = ev.pos()
      elif ev.isFinish():
        e_pos = ev.pos()
        print 'Start', s_pos, ', end', e_pos

    v.mouseDragEvent = drag

Basically, when I zoom on the bottom-right corner, the x coordinates are less than 700 (which shouldn't be the case, as they should be close to 800). Can you suggest how to pick up the right image pixels then? I have tried using the mapTo* methods from ViewBox, but I can't seem to get them right...
 


As for your question, the main problem is that padding would make zooming into the image a bit confusing, in that you'd be allowed to drag your zoom box over pixels which are out of the image.

With a little effort, you could restrict the selection box to only the area over the image. Another option is to limit the range of the ViewBox such that the user cannot zoom out bast the edge of the image. For example:

import pyqtgraph as pg
pg.mkQApp()

# random image with dark borders
data = pg.np.random.normal(size=(100, 100))
data[1:-1] += 5
data[:, 1:-1] += 5

win = pg.GraphicsWindow()
vb = win.addViewBox()
img = pg.ImageItem(data)
vb.addItem(img)
vb.setLimits(xMin=0, xMax=img.width(), yMin=0, yMax=img.height())
vb.setRange(xRange=[0, img.width()], yRange=[0, img.height()])


The setLimits sounds like a good idea, but for trying that I need to upgrade to version 0.9.9 first. I can't do that via easy_install or pip though, have you planned to make it available there too or should I just use the Git repo?
 
Moreover, I'm sticking to the ViewBox because, as I understand it, this will make it easier to add other widgets to the application such as spins, text boxes and so on.

Ultimately that should not make any difference -- the original example I gave only uses `pg.image()` as a convenient way to create a ViewBox, and the ImageView widget can be embedded within a larger GUI as well. The code for setting the range of the ViewBox will be the same in either case.

Luke

That's good, thanks for clarifying that.

Nicola

Luke Campagnola

unread,
Jun 8, 2014, 4:44:55 PM6/8/14
to pyqt...@googlegroups.com
Because you are overriding ViewBox.mouseDragEvent, the event position is in the coordinate system of the ViewBox (which is not the same coordinate system as the image. You can correct this easily:

        # Map event position from the viewbox coordinate system to the image coordinate system
        imagePos = v.mapToItem(imv.imageItem, ev.pos())



As for your question, the main problem is that padding would make zooming into the image a bit confusing, in that you'd be allowed to drag your zoom box over pixels which are out of the image.

With a little effort, you could restrict the selection box to only the area over the image. Another option is to limit the range of the ViewBox such that the user cannot zoom out bast the edge of the image. For example:

import pyqtgraph as pg
pg.mkQApp()

# random image with dark borders
data = pg.np.random.normal(size=(100, 100))
data[1:-1] += 5
data[:, 1:-1] += 5

win = pg.GraphicsWindow()
vb = win.addViewBox()
img = pg.ImageItem(data)
vb.addItem(img)
vb.setLimits(xMin=0, xMax=img.width(), yMin=0, yMax=img.height())
vb.setRange(xRange=[0, img.width()], yRange=[0, img.height()])


The setLimits sounds like a good idea, but for trying that I need to upgrade to version 0.9.9 first. I can't do that via easy_install or pip though, have you planned to make it available there too or should I just use the Git repo?

I am hoping to release 0.9.9 soon. Until then, you must pull the code from the github repository ("develop" branch)


Luke
Reply all
Reply to author
Forward
0 new messages