Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Source - https://stackoverflow.com/a/78380165
# Posted by Stefan, modified by community. See post 'Timeline' for change history
# Retrieved 2026-04-14, License - CC BY-SA 4.0

# Set update schedule for GitHub Actions

version: 2
updates:

- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
fail-fast: false
matrix:
runs-on: [ubuntu-latest]
python-version: [ "3.10", "3.12" ]
python-version: [ "3.10", "3.12", "3.14" ]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "WCONViewer"
version = "0.3.0"
version = "0.3.1"
description = "Viewer for WCON (Worm tracker Commons Object Notation) files in 2D"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
72 changes: 40 additions & 32 deletions wconviewer/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,40 +135,42 @@ def signed(vx, vy):
}


def compute_body_curvature(ts, x, y):
def compute_body_curvature(ts, x, y, head="L"):
"""
Angle (degrees, 180 = straight) between the points 2 before and 2 after each
body point, for all body points except the 2 at each end.

Returns an array of shape (n_frames, n_body_points - 4).
"""
body_curv_data = np.zeros((len(ts), x.shape[0] - 4))

for ti in range(len(ts)):
for i in range(x.shape[0])[2:-2]:
x1 = x[i - 2, ti]
xc = x[i, ti]
x2 = x[i + 2, ti]

y1 = y[i - 2, ti]
yc = y[i, ti]
y2 = y[i + 2, ti]

a1 = math.atan2(y1 - yc, x1 - xc)
a2 = math.atan2(y2 - yc, x2 - xc)
angle = a2 - a1
if angle < 0:
angle += 2 * math.pi
if angle > 2 * math.pi:
angle = 2 * math.pi - angle
The points are taken in order from the head to the tail, using the WCON head
value ("L", "R" or "?", single or per frame), so the first column is nearest
the head. If the head is "R" the order of the points is reversed for that
frame, and where the head is unknown ("?" or null), point 0 is assumed to be
the head.

deg = 360 * (angle / (2 * math.pi))
Returns an array of shape (n_frames, n_body_points - 4), or None if there
are too few body points (e.g. single point trackers) to calculate it.
"""
n_points = x.shape[0]
if n_points < 5:
print_(
"Cannot calculate body curvature with only %i body points (need 5+)"
% n_points
)
return None

body_curv_data[ti][i - 2] = deg
# Order the points from head to tail in each frame
reverse = np.array([h == "R" for h in np.broadcast_to(head, len(ts))])
order = np.arange(n_points)[:, None]
order = np.where(reverse[None, :], n_points - 1 - order, order)
frames = np.arange(len(ts))[None, :]
x = x[order, frames]
y = y[order, frames]

# print("At t=%s, i=%s: angle from between (%s,%s) - (%s,%s) - (%s,%s) = %s, %sdeg"%(ts[ti], i, x1,y1,xc,yc,x2,y2,angle,deg))
# Angles of the lines from each body point to the points 2 before and 2 after
a1 = np.arctan2(y[:-4] - y[2:-2], x[:-4] - x[2:-2])
a2 = np.arctan2(y[4:] - y[2:-2], x[4:] - x[2:-2])
angle = np.mod(a2 - a1, 2 * np.pi)

return body_curv_data
return np.degrees(angle).T


def generate_midlines_plot(ts, x, y, spacing=0.05, save_to=None, t_units="s"):
Expand Down Expand Up @@ -217,7 +219,7 @@ def generate_speeds_plot(
Plot the output of compute_speeds, with the same x/y swap as plot_midlines.
"""
fig = plt.figure()
info = "Speed of worm, signed by direction, and in x & y (along body) directions"
info = "Speed of worm"
window = speeds.get("window", 1)
if window > 1:
info += ", over %i time steps" % window
Expand Down Expand Up @@ -300,24 +302,30 @@ def generate_body_curvature_plot(
t_units="s",
):
"""
Plot the output of compute_body_curvature as a heat map of curvature against
time and position along the body.
Plot the output of compute_body_curvature as a heat map (a kymograph) of
curvature against time and position along the body, with the head at the
bottom and the tail at the top. Returns None if body_curv_data is None.
"""
if body_curv_data is None:
return None

fig = plt.figure()

plt.xlabel("Time (%s)" % t_units)
plt.ylabel("Percentage along worm")

plot0 = plt.imshow(
body_curv_data.transpose(),
interpolation="nearest",
aspect="auto",
origin="lower",
cmap=cmap,
vmin=min_curvature,
vmax=max_curvature,
)
ax = plt.gca()
info = "Propagation of curvature along body of worm (180=straight)"
# Just label the first (head) and last (tail) body points
ax.set_yticks([0, body_curv_data.shape[1] - 1], ["Head", "Tail"])
info = "Worm body curvature"
fig.canvas.manager.set_window_title(info)
plt.title(info)

Expand Down Expand Up @@ -384,7 +392,7 @@ def analyse_wcon_file(
if plot_body_curvature:
generate_body_curvature_plot(
ts,
compute_body_curvature(ts, x, y),
compute_body_curvature(ts, x, y, head),
save_to=save_figure_curvature_to,
t_units=t_units,
)
Expand Down
15 changes: 9 additions & 6 deletions wconviewer/WormView.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def update(self, ti):
(self.head_trace_plot,) = self.ax.plot(
[],
[],
color="grey",
color="blue",
marker=".",
markersize=0.5,
linewidth=0,
Expand Down Expand Up @@ -532,9 +532,9 @@ def show_worm_view(
# with a function giving the x value of the line for a time index
time_lines = []

def add_time_line(fig_other, x_for_index, window_x):
def add_time_line(fig_other, x_for_index, window_x, color="red"):
time_lines.append(
(fig_other.axes[0].axvline(0, color="red", linewidth=1), x_for_index)
(fig_other.axes[0].axvline(0, color=color, linewidth=1), x_for_index)
)
# Only the Qt backends can position the window (not e.g. Agg)
window = getattr(fig_other.canvas.manager, "window", None)
Expand All @@ -547,12 +547,15 @@ def add_time_line(fig_other, x_for_index, window_x):
generate_body_curvature_plot,
)

body_curv_data = compute_body_curvature(wv.wcon.times, wv.wcon.x, wv.wcon.y)
body_curv_data = compute_body_curvature(
wv.wcon.times, wv.wcon.x, wv.wcon.y, wv.wcon.head
)
fig_curv = generate_body_curvature_plot(
wv.wcon.times, body_curv_data, t_units=wv.wcon.t_units_used
)
# x axis is the time index
add_time_line(fig_curv, lambda ti: ti, 10)
if fig_curv is not None:
# x axis is the time index
add_time_line(fig_curv, lambda ti: ti, 10, color="black")
else:
print(" - Not showing curvature plot")

Expand Down
Loading