Discussion:
[fc] Image HitCoords
Nouvelle Collection
2014-03-18 11:12:10 UTC
Permalink
Hello,

A small question : sometimes I want to get the (x,y) hitcoords when
clicking on an 309x34 pixels image, and I would like x to be an integer in
[0, 308], but when self.canvas.ZoomToBB() has been used, sometimes the
(x,y) coordinates are a bit "off" of some pixels.

For example, when using x = event.HitCoords[0] -
event.BoundingBox[0,0], the range I get for x is [-1.2598, 302.24277] =>
how to get [0,308] instead ?

Is there a parameter somewhere for HitCoords accuracy ? (high, medium,
etc.)

Best regards, Jo
____

import wx
from wx.lib.floatcanvas import FloatCanvas

class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK
SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0), Height=img.GetHeight(),
Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
wx.CallAfter(self.canvas.ZoomToBB)

def OnMotion(self, event):
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print x, y # the max range I can get for x is [-1.2598,
302.24277] => how to get [0,308] instead ?

app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(300,200))
frame.Show(True)
app.MainLoop()
Chris Barker
2014-03-18 21:46:12 UTC
Permalink
On Tue, Mar 18, 2014 at 4:12 AM, Nouvelle Collection <
Post by Nouvelle Collection
A small question : sometimes I want to get the (x,y) hitcoords when
clicking on an 309x34 pixels image, and I would like x to be an integer in
[0, 308], but when self.canvas.ZoomToBB() has been used, sometimes the
(x,y) coordinates are a bit "off" of some pixels.
For example, when using x = event.HitCoords[0] -
event.BoundingBox[0,0], the range I get for x is [-1.2598, 302.24277] =>
how to get [0,308] instead ?
Is there a parameter somewhere for HitCoords accuracy ? (high, medium,
etc.)
No -- it's limited by pixel accuracy for the mouse click, and floating
point accuracy for the scaling calculation. So it depends on the zoom
level.

1) you'll want to round.

2) It depends on the zoom level -- if you have 308 pixel wide image, but
are showing it so that 4 pixels in the image are only one pixel big on the
screen, you can only get so close.

When I try this with the ScaledBitmap2 demo, it seems to work as well as
I"d expect.

The scale depends on two thing: how the ScaledImage is originally sclaed to
World Coords:

img = FloatCanvas.ScaledBitmap2( image,
(0,0),
Height=image.GetHeight(),
Position = 'tl',
Quality='high'
)
This is setting the Height in world coords the same as the image height in
pixels -- so you should get a 1:1 scaling of pixels to works coords

print Canvas.Scale

will tell you how WorldCoords are being scaled to the final drawn image:

print "The Scale is set to:", self.Canvas.Scale

For me, if it's close to 1.0, it works pretty well, much under one, and
you're seeing the limitations of pixel resolution.

However, I did find a big (mis-feature?): The ScaledBitmaps were getting
the HitLineWidth from the default DrawObject. But for those, you really
don't want a line width for the hit-test bitmap. It was set to 3 pixels, so
was letting the image be "hit" an extra 1-2 pixels outside the actual image.

Fixed in SVN.

-Chris
Post by Nouvelle Collection
Best regards, Jo
____
import wx
from wx.lib.floatcanvas import FloatCanvas
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK
SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0),
Height=img.GetHeight(), Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
wx.CallAfter(self.canvas.ZoomToBB)
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print x, y # the max range I can get for x is [-1.2598,
302.24277] => how to get [0,308] instead ?
app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(300,200))
frame.Show(True)
app.MainLoop()
_______________________________________________
FloatCanvas mailing list
http://mailman.paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas
--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker-***@public.gmane.org
Nouvelle Collection
2014-03-21 13:06:07 UTC
Permalink
Hello Chris,

Thanks a lot for your answer and for the explanations about how it works.

I retried my little example (see image_hit_coords_2.py in this email),
especially with the updated version of FloatCanvas.py from SVN, and here
are the results :

* when you launch image_hit_coords_2.py, the canvas.Scale is 1.02686
(because of the choice I did for the image, and for the Frame's width=350).
* the only values I can get for x (when MOTION on the image) are : [-0.34,
0.63, 1.60, ..., 298.62, 299.60]
* the theoritical values should be : [0, 1, 2, 3, ..., 308] (i.e. 309
values because my image's width is 309), but of course, as there is the
scaling factors, it is normal that we have floats, I totally agree with
that.

With a scale of 102.686 % (not far from 100%!), having a max of 299.60
instead of 308 might be a bit far from what we should get.

With such a scale (nearly 103%), I was expecting having values from 0 to
something like 308 or 307 or 306. But here 299.60 seems a bit far away,
don't you think so ?

Do you think there is something wrong in the way I compute x and y ? :

def OnMotion(self, event):
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print self.canvas.Scale, x, y

Thanks a lot,

Best regards, jo
Post by Chris Barker
On Tue, Mar 18, 2014 at 4:12 AM, Nouvelle Collection <
Post by Nouvelle Collection
A small question : sometimes I want to get the (x,y) hitcoords when
clicking on an 309x34 pixels image, and I would like x to be an integer in
[0, 308], but when self.canvas.ZoomToBB() has been used, sometimes the
(x,y) coordinates are a bit "off" of some pixels.
For example, when using x = event.HitCoords[0] -
event.BoundingBox[0,0], the range I get for x is [-1.2598, 302.24277] =>
how to get [0,308] instead ?
Is there a parameter somewhere for HitCoords accuracy ? (high, medium,
etc.)
No -- it's limited by pixel accuracy for the mouse click, and floating
point accuracy for the scaling calculation. So it depends on the zoom
level.
1) you'll want to round.
2) It depends on the zoom level -- if you have 308 pixel wide image, but
are showing it so that 4 pixels in the image are only one pixel big on the
screen, you can only get so close.
When I try this with the ScaledBitmap2 demo, it seems to work as well as
I"d expect.
The scale depends on two thing: how the ScaledImage is originally sclaed
img = FloatCanvas.ScaledBitmap2( image,
(0,0),
Height=image.GetHeight(),
Position = 'tl',
Quality='high'
)
This is setting the Height in world coords the same as the image height in
pixels -- so you should get a 1:1 scaling of pixels to works coords
print Canvas.Scale
print "The Scale is set to:", self.Canvas.Scale
For me, if it's close to 1.0, it works pretty well, much under one, and
you're seeing the limitations of pixel resolution.
However, I did find a big (mis-feature?): The ScaledBitmaps were getting
the HitLineWidth from the default DrawObject. But for those, you really
don't want a line width for the hit-test bitmap. It was set to 3 pixels, so
was letting the image be "hit" an extra 1-2 pixels outside the actual image.
Fixed in SVN.
-Chris
Post by Nouvelle Collection
Best regards, Jo
____
import wx
from wx.lib.floatcanvas import FloatCanvas
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor =
"DARK SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0),
Height=img.GetHeight(), Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
wx.CallAfter(self.canvas.ZoomToBB)
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print x, y # the max range I can get for x is [-1.2598,
302.24277] => how to get [0,308] instead ?
app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(300,200))
frame.Show(True)
app.MainLoop()
_______________________________________________
FloatCanvas mailing list
http://mailman.paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas
--
Christopher Barker, Ph.D.
Oceanographer
Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
_______________________________________________
FloatCanvas mailing list
http://mailman.paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas
Chris Barker
2014-03-21 19:30:19 UTC
Permalink
A couple thoughts:

1) if you want this to be more precise, you can adjust the scale to
integers, rather than using exactly what ZoomToBB gives you:

in __init__

wx.CallAfter(self.Scale)

this method does the work.
def Scale(self):

self.canvas.ZoomToBB() # to get it close
self.canvas.Scale = round(self.canvas.Scale)
self.canvas.ViewPortCenter = np.round(self.canvas.ViewPortCenter)
self.canvas.SetToNewScale()

Kind of a klunky API -- I should probably add a cleaner way to do that.

I think I get "perfect" results when that's all set that way.

With such a scale (nearly 103%), I was expecting having values from 0 to
Post by Nouvelle Collection
something like 308 or 307 or 306. But here 299.60 seems a bit far away,
don't you think so ?
well:

In [48]: 308 / 1.03
Out[48]: 299.02912621359224

but there is antoher player here:

FloatCanvas keeps track of how the Canvas is zoomed an panned with a Scale
and the ViewPortCenter. -- so the center point was not integers, either, so
two sources of fudging.


So: if you really want exact coords, you can do that above to get them.

But the other option is simply to round the results, and accept that there
may be a off-by-one error sometimes.

If this is the only zoom level you need, then setting it like the above
might make sense. And if you want to suggest a cleaner API for that, I
could add it.

I don't think supporting "use integer scaling" as a general purpose feature
makes sense -- the whole idea of FloatCanvas is that coordinates are
floating point -- any arbitrary scale may make sense for a
given application -- there isn't really anything that special about
integers.
Post by Nouvelle Collection
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print self.canvas.Scale, x, y
That's correct, but perhaps unnecessary:

you've placed the image on the canvas at (0,0), so that should be the
corner. You don't need to get it from the bounding box. It's
also available from:

img.XY (and maybe img.Width and img.Height, if you need those)

HTH,

-Chris
--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker-***@public.gmane.org
Chris Barker
2014-03-21 23:39:51 UTC
Permalink
if you want to suggest a cleaner API for that, I could add it.
got it.

I made .Scale and .ViewPOrtCenter properties, so tehy can do the right
thing if you set them directly. So you can now do:

class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK
SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0), Height=img.GetHeight(),
Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)

self.canvas.Scale = 1.0
self.canvas.ViewPortCenter = (154, -17)

def OnMotion(self, event):
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print self.canvas.Scale, x, y

app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(350,200))
frame.Show(True)
app.MainLoop()

now in SVN, along with this demo.

-Chris
--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker-***@public.gmane.org
Nouvelle Collection
2014-03-21 23:53:42 UTC
Permalink
Thanks Chris,
I tried this demo, but here we easily get 0 to 308 because scale = 1.0 so
it's easy : it's 1:1 scale, and so it's normal that everything is integer.

The more tricky thing is to have 0 to 308 (or even 0 to 306/ 307) when
scale = 0.87 or scale = 1.03.

Do you have an idea on how to avoid having so many "lost pixels" ? Example
: I had x = -0.34 ... 299.60 when scale = 1.03 ==> this means that
299.60 to 308 are "lost pixels".

Jo
Post by Chris Barker
if you want to suggest a cleaner API for that, I could add it.
got it.
I made .Scale and .ViewPOrtCenter properties, so tehy can do the right
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK
SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0),
Height=img.GetHeight(), Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
self.canvas.Scale = 1.0
self.canvas.ViewPortCenter = (154, -17)
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print self.canvas.Scale, x, y
app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(350,200))
frame.Show(True)
app.MainLoop()
now in SVN, along with this demo.
-Chris
--
Christopher Barker, Ph.D.
Oceanographer
Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
_______________________________________________
FloatCanvas mailing list
http://mailman.paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas
Chris Barker - NOAA Federal
2014-03-22 00:32:39 UTC
Permalink
On Mar 21, 2014, at 4:54 PM, Nouvelle Collection <
nouvellecollection-***@public.gmane.org> wrote:

Thanks Chris,
I tried this demo, but here we easily get 0 to 308 because scale = 1.0 so
it's easy : it's 1:1 scale, and so it's normal that everything is integer.


Exactly -- if you need exact, then you need to use round numbers.

The more tricky thing is to have 0 to 308 (or even 0 to 306/ 307) when
scale = 0.87 or scale = 1.03.

Do you have an idea on how to avoid having so many "lost pixels" ? Example
: I had x = -0.34 ... 299.60 when scale = 1.03 ==> this means that
299.60 to 308 are "lost pixels".


Well, 3% doesn't seem like much, but 3% of 300 is 9. So you are drawing
your 308 pixel image with about 318 pixels on the screen.

Though it does seem like you should be able to get within 1 pixel of the
"right" value.

I think I was kn my machine.

One thing to check is when you get mouse move events. Perhaps they are not
coming every single pixel?

Try calling canvas.PixelToWorld() for points from 300-320. And see what you
get.

Or capture the raw mouse move event, and see what pixel cords you get.

What's you use case here? Why does this precision matter?

-Chris


Jo
Post by Chris Barker
if you want to suggest a cleaner API for that, I could add it.
got it.
I made .Scale and .ViewPOrtCenter properties, so tehy can do the right
wx.Frame.__init__(self, *args, **kwargs)
self.canvas =FloatCanvas.FloatCanvas(self, BackgroundColor = "DARK
SLATE BLUE")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
img = wx.Image('mypng(309x34).png')
A = self.canvas.AddScaledBitmap(img, (0,0),
Height=img.GetHeight(), Position = 'tl')
A.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
self.canvas.Scale = 1.0
self.canvas.ViewPortCenter = (154, -17)
x, y = event.HitCoords[0] - event.BoundingBox[0,0],
event.BoundingBox[1,1] - event.HitCoords[1]
print self.canvas.Scale, x, y
app = wx.App(0)
frame = TestFrame(None, title="Image hitcoords", size=(350,200))
frame.Show(True)
app.MainLoop()
now in SVN, along with this demo.
-Chris
--
Christopher Barker, Ph.D.
Oceanographer
Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
_______________________________________________
FloatCanvas mailing list
http://mailman.paulmcnett.com/cgi-bin/mailman/listinfo/floatcanvas
Loading...