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
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
QIM
Tools
pt2d
Commits
e7488d98
Commit
e7488d98
authored
3 months ago
by
Christian
Browse files
Options
Downloads
Patches
Plain Diff
Added export path feature
parent
ac48bd9a
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
GUI_draft_live.py
+166
-71
166 additions, 71 deletions
GUI_draft_live.py
with
166 additions
and
71 deletions
GUI_draft_live.py
+
166
−
71
View file @
e7488d98
...
@@ -69,7 +69,7 @@ class LabeledPointItem(QGraphicsEllipseItem):
...
@@ -69,7 +69,7 @@ class LabeledPointItem(QGraphicsEllipseItem):
self
.
_text_item
.
setPos
(
tx
,
ty
)
self
.
_text_item
.
setPos
(
tx
,
ty
)
def
set_pos
(
self
,
x
,
y
):
def
set_pos
(
self
,
x
,
y
):
"""
Positions the circle so
that
its center is at (x, y).
"""
"""
Positions the circle so its center is at (x, y).
"""
self
.
_x
=
x
self
.
_x
=
x
self
.
_y
=
y
self
.
_y
=
y
self
.
setPos
(
x
-
self
.
_r
,
y
-
self
.
_r
)
self
.
setPos
(
x
-
self
.
_r
,
y
-
self
.
_r
)
...
@@ -93,21 +93,24 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -93,21 +93,24 @@ class ImageGraphicsView(QGraphicsView):
# Zoom around mouse pointer
# Zoom around mouse pointer
self
.
setTransformationAnchor
(
QGraphicsView
.
AnchorUnderMouse
)
self
.
setTransformationAnchor
(
QGraphicsView
.
AnchorUnderMouse
)
# Image display
item
# Image display
self
.
image_item
=
QGraphicsPixmapItem
()
self
.
image_item
=
QGraphicsPixmapItem
()
self
.
scene
.
addItem
(
self
.
image_item
)
self
.
scene
.
addItem
(
self
.
image_item
)
# Parallel lists: anchor_points + LabeledPointItem
self
.
anchor_points
=
[]
# List[(x, y)]
self
.
anchor_points
=
[]
# List of (x, y)
self
.
point_items
=
[]
# LabeledPointItem objects
self
.
point_items
=
[]
# List of LabeledPointItem
self
.
full_path_points
=
[]
# QGraphicsEllipseItems for the path
# We'll store the entire path coords (smoothed) for reference
self
.
_full_path_xy
=
[]
self
.
dot_radius
=
4
self
.
dot_radius
=
4
self
.
path_radius
=
1
self
.
path_radius
=
1
self
.
radius_cost_image
=
2
# cost-lowering radius
self
.
radius_cost_image
=
2
self
.
_img_w
=
0
self
.
_img_w
=
0
self
.
_img_h
=
0
self
.
_img_h
=
0
#
For panning/draggin
g
#
Pan/Dra
g
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
...
@@ -117,15 +120,12 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -117,15 +120,12 @@ class ImageGraphicsView(QGraphicsView):
self
.
_was_dragging
=
False
self
.
_was_dragging
=
False
self
.
_dragging_idx
=
None
self
.
_dragging_idx
=
None
self
.
_drag_offset
=
(
0
,
0
)
self
.
_drag_offset
=
(
0
,
0
)
self
.
_drag_counter
=
0
# throttles path updates while dragging
self
.
_drag_counter
=
0
#
We will keep two copies of the c
ost image
#
C
ost image
s
self
.
cost_image_original
=
None
self
.
cost_image_original
=
None
self
.
cost_image
=
None
self
.
cost_image
=
None
# Path circles displayed
self
.
full_path_points
=
[]
# --------------------------------------------------------------------
# --------------------------------------------------------------------
# LOADING
# LOADING
# --------------------------------------------------------------------
# --------------------------------------------------------------------
...
@@ -142,11 +142,9 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -142,11 +142,9 @@ class ImageGraphicsView(QGraphicsView):
self
.
resetTransform
()
self
.
resetTransform
()
self
.
fitInView
(
self
.
image_item
,
Qt
.
KeepAspectRatio
)
self
.
fitInView
(
self
.
image_item
,
Qt
.
KeepAspectRatio
)
#
Place S/E at left
a
n
d
right
#
By default,
a
d
d
S/E
s_x
,
s_y
=
0.15
*
self
.
_img_w
,
0.5
*
self
.
_img_h
s_x
,
s_y
=
0.15
*
self
.
_img_w
,
0.5
*
self
.
_img_h
e_x
,
e_y
=
0.85
*
self
.
_img_w
,
0.5
*
self
.
_img_h
e_x
,
e_y
=
0.85
*
self
.
_img_w
,
0.5
*
self
.
_img_h
# Insert S/E
self
.
_insert_anchor_point
(
-
1
,
s_x
,
s_y
,
label
=
"
S
"
,
removable
=
False
,
z_val
=
100
,
radius
=
6
)
self
.
_insert_anchor_point
(
-
1
,
s_x
,
s_y
,
label
=
"
S
"
,
removable
=
False
,
z_val
=
100
,
radius
=
6
)
self
.
_insert_anchor_point
(
-
1
,
e_x
,
e_y
,
label
=
"
E
"
,
removable
=
False
,
z_val
=
100
,
radius
=
6
)
self
.
_insert_anchor_point
(
-
1
,
e_x
,
e_y
,
label
=
"
E
"
,
removable
=
False
,
z_val
=
100
,
radius
=
6
)
...
@@ -154,15 +152,11 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -154,15 +152,11 @@ class ImageGraphicsView(QGraphicsView):
# ANCHOR POINTS
# ANCHOR POINTS
# --------------------------------------------------------------------
# --------------------------------------------------------------------
def
_insert_anchor_point
(
self
,
idx
,
x
,
y
,
label
=
""
,
removable
=
True
,
z_val
=
0
,
radius
=
4
):
def
_insert_anchor_point
(
self
,
idx
,
x
,
y
,
label
=
""
,
removable
=
True
,
z_val
=
0
,
radius
=
4
):
"""
"""
Insert anchor at index=idx (or -1 => before E). Clamps x,y to image bounds.
"""
Insert at index=idx, or -1 => append just before E if E exists.
Clamps x,y so points can
'
t go outside the image.
"""
x_clamped
=
self
.
_clamp
(
x
,
radius
,
self
.
_img_w
-
radius
)
x_clamped
=
self
.
_clamp
(
x
,
radius
,
self
.
_img_w
-
radius
)
y_clamped
=
self
.
_clamp
(
y
,
radius
,
self
.
_img_h
-
radius
)
y_clamped
=
self
.
_clamp
(
y
,
radius
,
self
.
_img_h
-
radius
)
if
idx
<
0
:
if
idx
<
0
:
# If we have at least 2 anchors, the last is E => insert before it
if
len
(
self
.
anchor_points
)
>=
2
:
if
len
(
self
.
anchor_points
)
>=
2
:
idx
=
len
(
self
.
anchor_points
)
-
1
idx
=
len
(
self
.
anchor_points
)
-
1
else
:
else
:
...
@@ -171,31 +165,124 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -171,31 +165,124 @@ class ImageGraphicsView(QGraphicsView):
self
.
anchor_points
.
insert
(
idx
,
(
x_clamped
,
y_clamped
))
self
.
anchor_points
.
insert
(
idx
,
(
x_clamped
,
y_clamped
))
color
=
Qt
.
green
if
label
in
(
"
S
"
,
"
E
"
)
else
Qt
.
red
color
=
Qt
.
green
if
label
in
(
"
S
"
,
"
E
"
)
else
Qt
.
red
item
=
LabeledPointItem
(
item
=
LabeledPointItem
(
x_clamped
,
y_clamped
,
x_clamped
,
y_clamped
,
label
=
label
,
label
=
label
,
radius
=
radius
,
color
=
color
,
radius
=
radius
,
color
=
color
,
removable
=
removable
,
z_value
=
z_val
removable
=
removable
,
z_value
=
z_val
)
)
self
.
point_items
.
insert
(
idx
,
item
)
self
.
point_items
.
insert
(
idx
,
item
)
self
.
scene
.
addItem
(
item
)
self
.
scene
.
addItem
(
item
)
def
_add_guide_point
(
self
,
x
,
y
):
def
_add_guide_point
(
self
,
x
,
y
):
"""
Called when user left-clicks an empty spot. Insert a red guide point, recalc path.
"""
"""
User clicked => find the correct sub-path, insert the point in that sub-path.
"""
# clamp to image boundaries
x_clamped
=
self
.
_clamp
(
x
,
self
.
dot_radius
,
self
.
_img_w
-
self
.
dot_radius
)
x_clamped
=
self
.
_clamp
(
x
,
self
.
dot_radius
,
self
.
_img_w
-
self
.
dot_radius
)
y_clamped
=
self
.
_clamp
(
y
,
self
.
dot_radius
,
self
.
_img_h
-
self
.
dot_radius
)
y_clamped
=
self
.
_clamp
(
y
,
self
.
dot_radius
,
self
.
_img_h
-
self
.
dot_radius
)
# 1) revert cost
self
.
_revert_cost_to_original
()
self
.
_revert_cost_to_original
()
# 2) Insert new anchor
if
not
self
.
_full_path_xy
:
self
.
_insert_anchor_point
(
-
1
,
x_clamped
,
y_clamped
,
label
=
""
,
removable
=
True
,
z_val
=
1
,
radius
=
self
.
dot_radius
)
# If there's no existing path built, we just insert normally
self
.
_insert_anchor_point
(
-
1
,
x_clamped
,
y_clamped
,
label
=
""
,
removable
=
True
,
z_val
=
1
,
radius
=
self
.
dot_radius
)
else
:
# Insert the new anchor in between the correct anchors,
# by finding nearest coordinate in _full_path_xy, then
# walking left+right until we find bounding anchors.
self
.
_insert_anchor_between_subpath
(
x_clamped
,
y_clamped
)
# 3) Re-apply cost-lowering
self
.
_apply_all_guide_points_to_cost
()
self
.
_apply_all_guide_points_to_cost
()
# 4) Rebuild path
self
.
_rebuild_full_path
()
self
.
_rebuild_full_path
()
def
_insert_anchor_between_subpath
(
self
,
x_new
,
y_new
):
"""
Find the subpath bounding (x_new,y_new) and insert the new anchor accordingly.
"""
# If no path, fallback
if
not
self
.
_full_path_xy
:
self
.
_insert_anchor_point
(
-
1
,
x_new
,
y_new
)
return
# 1) Find nearest coordinate in the path
best_idx
=
None
best_d2
=
float
(
'
inf
'
)
for
i
,
(
px
,
py
)
in
enumerate
(
self
.
_full_path_xy
):
dx
=
px
-
x_new
dy
=
py
-
y_new
d2
=
dx
*
dx
+
dy
*
dy
if
d2
<
best_d2
:
best_d2
=
d2
best_idx
=
i
if
best_idx
is
None
:
# fallback
self
.
_insert_anchor_point
(
-
1
,
x_new
,
y_new
)
return
# 2) Identify bounding anchors by walking left / right
def
approx_equal
(
xa
,
ya
,
xb
,
yb
,
tol
=
1e-3
):
return
(
abs
(
xa
-
xb
)
<
tol
)
and
(
abs
(
ya
-
yb
)
<
tol
)
def
is_anchor
(
coord
):
cx
,
cy
=
coord
# check if (cx,cy) is approx any anchor in self.anchor_points
for
(
ax
,
ay
)
in
self
.
anchor_points
:
if
approx_equal
(
ax
,
ay
,
cx
,
cy
):
return
True
return
False
left_anchor_pt
=
None
right_anchor_pt
=
None
# walk left
iL
=
best_idx
while
iL
>=
0
:
px
,
py
=
self
.
_full_path_xy
[
iL
]
if
is_anchor
((
px
,
py
)):
left_anchor_pt
=
(
px
,
py
)
break
iL
-=
1
# walk right
iR
=
best_idx
while
iR
<
len
(
self
.
_full_path_xy
):
px
,
py
=
self
.
_full_path_xy
[
iR
]
if
is_anchor
((
px
,
py
)):
right_anchor_pt
=
(
px
,
py
)
break
iR
+=
1
if
not
left_anchor_pt
or
not
right_anchor_pt
:
# If we can't find bounding anchors => fallback
self
.
_insert_anchor_point
(
-
1
,
x_new
,
y_new
)
return
# 3) Find which anchor_points indices correspond to left_anchor_pt, right_anchor_pt
left_idx
=
None
right_idx
=
None
for
i
,
(
ax
,
ay
)
in
enumerate
(
self
.
anchor_points
):
if
approx_equal
(
ax
,
ay
,
left_anchor_pt
[
0
],
left_anchor_pt
[
1
]):
left_idx
=
i
if
approx_equal
(
ax
,
ay
,
right_anchor_pt
[
0
],
right_anchor_pt
[
1
]):
right_idx
=
i
if
left_idx
is
None
or
right_idx
is
None
:
# fallback
self
.
_insert_anchor_point
(
-
1
,
x_new
,
y_new
)
return
# We want the new anchor to be inserted right after left_idx,
# so that the subpath between left_idx and right_idx
# is effectively subdivided.
# This ensures anchor_points = [..., left_anchor, new_point, ..., right_anchor, ...]
insert_idx
=
right_idx
# But if left_idx < right_idx => we do insert_idx=left_idx+1
# in case we want them consecutive.
if
left_idx
<
right_idx
:
insert_idx
=
left_idx
+
1
else
:
# means the path might be reversed, or there's some tricky indexing
# We'll just do min or max
insert_idx
=
max
(
right_idx
,
left_idx
)
self
.
_insert_anchor_point
(
insert_idx
,
x_new
,
y_new
,
label
=
""
,
removable
=
True
,
z_val
=
1
,
radius
=
self
.
dot_radius
)
# --------------------------------------------------------------------
# --------------------------------------------------------------------
# COST IMAGE
# COST IMAGE
# --------------------------------------------------------------------
# --------------------------------------------------------------------
...
@@ -204,7 +291,6 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -204,7 +291,6 @@ class ImageGraphicsView(QGraphicsView):
self
.
cost_image
=
self
.
cost_image_original
.
copy
()
self
.
cost_image
=
self
.
cost_image_original
.
copy
()
def
_apply_all_guide_points_to_cost
(
self
):
def
_apply_all_guide_points_to_cost
(
self
):
"""
Lower cost around every REMOVABLE anchor (the red ones).
"""
if
self
.
cost_image
is
None
:
if
self
.
cost_image
is
None
:
return
return
for
i
,
(
ax
,
ay
)
in
enumerate
(
self
.
anchor_points
):
for
i
,
(
ax
,
ay
)
in
enumerate
(
self
.
anchor_points
):
...
@@ -234,12 +320,13 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -234,12 +320,13 @@ class ImageGraphicsView(QGraphicsView):
# PATH BUILDING
# PATH BUILDING
# --------------------------------------------------------------------
# --------------------------------------------------------------------
def
_rebuild_full_path
(
self
):
def
_rebuild_full_path
(
self
):
# Remove old path items
"""
Compute subpaths between anchors, smooth them, store all coords, display all of them.
"""
# Clear old path visuals
for
item
in
self
.
full_path_points
:
for
item
in
self
.
full_path_points
:
self
.
scene
.
removeItem
(
item
)
self
.
scene
.
removeItem
(
item
)
self
.
full_path_points
.
clear
()
self
.
full_path_points
.
clear
()
self
.
_full_path_xy
.
clear
()
# Build subpaths
if
len
(
self
.
anchor_points
)
<
2
or
self
.
cost_image
is
None
:
if
len
(
self
.
anchor_points
)
<
2
or
self
.
cost_image
is
None
:
return
return
...
@@ -251,29 +338,33 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -251,29 +338,33 @@ class ImageGraphicsView(QGraphicsView):
if
i
==
0
:
if
i
==
0
:
big_xy
.
extend
(
sub_xy
)
big_xy
.
extend
(
sub_xy
)
else
:
else
:
# avoid
duplic
ating the shared anchor
# avoid
repe
ating the shared anchor
if
len
(
sub_xy
)
>
1
:
if
len
(
sub_xy
)
>
1
:
big_xy
.
extend
(
sub_xy
[
1
:])
big_xy
.
extend
(
sub_xy
[
1
:])
# Smooth
ing with Savitzky-Golay
# Smooth
if we have enough points
if
len
(
big_xy
)
>=
7
:
if
len
(
big_xy
)
>=
7
:
arr_xy
=
np
.
array
(
big_xy
)
# shape (N,2)
arr_xy
=
np
.
array
(
big_xy
)
smoothed
=
savgol_filter
(
arr_xy
,
window_length
=
7
,
polyorder
=
1
,
axis
=
0
)
smoothed
=
savgol_filter
(
arr_xy
,
window_length
=
7
,
polyorder
=
1
,
axis
=
0
)
big_xy
=
smoothed
.
tolist
()
big_xy
=
smoothed
.
tolist
()
# Draw them
# We now store the entire path in _full_path_xy
self
.
_full_path_xy
=
big_xy
[:]
# Display ALL points
for
(
px
,
py
)
in
big_xy
:
for
(
px
,
py
)
in
big_xy
:
path_item
=
LabeledPointItem
(
px
,
py
,
label
=
""
,
radius
=
self
.
path_radius
,
path_item
=
LabeledPointItem
(
px
,
py
,
label
=
""
,
radius
=
self
.
path_radius
,
color
=
Qt
.
magenta
,
removable
=
False
,
z_value
=
0
)
color
=
Qt
.
magenta
,
removable
=
False
,
z_value
=
0
)
self
.
full_path_points
.
append
(
path_item
)
self
.
full_path_points
.
append
(
path_item
)
self
.
scene
.
addItem
(
path_item
)
self
.
scene
.
addItem
(
path_item
)
#
Ensure S/E remain
on top
#
Keep S/E
on top
for
p_item
in
self
.
point_items
:
for
p_item
in
self
.
point_items
:
if
p_item
.
_text_item
:
if
p_item
.
_text_item
:
p_item
.
setZValue
(
100
)
p_item
.
setZValue
(
100
)
def
_compute_subpath_xy
(
self
,
xA
,
yA
,
xB
,
yB
):
def
_compute_subpath_xy
(
self
,
xA
,
yA
,
xB
,
yB
):
"""
Return the raw path from (xA,yA)->(xB,yB).
"""
if
self
.
cost_image
is
None
:
if
self
.
cost_image
is
None
:
return
[]
return
[]
h
,
w
=
self
.
cost_image
.
shape
h
,
w
=
self
.
cost_image
.
shape
...
@@ -299,10 +390,9 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -299,10 +390,9 @@ class ImageGraphicsView(QGraphicsView):
self
.
_was_dragging
=
False
self
.
_was_dragging
=
False
self
.
_press_view_pos
=
event
.
pos
()
self
.
_press_view_pos
=
event
.
pos
()
#
Check
if user click
ed
near an existing anchor => drag
#
See
if user
is
click
ing
near an existing anchor => drag
it
idx
=
self
.
_find_item_near
(
event
.
pos
(),
threshold
=
10
)
idx
=
self
.
_find_item_near
(
event
.
pos
(),
threshold
=
10
)
if
idx
is
not
None
:
if
idx
is
not
None
:
# drag existing anchor
self
.
_dragging_idx
=
idx
self
.
_dragging_idx
=
idx
self
.
_drag_counter
=
0
self
.
_drag_counter
=
0
...
@@ -313,19 +403,19 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -313,19 +403,19 @@ class ImageGraphicsView(QGraphicsView):
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
return
return
else
:
else
:
#
If
no anchor
near => user is placing
a new point
# no anchor
=> we'll add
a new point
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
setDragMode
(
QGraphicsView
.
ScrollHandDrag
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ClosedHandCursor
)
elif
event
.
button
()
==
Qt
.
RightButton
:
elif
event
.
button
()
==
Qt
.
RightButton
:
# Right-click => remove
point if it's
removable
# Right-click => remove
anchor if
removable
self
.
_remove_point_by_click
(
event
.
pos
())
self
.
_remove_point_by_click
(
event
.
pos
())
super
().
mousePressEvent
(
event
)
super
().
mousePressEvent
(
event
)
def
mouseMoveEvent
(
self
,
event
):
def
mouseMoveEvent
(
self
,
event
):
if
self
.
_dragging_idx
is
not
None
:
if
self
.
_dragging_idx
is
not
None
:
#
d
ragging
an
anchor
#
D
ragging anchor
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
x_new
=
scene_pos
.
x
()
-
self
.
_drag_offset
[
0
]
x_new
=
scene_pos
.
x
()
-
self
.
_drag_offset
[
0
]
y_new
=
scene_pos
.
y
()
-
self
.
_drag_offset
[
1
]
y_new
=
scene_pos
.
y
()
-
self
.
_drag_offset
[
1
]
...
@@ -338,15 +428,13 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -338,15 +428,13 @@ class ImageGraphicsView(QGraphicsView):
self
.
_drag_counter
+=
1
self
.
_drag_counter
+=
1
if
self
.
_drag_counter
>=
4
:
if
self
.
_drag_counter
>=
4
:
# partial path update
=> revert cost, reapply, rebuild
# partial path update
self
.
_drag_counter
=
0
self
.
_drag_counter
=
0
self
.
_revert_cost_to_original
()
self
.
_revert_cost_to_original
()
self
.
_apply_all_guide_points_to_cost
()
self
.
_apply_all_guide_points_to_cost
()
# anchor_points updated
self
.
anchor_points
[
self
.
_dragging_idx
]
=
(
x_clamped
,
y_clamped
)
self
.
anchor_points
[
self
.
_dragging_idx
]
=
(
x_clamped
,
y_clamped
)
self
.
_rebuild_full_path
()
self
.
_rebuild_full_path
()
return
else
:
else
:
if
self
.
_mouse_pressed
and
(
event
.
buttons
()
&
Qt
.
LeftButton
):
if
self
.
_mouse_pressed
and
(
event
.
buttons
()
&
Qt
.
LeftButton
):
dist
=
(
event
.
pos
()
-
self
.
_press_view_pos
).
manhattanLength
()
dist
=
(
event
.
pos
()
-
self
.
_press_view_pos
).
manhattanLength
()
...
@@ -362,7 +450,7 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -362,7 +450,7 @@ class ImageGraphicsView(QGraphicsView):
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
self
.
viewport
().
setCursor
(
Qt
.
ArrowCursor
)
if
self
.
_dragging_idx
is
not
None
:
if
self
.
_dragging_idx
is
not
None
:
#
done
dragging => final
path
update
#
finished
dragging => final update
idx
=
self
.
_dragging_idx
idx
=
self
.
_dragging_idx
self
.
_dragging_idx
=
None
self
.
_dragging_idx
=
None
self
.
_drag_offset
=
(
0
,
0
)
self
.
_drag_offset
=
(
0
,
0
)
...
@@ -374,8 +462,9 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -374,8 +462,9 @@ class ImageGraphicsView(QGraphicsView):
self
.
_revert_cost_to_original
()
self
.
_revert_cost_to_original
()
self
.
_apply_all_guide_points_to_cost
()
self
.
_apply_all_guide_points_to_cost
()
self
.
_rebuild_full_path
()
self
.
_rebuild_full_path
()
else
:
else
:
# If
no
t dragging =>
place a
new guide point
# If
user wasn'
t dragging =>
add
new guide point
if
not
self
.
_was_dragging
:
if
not
self
.
_was_dragging
:
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
scene_pos
=
self
.
mapToScene
(
event
.
pos
())
x
,
y
=
scene_pos
.
x
(),
scene_pos
.
y
()
x
,
y
=
scene_pos
.
x
(),
scene_pos
.
y
()
...
@@ -387,7 +476,7 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -387,7 +476,7 @@ class ImageGraphicsView(QGraphicsView):
idx
=
self
.
_find_item_near
(
view_pos
,
threshold
=
10
)
idx
=
self
.
_find_item_near
(
view_pos
,
threshold
=
10
)
if
idx
is
None
:
if
idx
is
None
:
return
return
# skip if
it's
S/E
# skip if S/E
if
not
self
.
point_items
[
idx
].
is_removable
():
if
not
self
.
point_items
[
idx
].
is_removable
():
return
return
...
@@ -402,8 +491,9 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -402,8 +491,9 @@ class ImageGraphicsView(QGraphicsView):
def
_find_item_near
(
self
,
view_pos
,
threshold
=
10
):
def
_find_item_near
(
self
,
view_pos
,
threshold
=
10
):
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
()
min_dist
=
float
(
'
inf
'
)
closest_idx
=
None
closest_idx
=
None
min_dist
=
float
(
'
inf
'
)
for
i
,
itm
in
enumerate
(
self
.
point_items
):
for
i
,
itm
in
enumerate
(
self
.
point_items
):
d
=
itm
.
distance_to
(
x_click
,
y_click
)
d
=
itm
.
distance_to
(
x_click
,
y_click
)
if
d
<
min_dist
:
if
d
<
min_dist
:
...
@@ -417,10 +507,8 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -417,10 +507,8 @@ class ImageGraphicsView(QGraphicsView):
# ZOOM
# ZOOM
# --------------------------------------------------------------------
# --------------------------------------------------------------------
def
wheelEvent
(
self
,
event
):
def
wheelEvent
(
self
,
event
):
"""
Zoom in/out with mouse wheel.
"""
zoom_in_factor
=
1.25
zoom_in_factor
=
1.25
zoom_out_factor
=
1
/
zoom_in_factor
zoom_out_factor
=
1
/
zoom_in_factor
if
event
.
angleDelta
().
y
()
>
0
:
if
event
.
angleDelta
().
y
()
>
0
:
self
.
scale
(
zoom_in_factor
,
zoom_in_factor
)
self
.
scale
(
zoom_in_factor
,
zoom_in_factor
)
else
:
else
:
...
@@ -442,9 +530,10 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -442,9 +530,10 @@ class ImageGraphicsView(QGraphicsView):
for
p
in
self
.
full_path_points
:
for
p
in
self
.
full_path_points
:
self
.
scene
.
removeItem
(
p
)
self
.
scene
.
removeItem
(
p
)
self
.
full_path_points
.
clear
()
self
.
full_path_points
.
clear
()
self
.
_full_path_xy
.
clear
()
def
clear_guide_points
(
self
):
def
clear_guide_points
(
self
):
"""
Remove all removable
(guide)
anchors, keep S/E.
Then r
ebuild.
"""
"""
Remove all removable anchors, keep S/E.
R
ebuild
path
.
"""
i
=
0
i
=
0
while
i
<
len
(
self
.
anchor_points
):
while
i
<
len
(
self
.
anchor_points
):
if
self
.
point_items
[
i
].
is_removable
():
if
self
.
point_items
[
i
].
is_removable
():
...
@@ -454,14 +543,19 @@ class ImageGraphicsView(QGraphicsView):
...
@@ -454,14 +543,19 @@ class ImageGraphicsView(QGraphicsView):
else
:
else
:
i
+=
1
i
+=
1
for
it
em
in
self
.
full_path_points
:
for
it
in
self
.
full_path_points
:
self
.
scene
.
removeItem
(
it
em
)
self
.
scene
.
removeItem
(
it
)
self
.
full_path_points
.
clear
()
self
.
full_path_points
.
clear
()
self
.
_full_path_xy
.
clear
()
self
.
_revert_cost_to_original
()
self
.
_revert_cost_to_original
()
self
.
_apply_all_guide_points_to_cost
()
self
.
_apply_all_guide_points_to_cost
()
self
.
_rebuild_full_path
()
self
.
_rebuild_full_path
()
def
get_full_path_xy
(
self
):
"""
Return the entire path (x,y) array after smoothing.
"""
return
self
.
_full_path_xy
class
MainWindow
(
QMainWindow
):
class
MainWindow
(
QMainWindow
):
def
__init__
(
self
):
def
__init__
(
self
):
...
@@ -482,10 +576,10 @@ class MainWindow(QMainWindow):
...
@@ -482,10 +576,10 @@ class MainWindow(QMainWindow):
self
.
btn_load_image
.
clicked
.
connect
(
self
.
load_image
)
self
.
btn_load_image
.
clicked
.
connect
(
self
.
load_image
)
btn_layout
.
addWidget
(
self
.
btn_load_image
)
btn_layout
.
addWidget
(
self
.
btn_load_image
)
# Export P
oints
# Export P
ath
self
.
btn_export_p
oints
=
QPushButton
(
"
Export P
oints
"
)
self
.
btn_export_p
ath
=
QPushButton
(
"
Export P
ath
"
)
self
.
btn_export_p
oints
.
clicked
.
connect
(
self
.
export_p
oints
)
self
.
btn_export_p
ath
.
clicked
.
connect
(
self
.
export_p
ath
)
btn_layout
.
addWidget
(
self
.
btn_export_p
oints
)
btn_layout
.
addWidget
(
self
.
btn_export_p
ath
)
# Clear Points
# Clear Points
self
.
btn_clear_points
=
QPushButton
(
"
Clear Points
"
)
self
.
btn_clear_points
=
QPushButton
(
"
Clear Points
"
)
...
@@ -497,7 +591,6 @@ class MainWindow(QMainWindow):
...
@@ -497,7 +591,6 @@ class MainWindow(QMainWindow):
self
.
resize
(
900
,
600
)
self
.
resize
(
900
,
600
)
def
load_image
(
self
):
def
load_image
(
self
):
"""
Open file dialog, load image, compute cost image, store in view.
"""
options
=
QFileDialog
.
Options
()
options
=
QFileDialog
.
Options
()
file_path
,
_
=
QFileDialog
.
getOpenFileName
(
file_path
,
_
=
QFileDialog
.
getOpenFileName
(
self
,
"
Open Image
"
,
""
,
self
,
"
Open Image
"
,
""
,
...
@@ -510,23 +603,25 @@ class MainWindow(QMainWindow):
...
@@ -510,23 +603,25 @@ class MainWindow(QMainWindow):
self
.
image_view
.
cost_image_original
=
cost_img
self
.
image_view
.
cost_image_original
=
cost_img
self
.
image_view
.
cost_image
=
cost_img
.
copy
()
self
.
image_view
.
cost_image
=
cost_img
.
copy
()
def
export_points
(
self
):
def
export_path
(
self
):
if
not
self
.
image_view
.
anchor_points
:
"""
Export the full path (x,y) as a .npy file.
"""
print
(
"
No anchor points to export.
"
)
full_xy
=
self
.
image_view
.
get_full_path_xy
()
if
not
full_xy
:
print
(
"
No path to export.
"
)
return
return
options
=
QFileDialog
.
Options
()
options
=
QFileDialog
.
Options
()
file_path
,
_
=
QFileDialog
.
getSaveFileName
(
file_path
,
_
=
QFileDialog
.
getSaveFileName
(
self
,
"
Export P
oints
"
,
""
,
self
,
"
Export P
ath
"
,
""
,
"
NumPy Files (*.npy);;All Files (*)
"
,
"
NumPy Files (*.npy);;All Files (*)
"
,
options
=
options
options
=
options
)
)
if
file_path
:
if
file_path
:
points_
arr
ay
=
np
.
array
(
self
.
image_view
.
anchor_points
)
arr
=
np
.
array
(
full_xy
)
np
.
save
(
file_path
,
points_
arr
ay
)
np
.
save
(
file_path
,
arr
)
print
(
f
"
Exported
{
len
(
points_
arr
ay
)
}
points to
{
file_path
}
"
)
print
(
f
"
Exported
path with
{
len
(
arr
)
}
points to
{
file_path
}
"
)
def
clear_points
(
self
):
def
clear_points
(
self
):
"""
Remove all removable anchors (guide points), keep S/E in place.
"""
self
.
image_view
.
clear_guide_points
()
self
.
image_view
.
clear_guide_points
()
def
closeEvent
(
self
,
event
):
def
closeEvent
(
self
,
event
):
...
...
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