Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
pt2d
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
QIM
Tools
pt2d
Commits
7f9fa646
Commit
7f9fa646
authored
6 months ago
by
s224389
Browse files
Options
Downloads
Patches
Plain Diff
Added ability to drag points around. Also implemented point class for better tracking.
parent
b69dbdf7
No related branches found
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
GUI_draft.py
+120
-42
120 additions, 42 deletions
GUI_draft.py
with
120 additions
and
42 deletions
GUI_draft.py
+
120
−
42
View file @
7f9fa646
...
@@ -8,6 +8,42 @@ from PyQt5.QtWidgets import (
...
@@ -8,6 +8,42 @@ from PyQt5.QtWidgets import (
)
)
from
PyQt5.QtGui
import
QPixmap
,
QPen
,
QBrush
from
PyQt5.QtGui
import
QPixmap
,
QPen
,
QBrush
from
PyQt5.QtCore
import
Qt
,
QRectF
from
PyQt5.QtCore
import
Qt
,
QRectF
import
math
class
PointItem
(
QGraphicsEllipseItem
):
"""
Represents a single draggable point on the scene.
"""
def
__init__
(
self
,
x
,
y
,
radius
=
4
,
parent
=
None
):
super
().
__init__
(
x
-
radius
,
y
-
radius
,
2
*
radius
,
2
*
radius
,
parent
)
self
.
_x
=
x
self
.
_y
=
y
self
.
_r
=
radius
self
.
setBrush
(
QBrush
(
Qt
.
red
))
self
.
setPen
(
QPen
(
Qt
.
red
))
def
get_pos
(
self
):
"""
Return the (x, y) of this point in scene coords.
"""
return
(
self
.
_x
,
self
.
_y
)
def
set_pos
(
self
,
x
,
y
):
"""
Move point to (x,y).
This also updates the ellipse rectangle so the visual dot moves.
"""
self
.
_x
=
x
self
.
_y
=
y
self
.
setRect
(
x
-
self
.
_r
,
y
-
self
.
_r
,
2
*
self
.
_r
,
2
*
self
.
_r
)
def
distance_to
(
self
,
x_other
,
y_other
):
"""
Euclidean distance from this point to arbitrary (x_other, y_other).
"""
dx
=
self
.
_x
-
x_other
dy
=
self
.
_y
-
y_other
return
math
.
sqrt
(
dx
*
dx
+
dy
*
dy
)
class
ImageGraphicsView
(
QGraphicsView
):
class
ImageGraphicsView
(
QGraphicsView
):
...
@@ -30,7 +66,6 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -30,7 +66,6 @@ class ImageGraphicsView(QGraphicsView):
# Points and dot items
# Points and dot items
self
.
points
=
[]
self
.
points
=
[]
self
.
point_items
=
[]
self
.
editor_mode
=
False
self
.
editor_mode
=
False
self
.
dot_radius
=
4
self
.
dot_radius
=
4
...
@@ -43,6 +78,8 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -43,6 +78,8 @@ class ImageGraphicsView(QGraphicsView):
self
.
_press_view_pos
=
None
self
.
_press_view_pos
=
None
self
.
_drag_threshold
=
5
self
.
_drag_threshold
=
5
self
.
_was_dragging
=
False
self
.
_was_dragging
=
False
self
.
_dragging_idx
=
None
self
.
_drag_offset
=
(
0
,
0
)
def
load_image
(
self
,
image_path
):
def
load_image
(
self
,
image_path
):
"""
Load an image and fit it in the view.
"""
"""
Load an image and fit it in the view.
"""
...
@@ -56,7 +93,6 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -56,7 +93,6 @@ class ImageGraphicsView(QGraphicsView):
# Clear existing dots from previous image
# Clear existing dots from previous image
self
.
points
.
clear
()
self
.
points
.
clear
()
self
.
_clear_point_items
()
# Reset transform then fit image in view
# Reset transform then fit image in view
self
.
resetTransform
()
self
.
resetTransform
()
...
@@ -72,7 +108,28 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -72,7 +108,28 @@ class ImageGraphicsView(QGraphicsView):
self
.
_was_dragging
=
False
self
.
_was_dragging
=
False
self
.
_press_view_pos
=
event
.
pos
()
self
.
_press_view_pos
=
event
.
pos
()
# Switch to closed-hand cursor while left mouse is down
if
self
.
editor_mode
:
# Check if we're near a point
idx
=
self
.
_find_point_near
(
event
.
pos
(),
threshold
=
10
)
if
idx
is
not
None
:
# Start dragging that point
self
.
_dragging_idx
=
idx
# Compute offset so point doesn't jump if clicked off-center
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
px
,
py
=
self
.
points
[
idx
].
get_pos
()
self
.
_drag_offset
=
(
scene_pos
.
x
()
-
px
,
scene_pos
.
y
()
-
py
)
# Temporarily disable QGraphicsView's panning
self
.
setDragMode
(
QGraphicsView
.
NoDrag
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
return
else
:
# Not near a point, so we do normal panning
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
else
:
# Editor mode is off => always do normal panning
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
elif
event
.
button
()
==
Qt
.
RightButton
:
elif
event
.
button
()
==
Qt
.
RightButton
:
...
@@ -82,41 +139,46 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -82,41 +139,46 @@ class ImageGraphicsView(QGraphicsView):
super
().
mousePressEvent
(
event
)
super
().
mousePressEvent
(
event
)
def
mouseMoveEvent
(
self
,
event
):
def
mouseMoveEvent
(
self
,
event
):
"""
if
self
.
_dragging_idx
is
not
None
:
If movement > _drag_threshold: consider it a drag.
# Move that point to new coords
The actual panning is handled by QGraphicsView in ScrollHandDrag mode.
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
"""
x_new
=
scene_pos
.
x
()
-
self
.
_drag_offset
[
0
]
# Check if the mouse is being dragged
y_new
=
scene_pos
.
y
()
-
self
.
_drag_offset
[
1
]
self
.
points
[
self
.
_dragging_idx
].
set_pos
(
x_new
,
y_new
)
return
# Skip QGraphicsView's panning logic
else
:
# Old logic: if movement > threshold -> set _was_dragging = True
if
self
.
_mouse_pressed
and
(
event
.
buttons
()
&
Qt
.
LeftButton
):
if
self
.
_mouse_pressed
and
(
event
.
buttons
()
&
Qt
.
LeftButton
):
# If the mouse moved more than the threshold, consider it a drag
dist
=
(
event
.
pos
()
-
self
.
_press_view_pos
).
manhattanLength
()
dist
=
(
event
.
pos
()
-
self
.
_press_view_pos
).
manhattanLength
()
if
dist
>
self
.
_drag_threshold
:
if
dist
>
self
.
_drag_threshold
:
self
.
_was_dragging
=
True
self
.
_was_dragging
=
True
super
().
mouseMoveEvent
(
event
)
super
().
mouseMoveEvent
(
event
)
def
mouseReleaseEvent
(
self
,
event
):
def
mouseReleaseEvent
(
self
,
event
):
"""
After releasing the left button, go back to arrow cursor.
If it wasn
'
t a drag, treat as a click (Editor Mode: add dot).
"""
# Let QGraphicsView handle release first
super
().
mouseReleaseEvent
(
event
)
super
().
mouseReleaseEvent
(
event
)
if
event
.
button
()
==
Qt
.
LeftButton
and
self
.
_mouse_pressed
:
if
event
.
button
()
==
Qt
.
LeftButton
and
self
.
_mouse_pressed
:
self
.
_mouse_pressed
=
False
self
.
_mouse_pressed
=
False
# Always go back to arrow cursor AFTER letting QGraphicsView handle release
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
if
not
self
.
_was_dragging
:
# If we were dragging a point, stop.
# It's a click: if editor mode is ON add a dot
if
self
.
_dragging_idx
is
not
None
:
if
self
.
editor_mode
:
self
.
_dragging_idx
=
None
self
.
_drag_offset
=
(
0
,
0
)
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
else
:
# We were NOT dragging a point => check if it was a click to add a new point
if
not
self
.
_was_dragging
and
self
.
editor_mode
:
self
.
_add_point
(
event
.
pos
())
self
.
_add_point
(
event
.
pos
())
self
.
_was_dragging
=
False
self
.
_was_dragging
=
False
def
wheelEvent
(
self
,
event
):
def
wheelEvent
(
self
,
event
):
"""
Mouse wheel = zoom.
"""
"""
Mouse wheel = zoom.
"""
zoom_in_factor
=
1.25
zoom_in_factor
=
1.25
...
@@ -136,9 +198,8 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -136,9 +198,8 @@ class ImageGraphicsView(QGraphicsView):
scene_pos
=
self
.
mapToScene
(
view_pos
)
scene_pos
=
self
.
mapToScene
(
view_pos
)
x
,
y
=
scene_pos
.
x
(),
scene_pos
.
y
()
x
,
y
=
scene_pos
.
x
(),
scene_pos
.
y
()
self
.
points
.
append
((
x
,
y
))
dot
=
PointItem
(
x
,
y
,
radius
=
self
.
dot_radius
)
dot
=
self
.
_create_dot_item
(
x
,
y
)
self
.
points
.
append
(
dot
)
self
.
point_items
.
append
(
dot
)
self
.
scene
.
addItem
(
dot
)
self
.
scene
.
addItem
(
dot
)
def
_remove_point
(
self
,
view_pos
):
def
_remove_point
(
self
,
view_pos
):
...
@@ -146,22 +207,19 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -146,22 +207,19 @@ class ImageGraphicsView(QGraphicsView):
scene_pos
=
self
.
mapToScene
(
view_pos
)
scene_pos
=
self
.
mapToScene
(
view_pos
)
x_click
,
y_click
=
scene_pos
.
x
(),
scene_pos
.
y
()
x_click
,
y_click
=
scene_pos
.
x
(),
scene_pos
.
y
()
# Define threshold for removing a point
threshold
=
10
threshold
=
10
closest_idx
=
None
closest_idx
=
None
min_dist
=
float
(
'
inf
'
)
min_dist
=
float
(
'
inf
'
)
# Find the closest point to the click
for
i
,
point_item
in
enumerate
(
self
.
points
):
for
i
,
(
x
,
y
)
in
enumerate
(
self
.
points
):
dist
=
point_item
.
distance_to
(
x_click
,
y_click
)
dist_sq
=
(
x
-
x_click
)
**
2
+
(
y
-
y_click
)
**
2
if
dist
<
min_dist
:
if
dist_sq
<
min_dist
:
min_dist
=
dist
min_dist
=
dist_sq
closest_idx
=
i
closest_idx
=
i
# Remove the closest point if it's within the threshold
# Remove if within threshold
if
closest_idx
is
not
None
and
min_dist
<=
threshold
**
2
:
if
closest_idx
is
not
None
and
min_dist
<=
threshold
:
self
.
scene
.
removeItem
(
self
.
point_items
[
closest_idx
])
self
.
scene
.
removeItem
(
self
.
points
[
closest_idx
])
del
self
.
point_items
[
closest_idx
]
del
self
.
points
[
closest_idx
]
del
self
.
points
[
closest_idx
]
def
_create_dot_item
(
self
,
x
,
y
):
def
_create_dot_item
(
self
,
x
,
y
):
...
@@ -174,9 +232,29 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -174,9 +232,29 @@ class ImageGraphicsView(QGraphicsView):
def
_clear_point_items
(
self
):
def
_clear_point_items
(
self
):
"""
Remove all dot items from the scene.
"""
"""
Remove all dot items from the scene.
"""
for
item
in
self
.
point_items
:
for
p
in
self
.
points
:
self
.
scene
.
removeItem
(
item
)
self
.
scene
.
removeItem
(
p
)
self
.
point_items
=
[]
self
.
points
.
clear
()
def
_find_point_near
(
self
,
view_pos
,
threshold
=
10
):
scene_pos
=
self
.
mapToScene
(
view_pos
)
x_click
,
y_click
=
scene_pos
.
x
(),
scene_pos
.
y
()
closest_idx
=
None
min_dist
=
float
(
'
inf
'
)
for
i
,
p
in
enumerate
(
self
.
points
):
dist
=
p
.
distance_to
(
x_click
,
y_click
)
if
dist
<
min_dist
:
min_dist
=
dist
closest_idx
=
i
if
closest_idx
is
not
None
and
min_dist
<=
threshold
:
return
closest_idx
return
None
class
MainWindow
(
QMainWindow
):
class
MainWindow
(
QMainWindow
):
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment