flowtracks 1.6.2.dev6__py3-none-any.whl → 1.6.2.dev12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowtracks/_version.py +3 -3
- flowtracks/clean.py +202 -0
- flowtracks/graphics.py +194 -0
- flowtracks/merge.py +150 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/METADATA +1 -1
- flowtracks-1.6.2.dev12.dist-info/RECORD +55 -0
- flowtracks-1.6.2.dev6.dist-info/RECORD +0 -53
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/batch_Lagrangian_to_Eulerian.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/hdf5_scene_analysis.ipynb +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/linking_trajectories.ipynb +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_dual_rig_trajectories.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_flowtracks.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_hdf5_vs_zarr_scene_analysis.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_linking_trajectories_zarr.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_phase_averaging_demo.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_phase_metrics.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_vtk_writers_preview.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/marimo_zarr_dashboard.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/repeated_interpolation.ipynb +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/run_pipeline.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/visualize_marimo.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/visualize_results.py +0 -0
- {flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/scripts/analyse_fhdf.py +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/WHEEL +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/entry_points.txt +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/licenses/LICENSE.txt +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/licenses/LICENSES/BSD-3-Clause.txt +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/licenses/LICENSES/GPL-3.0-only.txt +0 -0
- {flowtracks-1.6.2.dev6.dist-info → flowtracks-1.6.2.dev12.dist-info}/top_level.txt +0 -0
flowtracks/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.6.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 6, 2, '
|
|
21
|
+
__version__ = version = '1.6.2.dev12'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 6, 2, 'dev12')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g4ea7448e5'
|
flowtracks/clean.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# SPDX-FileCopyrightText: Alex Liberzon
|
|
3
|
+
"""
|
|
4
|
+
Cleaning chain for raw tracker output: repair, kink split, smoothing, stitching.
|
|
5
|
+
|
|
6
|
+
Raw linkage from a tracker (one row per point, with a trajectory id) is turned
|
|
7
|
+
into smooth trajectories in one call, :func:`clean_trajectories`:
|
|
8
|
+
|
|
9
|
+
1. repair (:func:`flowtracks.repair.repair_arrays`): cut wrong links, attach
|
|
10
|
+
single points, join pieces across short gaps;
|
|
11
|
+
2. Savitzky-Golay smoothing (:func:`flowtracks.smoothing.savitzky_golay`);
|
|
12
|
+
3. kink split: a fast, violent turn (larger than ``kink_deg`` on steps longer
|
|
13
|
+
than ``kink_step_mm``) is a wrong link the repair missed; the pieces get
|
|
14
|
+
fresh ids;
|
|
15
|
+
4. smoothing, a first stitch (``gap1``, ``dist1``, ``vd1``), smoothing;
|
|
16
|
+
5. a second kink split, smoothing, a second, tighter stitch (``gap2``,
|
|
17
|
+
``dist2``, ``vd2``);
|
|
18
|
+
6. final smoothing (optionally a longer window for long trajectories).
|
|
19
|
+
|
|
20
|
+
Positions are in metres, so velocities are in m/s at the given frame rate.
|
|
21
|
+
Every setting has a default in :data:`CLEAN_DEFAULTS`; ``fps`` has none on
|
|
22
|
+
purpose -- the frame rate must be stated. :func:`resolve_settings` merges a
|
|
23
|
+
parameter-file section and overrides onto the defaults and reports where each
|
|
24
|
+
value came from.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
from flowtracks.repair import repair_arrays
|
|
30
|
+
from flowtracks.smoothing import savitzky_golay
|
|
31
|
+
from flowtracks.stitching import stitch_trajectories
|
|
32
|
+
from flowtracks.trajectory import Trajectory
|
|
33
|
+
|
|
34
|
+
#: Default settings of :func:`clean_trajectories` (positions in metres).
|
|
35
|
+
CLEAN_DEFAULTS = dict(
|
|
36
|
+
fps=None, # frame rate [Hz]; required
|
|
37
|
+
repair_max_gap=1, # repair: max frame gap for attach / join,
|
|
38
|
+
repair_max_distance_mm=0.3, # max distance [mm],
|
|
39
|
+
repair_link_percentile=99.9, # link check calibrated on this percentile,
|
|
40
|
+
repair_cut_factor=2.0, # cut threshold factor,
|
|
41
|
+
repair_cut=True, # and which of the three repairs run
|
|
42
|
+
repair_attach=True,
|
|
43
|
+
repair_join=True,
|
|
44
|
+
sg_window=7, # Savitzky-Golay window / order
|
|
45
|
+
sg_order=2,
|
|
46
|
+
kink_deg=90.0, # kink split: turn angle ...
|
|
47
|
+
kink_step_mm=25.0, # ... on steps longer than this
|
|
48
|
+
gap1=12, # first stitch: max gap [frames],
|
|
49
|
+
dist1=0.05, # max distance [m],
|
|
50
|
+
vd1=0.5, # max velocity difference [m/s]
|
|
51
|
+
gap2=8, # second (tight) stitch on clean ends
|
|
52
|
+
dist2=0.025,
|
|
53
|
+
vd2=0.35,
|
|
54
|
+
win_long=0, # >0: final smoothing (win_long, order_long)
|
|
55
|
+
order_long=3, # for tracks with >= len_long points
|
|
56
|
+
len_long=25,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resolve_settings(section=None, overrides=None, source_name="parameter file"):
|
|
61
|
+
"""Settings of :func:`clean_trajectories`: defaults <- section <- overrides.
|
|
62
|
+
|
|
63
|
+
Arguments:
|
|
64
|
+
section - mapping read from a parameter file (may be None).
|
|
65
|
+
overrides - mapping of explicit values (e.g. command-line flags); None
|
|
66
|
+
values are ignored.
|
|
67
|
+
source_name - how ``section`` is named in the returned sources.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
(settings, sources) - two dicts keyed like :data:`CLEAN_DEFAULTS`; sources
|
|
71
|
+
say "override", ``source_name`` or "default" for every key.
|
|
72
|
+
|
|
73
|
+
Raises ValueError on an unknown key in ``section`` or a missing ``fps``.
|
|
74
|
+
"""
|
|
75
|
+
section = dict(section or {})
|
|
76
|
+
overrides = {k: v for k, v in (overrides or {}).items() if v is not None}
|
|
77
|
+
unknown = sorted(set(section) - set(CLEAN_DEFAULTS))
|
|
78
|
+
if unknown:
|
|
79
|
+
raise ValueError(f"unknown cleaning settings: {unknown}")
|
|
80
|
+
settings, sources = {}, {}
|
|
81
|
+
for k, v in CLEAN_DEFAULTS.items():
|
|
82
|
+
cast = float if k == "fps" else type(v)
|
|
83
|
+
if k in overrides:
|
|
84
|
+
settings[k], sources[k] = cast(overrides[k]), "override"
|
|
85
|
+
elif k in section:
|
|
86
|
+
settings[k], sources[k] = cast(section[k]), source_name
|
|
87
|
+
else:
|
|
88
|
+
settings[k], sources[k] = v, "default"
|
|
89
|
+
if settings["fps"] is None:
|
|
90
|
+
raise ValueError("fps (frame rate) must be given")
|
|
91
|
+
return settings, sources
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def trajectories_from_arrays(trajid, time, pos):
|
|
95
|
+
"""Trajectory objects (zero velocity) from flat one-row-per-point arrays.
|
|
96
|
+
|
|
97
|
+
Rows are grouped by ``trajid`` and ordered by ``time`` inside each group;
|
|
98
|
+
groups of a single point are dropped.
|
|
99
|
+
"""
|
|
100
|
+
trajid, time, pos = np.asarray(trajid), np.asarray(time), np.asarray(pos)
|
|
101
|
+
order = np.argsort(trajid, kind="stable")
|
|
102
|
+
tj, tm, pp = trajid[order], time[order], pos[order]
|
|
103
|
+
bounds = np.flatnonzero(np.diff(tj)) + 1
|
|
104
|
+
starts = np.r_[0, bounds]
|
|
105
|
+
out = []
|
|
106
|
+
for i, s in enumerate(starts):
|
|
107
|
+
e = int(bounds[i]) if i < len(bounds) else len(tj)
|
|
108
|
+
if e - s >= 2:
|
|
109
|
+
o = np.argsort(tm[s:e], kind="stable")
|
|
110
|
+
out.append(Trajectory(pp[s:e][o], np.zeros_like(pp[s:e][o]),
|
|
111
|
+
tm[s:e][o], int(tj[s])))
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def kink_split(trajs, max_deg, step_min, fresh_start=10_000_000):
|
|
116
|
+
"""Split trajectories at fast, violent turns.
|
|
117
|
+
|
|
118
|
+
A point is a cut point when the velocity turns by more than ``max_deg``
|
|
119
|
+
degrees there and both the step into it and the step out of it are longer
|
|
120
|
+
than ``step_min`` (position units). Every piece of a cut trajectory gets a
|
|
121
|
+
fresh id counting from ``fresh_start``, so readers grouping by id cannot
|
|
122
|
+
put the pieces back together.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
(trajectories, number of cuts)
|
|
126
|
+
"""
|
|
127
|
+
out, ncuts, nxt = [], 0, fresh_start
|
|
128
|
+
for tr in trajs:
|
|
129
|
+
v = tr.velocity()
|
|
130
|
+
n = np.linalg.norm(v, axis=1)
|
|
131
|
+
t, p = np.asarray(tr.time()), tr.pos()
|
|
132
|
+
st = np.linalg.norm(np.diff(p, axis=0), axis=1)
|
|
133
|
+
ok = (n[:-1] > 1e-12) & (n[1:] > 1e-12)
|
|
134
|
+
cosang = np.ones(len(v) - 1)
|
|
135
|
+
cosang[ok] = np.clip((v[:-1][ok] * v[1:][ok]).sum(1) / (n[:-1][ok] * n[1:][ok]),
|
|
136
|
+
-1, 1)
|
|
137
|
+
ang = np.degrees(np.arccos(cosang))
|
|
138
|
+
instep = np.concatenate([st[:1], st[:-1]])
|
|
139
|
+
bad = np.flatnonzero((ang > max_deg) & (instep > step_min) & (st > step_min)) + 1
|
|
140
|
+
ncuts += len(bad)
|
|
141
|
+
segs = np.split(np.arange(len(t)), bad)
|
|
142
|
+
if len(segs) == 1:
|
|
143
|
+
out.append(tr)
|
|
144
|
+
continue
|
|
145
|
+
for seg in segs:
|
|
146
|
+
if len(seg) >= 2:
|
|
147
|
+
out.append(Trajectory(p[seg], v[seg], t[seg], nxt))
|
|
148
|
+
nxt += 1
|
|
149
|
+
return out, ncuts
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def clean_trajectories(trajid, time, pos, settings, log=print):
|
|
153
|
+
"""Run the cleaning chain (module docstring) on flat tracker output.
|
|
154
|
+
|
|
155
|
+
Arguments:
|
|
156
|
+
trajid, time, pos - (N,), (N,), (N, 3) one row per point, pos in metres.
|
|
157
|
+
settings - complete settings, e.g. from :func:`resolve_settings`.
|
|
158
|
+
log - callable for progress lines, or None for silence.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
list of smoothed :class:`~flowtracks.trajectory.Trajectory` objects.
|
|
162
|
+
"""
|
|
163
|
+
log = log or (lambda *a, **k: None)
|
|
164
|
+
c = settings
|
|
165
|
+
fps, sgw, sgo = c["fps"], c["sg_window"], c["sg_order"]
|
|
166
|
+
tj, rep = repair_arrays(
|
|
167
|
+
trajid, time, pos, max_gap=c["repair_max_gap"],
|
|
168
|
+
max_distance=c["repair_max_distance_mm"] * 1e-3,
|
|
169
|
+
link_percentile=c["repair_link_percentile"], cut=c["repair_cut"],
|
|
170
|
+
attach=c["repair_attach"], join=c["repair_join"],
|
|
171
|
+
cut_factor=c["repair_cut_factor"])
|
|
172
|
+
log(f"repair: cut={rep.get('links_cut')} joins={rep.get('joins')}")
|
|
173
|
+
s1 = savitzky_golay(trajectories_from_arrays(tj, time, pos), fps, sgw, sgo,
|
|
174
|
+
min_window=5)
|
|
175
|
+
split, ncuts = kink_split(s1, c["kink_deg"], c["kink_step_mm"] / 1000.0)
|
|
176
|
+
log(f"kinksplit(>{c['kink_deg']}deg, steps>{c['kink_step_mm']}mm): "
|
|
177
|
+
f"{len(s1)} -> {len(split)} objs, {ncuts} cuts")
|
|
178
|
+
s2 = savitzky_golay(split, fps, sgw, sgo, min_window=5)
|
|
179
|
+
st = stitch_trajectories(s2, fps=fps, max_gap=c["gap1"],
|
|
180
|
+
max_distance=c["dist1"], max_vel_diff=c["vd1"])
|
|
181
|
+
s2b = savitzky_golay(st, fps, sgw, sgo, min_window=5)
|
|
182
|
+
split2, _ = kink_split(s2b, c["kink_deg"], c["kink_step_mm"] / 1000.0,
|
|
183
|
+
fresh_start=20_000_000)
|
|
184
|
+
s2c = savitzky_golay(split2, fps, sgw, sgo, min_window=5)
|
|
185
|
+
st2 = stitch_trajectories(s2c, fps=fps, max_gap=c["gap2"],
|
|
186
|
+
max_distance=c["dist2"], max_vel_diff=c["vd2"])
|
|
187
|
+
if c["win_long"] > 0:
|
|
188
|
+
short = [t for t in st2 if len(t) < c["len_long"]]
|
|
189
|
+
long = [t for t in st2 if len(t) >= c["len_long"]]
|
|
190
|
+
s_short = savitzky_golay(short, fps, sgw, sgo, min_window=5)
|
|
191
|
+
s_long = savitzky_golay(long, fps, c["win_long"], c["order_long"],
|
|
192
|
+
min_window=7)
|
|
193
|
+
out = s_short + s_long
|
|
194
|
+
log(f"two-tier final smooth: {len(s_short)}x({sgw},{sgo}) + {len(s_long)}x"
|
|
195
|
+
f"({c['win_long']},{c['order_long']})")
|
|
196
|
+
else:
|
|
197
|
+
out = savitzky_golay(st2, fps, sgw, sgo, min_window=5)
|
|
198
|
+
lens = np.array([len(t) for t in out]) if out else np.zeros(1, int)
|
|
199
|
+
log(f"final: n={len(out)} joined1={len(s2) - len(st)} "
|
|
200
|
+
f"joined2={len(s2c) - len(st2)} med={np.median(lens):.0f} "
|
|
201
|
+
f"max={lens.max()} rows={lens.sum()}")
|
|
202
|
+
return out
|
flowtracks/graphics.py
CHANGED
|
@@ -315,3 +315,197 @@ def plot_trajectories_3d(source, trajids=None, scalars="speed", context=0,
|
|
|
315
315
|
if show:
|
|
316
316
|
plotter.show()
|
|
317
317
|
return plotter
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _far_walls(bounds, toward_camera, step):
|
|
321
|
+
"""Grey back walls (the three box faces away from the camera) and their
|
|
322
|
+
grid lines every ``step``, as two PolyData."""
|
|
323
|
+
import pyvista as pv
|
|
324
|
+
|
|
325
|
+
lo, hi = np.asarray(bounds[0::2], float), np.asarray(bounds[1::2], float)
|
|
326
|
+
walls, lines = [], []
|
|
327
|
+
for ax in range(3):
|
|
328
|
+
level = lo[ax] if toward_camera[ax] > 0 else hi[ax]
|
|
329
|
+
u, v = [a for a in range(3) if a != ax]
|
|
330
|
+
corners = np.zeros((4, 3))
|
|
331
|
+
corners[:, ax] = level
|
|
332
|
+
corners[:, u] = [lo[u], hi[u], hi[u], lo[u]]
|
|
333
|
+
corners[:, v] = [lo[v], lo[v], hi[v], hi[v]]
|
|
334
|
+
walls.append(pv.PolyData(corners, faces=[4, 0, 1, 2, 3]))
|
|
335
|
+
# grid lines lifted a hair off the wall so they never z-fight it
|
|
336
|
+
lift = 1e-3 * (hi[ax] - lo[ax]) * (1 if level == lo[ax] else -1)
|
|
337
|
+
for a, b in ((u, v), (v, u)):
|
|
338
|
+
ticks = np.arange(np.ceil(lo[a] / step) * step, hi[a] + 1e-9, step)
|
|
339
|
+
for t in ticks:
|
|
340
|
+
seg = np.zeros((2, 3))
|
|
341
|
+
seg[:, ax] = level + lift
|
|
342
|
+
seg[:, a] = t
|
|
343
|
+
seg[:, b] = [lo[b], hi[b]]
|
|
344
|
+
lines.append(pv.Line(seg[0], seg[1]))
|
|
345
|
+
return pv.merge(walls), pv.merge(lines)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _box_axis_labels(pl, bounds, toward_camera, step, titles, font_size):
|
|
349
|
+
"""Tick labels every ``step`` and axis titles along the box edges nearest
|
|
350
|
+
the camera on the floor (x, z) and on the far vertical edge (y), as
|
|
351
|
+
ParaView draws them. (VTK's cube-axes actor caps the label count at its
|
|
352
|
+
own tick spacing, so it cannot label every ``step``.)"""
|
|
353
|
+
lo, hi = np.asarray(bounds[0::2], float), np.asarray(bounds[1::2], float)
|
|
354
|
+
near = np.where(np.asarray(toward_camera) > 0, hi, lo)
|
|
355
|
+
far = np.where(np.asarray(toward_camera) > 0, lo, hi)
|
|
356
|
+
out = np.where(np.asarray(toward_camera) > 0, 1.0, -1.0) # outward, camera side
|
|
357
|
+
off = 0.035 * np.max(hi - lo)
|
|
358
|
+
edges = ( # axis, anchor of the edge, outward offset of its labels
|
|
359
|
+
(0, np.array([0.0, lo[1], near[2]]), np.array([0.0, -off, out[2] * off])),
|
|
360
|
+
(2, np.array([near[0], lo[1], 0.0]), np.array([out[0] * off, -off, 0.0])),
|
|
361
|
+
(1, np.array([far[0], 0.0, near[2]]), np.array([-out[0] * off, 0.0, out[2] * off])),
|
|
362
|
+
)
|
|
363
|
+
for ax, anchor, shift in edges:
|
|
364
|
+
ticks = np.arange(np.ceil(lo[ax] / step) * step, hi[ax] + 1e-9, step)
|
|
365
|
+
pts = np.tile(anchor + shift, (len(ticks), 1))
|
|
366
|
+
pts[:, ax] = ticks
|
|
367
|
+
pl.add_point_labels(pts, [f"{t:.0f}" for t in ticks], font_size=font_size,
|
|
368
|
+
text_color="black", shape=None, show_points=False,
|
|
369
|
+
always_visible=True, name=f"ticks{ax}")
|
|
370
|
+
mid = anchor + 2.6 * shift
|
|
371
|
+
mid[ax] = (lo[ax] + hi[ax]) / 2
|
|
372
|
+
pl.add_point_labels([mid], [titles[ax]], font_size=int(font_size * 1.3),
|
|
373
|
+
text_color="black", shape=None, show_points=False,
|
|
374
|
+
always_visible=True, name=f"title{ax}")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _use_font_file(pl, font_file):
|
|
378
|
+
"""Point every text of the plotter at a TrueType file. (VTK's built-in
|
|
379
|
+
sans font draws '[' and ']' as parentheses.)"""
|
|
380
|
+
props = []
|
|
381
|
+
for actor in pl.actors.values():
|
|
382
|
+
if hasattr(actor, "GetTextProperty"):
|
|
383
|
+
props.append(actor.GetTextProperty())
|
|
384
|
+
mapper = actor.GetMapper() if hasattr(actor, "GetMapper") else None
|
|
385
|
+
if mapper is not None and hasattr(mapper, "GetLabelTextProperty"):
|
|
386
|
+
props.append(mapper.GetLabelTextProperty())
|
|
387
|
+
# add_point_labels: the style sits on the label hierarchy feeding the mapper
|
|
388
|
+
source = mapper.GetInputAlgorithm() if hasattr(mapper, "GetInputAlgorithm") else None
|
|
389
|
+
if source is not None and hasattr(source, "GetTextProperty"):
|
|
390
|
+
props.append(source.GetTextProperty())
|
|
391
|
+
for bar in pl.scalar_bars.values():
|
|
392
|
+
props += [bar.GetTitleTextProperty(), bar.GetLabelTextProperty()]
|
|
393
|
+
for prop in props:
|
|
394
|
+
prop.SetFontFamily(4) # VTK_FONT_FILE
|
|
395
|
+
prop.SetFontFile(str(font_file))
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def animate_trajectories_3d(source, out_dir, frames=None, *, component=1, tail=20,
|
|
399
|
+
pos_scale=1.0, bounds=None, cmap="coolwarm",
|
|
400
|
+
clim=(-0.5, 0.5), bar_title=None, bar_labels=21,
|
|
401
|
+
bar_fmt="%.2f", view_direction=(-1.0, 1.0, 1.0),
|
|
402
|
+
view_up=(0.0, 1.0, 0.0), zoom=1.0, window_center=(0.0, 0.0),
|
|
403
|
+
window_size=(1920, 1080), point_size=6.0,
|
|
404
|
+
grid_step=500.0, axis_titles=("x", "y", "z"),
|
|
405
|
+
wall_color=(0.54, 0.54, 0.54), background="white",
|
|
406
|
+
font_size=20, font_file=None, outlines=(),
|
|
407
|
+
outline_color="black", outline_width=3.0, movie=None,
|
|
408
|
+
fps=10, label=None):
|
|
409
|
+
"""Off-screen movie frames of trajectories as trailing beads (PyVista).
|
|
410
|
+
|
|
411
|
+
Every frame shows each particle's last ``tail`` positions as shaded
|
|
412
|
+
spheres coloured by one velocity component (``component``: 0, 1, 2), in a
|
|
413
|
+
box with grey back walls, a ``grid_step`` grid and labelled axes, seen
|
|
414
|
+
with a fixed parallel-projection camera -- the usual ParaView look.
|
|
415
|
+
|
|
416
|
+
source - zarr path / ZarrScene / Scene, as :func:`plot_trajectories_3d`.
|
|
417
|
+
out_dir - PNG frames are written as ``frame_0000.png`` ...
|
|
418
|
+
frames - frame numbers to render (default: every frame of the source).
|
|
419
|
+
pos_scale - multiplies positions (e.g. 1000 for m -> mm axes).
|
|
420
|
+
bounds - (x0, x1, y0, y1, z0, z1) of the box, in scaled units (default:
|
|
421
|
+
the data's 0.5-99.5 percentile box).
|
|
422
|
+
view_direction - from the scene towards the camera; ``view_up`` the
|
|
423
|
+
screen's up; ``zoom`` > 1 enlarges; ``window_center`` shifts the
|
|
424
|
+
projection centre in normalized window units (VTK SetWindowCenter).
|
|
425
|
+
movie - if given, the frames are also encoded to this .mp4 with ffmpeg.
|
|
426
|
+
label - optional ``str.format`` text drawn bottom-left, given ``frame``.
|
|
427
|
+
font_file - TrueType file for all text (VTK's built-in font has no
|
|
428
|
+
square brackets).
|
|
429
|
+
outlines - (N, 3) point arrays in scaled units, each drawn as a closed
|
|
430
|
+
line (e.g. the top and bottom rims of a cylindrical vessel).
|
|
431
|
+
|
|
432
|
+
Returns the list of PNG paths.
|
|
433
|
+
"""
|
|
434
|
+
import shutil
|
|
435
|
+
import subprocess
|
|
436
|
+
|
|
437
|
+
import pyvista as pv
|
|
438
|
+
|
|
439
|
+
from flowtracks.writers import _trajectory_arrays
|
|
440
|
+
|
|
441
|
+
pos, vel, time, _ = _trajectory_arrays(source)
|
|
442
|
+
pos = pos * pos_scale
|
|
443
|
+
value = vel[:, component]
|
|
444
|
+
order = np.argsort(time, kind="stable")
|
|
445
|
+
pos, value, time = pos[order], value[order], time[order]
|
|
446
|
+
if frames is None:
|
|
447
|
+
frames = np.unique(time)
|
|
448
|
+
if bounds is None:
|
|
449
|
+
lo, hi = np.percentile(pos, 0.5, axis=0), np.percentile(pos, 99.5, axis=0)
|
|
450
|
+
bounds = (lo[0], hi[0], lo[1], hi[1], lo[2], hi[2])
|
|
451
|
+
out_dir = Path(out_dir)
|
|
452
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
453
|
+
|
|
454
|
+
direction = np.asarray(view_direction, float)
|
|
455
|
+
direction /= np.linalg.norm(direction)
|
|
456
|
+
walls, grid = _far_walls(bounds, direction, grid_step)
|
|
457
|
+
|
|
458
|
+
pl = pv.Plotter(off_screen=True, window_size=list(window_size))
|
|
459
|
+
pl.set_background(background)
|
|
460
|
+
pl.add_mesh(walls, color=wall_color, lighting=False, name="walls")
|
|
461
|
+
pl.add_mesh(grid, color="black", line_width=1.5, name="grid")
|
|
462
|
+
for k, ring in enumerate(outlines):
|
|
463
|
+
ring = np.asarray(ring, float)
|
|
464
|
+
line = pv.lines_from_points(np.vstack([ring, ring[:1]]))
|
|
465
|
+
pl.add_mesh(line, color=outline_color, line_width=outline_width,
|
|
466
|
+
render_lines_as_tubes=True, name=f"outline{k}")
|
|
467
|
+
_box_axis_labels(pl, bounds, direction, grid_step, axis_titles, font_size)
|
|
468
|
+
pl.add_axes(viewport=(0.0, 0.82, 0.12, 1.0))
|
|
469
|
+
centre = np.array([(bounds[0] + bounds[1]) / 2, (bounds[2] + bounds[3]) / 2,
|
|
470
|
+
(bounds[4] + bounds[5]) / 2])
|
|
471
|
+
size = np.linalg.norm(np.subtract(bounds[1::2], bounds[0::2]))
|
|
472
|
+
pl.enable_parallel_projection()
|
|
473
|
+
pl.camera.focal_point = centre
|
|
474
|
+
pl.camera.position = centre + direction * 4 * size
|
|
475
|
+
pl.camera.up = view_up
|
|
476
|
+
pl.reset_camera(bounds=list(bounds))
|
|
477
|
+
pl.camera.parallel_scale /= zoom
|
|
478
|
+
pl.camera.SetWindowCenter(*window_center)
|
|
479
|
+
|
|
480
|
+
bar = dict(title=bar_title or "", vertical=True, position_x=0.93, position_y=0.02,
|
|
481
|
+
height=0.96, width=0.012, n_labels=bar_labels, fmt=bar_fmt,
|
|
482
|
+
color="black", title_font_size=int(font_size * 1.2),
|
|
483
|
+
label_font_size=font_size)
|
|
484
|
+
paths = []
|
|
485
|
+
starts = np.searchsorted(time, np.asarray(frames) - tail + 1, side="left")
|
|
486
|
+
ends = np.searchsorted(time, np.asarray(frames), side="right")
|
|
487
|
+
for i, (frame, s, e) in enumerate(zip(frames, starts, ends)):
|
|
488
|
+
beads = pv.PolyData(pos[s:e] if e > s else np.zeros((0, 3)))
|
|
489
|
+
if e > s:
|
|
490
|
+
beads.point_data["value"] = value[s:e]
|
|
491
|
+
pl.add_mesh(beads, scalars="value", cmap=cmap, clim=clim,
|
|
492
|
+
point_size=point_size, render_points_as_spheres=True,
|
|
493
|
+
style="points", scalar_bar_args=bar, name="beads")
|
|
494
|
+
if label:
|
|
495
|
+
pl.add_text(label.format(frame=frame), position="lower_left",
|
|
496
|
+
font_size=font_size, color="black", name="label")
|
|
497
|
+
if font_file:
|
|
498
|
+
_use_font_file(pl, font_file)
|
|
499
|
+
path = out_dir / f"frame_{i:04d}.png"
|
|
500
|
+
pl.screenshot(str(path))
|
|
501
|
+
paths.append(path)
|
|
502
|
+
pl.close()
|
|
503
|
+
|
|
504
|
+
if movie is not None:
|
|
505
|
+
ffmpeg = shutil.which("ffmpeg")
|
|
506
|
+
if ffmpeg is None:
|
|
507
|
+
raise RuntimeError("ffmpeg not found; the PNG frames are in " + str(out_dir))
|
|
508
|
+
subprocess.run([ffmpeg, "-v", "error", "-y", "-framerate", str(fps),
|
|
509
|
+
"-i", str(out_dir / "frame_%04d.png"), "-c:v", "libx264",
|
|
510
|
+
"-pix_fmt", "yuv420p", "-crf", "18", str(movie)], check=True)
|
|
511
|
+
return paths
|
flowtracks/merge.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# SPDX-FileCopyrightText: Alex Liberzon
|
|
3
|
+
"""
|
|
4
|
+
Merging trajectory datasets of two camera systems that see a common volume.
|
|
5
|
+
|
|
6
|
+
Two systems (two camera rigs, two recordings of one rig) each produce their
|
|
7
|
+
own trajectories in their own coordinate frame. Where their volumes overlap
|
|
8
|
+
the same particle appears twice. This module
|
|
9
|
+
|
|
10
|
+
1. measures the rigid map between the two frames from particles both systems
|
|
11
|
+
see in the same frame (:func:`estimate_rigid_map`): for each candidate
|
|
12
|
+
rotation, the same-frame neighbour differences pile up at the true offset;
|
|
13
|
+
the rotation whose pile is sharpest wins and the offset is refined on tight
|
|
14
|
+
pairs. No sharp pile means no common particles, and it refuses
|
|
15
|
+
(ValueError) rather than guess;
|
|
16
|
+
2. maps points of the second system into the first (:func:`apply_rigid`);
|
|
17
|
+
3. finds the trajectory pairs that share same-frame points closer than a
|
|
18
|
+
tolerance (:func:`overlap_counts`), each trajectory's best partner
|
|
19
|
+
(:func:`best_partners`) and the duplicates a keep-the-longer rule removes
|
|
20
|
+
(:func:`duplicates`).
|
|
21
|
+
|
|
22
|
+
Units are whatever the inputs use; tolerances are in the same unit.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
from scipy.spatial import cKDTree
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def estimate_rigid_map(pairs, rotations, search_radius=150.0, bin_size=6.0,
|
|
30
|
+
tight=8.0, min_peak=50, min_ratio=100.0, refine=2,
|
|
31
|
+
log=print):
|
|
32
|
+
"""Rigid map from system B to system A: ``p_A = R @ p_B - offset``.
|
|
33
|
+
|
|
34
|
+
Arguments:
|
|
35
|
+
pairs - list of (points_A, points_B) arrays, (n, 3) and (m, 3), each pair
|
|
36
|
+
taken in the same frame (use well-triangulated points only).
|
|
37
|
+
rotations - list of (label, 3x3 matrix) candidates for R.
|
|
38
|
+
search_radius - neighbours of a point of A within this distance of the
|
|
39
|
+
rotated B points vote for the offset.
|
|
40
|
+
bin_size - histogram bin of the votes; the offset is the densest bin.
|
|
41
|
+
tight - pairs closer than this after the first estimate refine it.
|
|
42
|
+
min_peak, min_ratio - the densest bin must hold at least ``min_peak``
|
|
43
|
+
votes and ``min_ratio`` times the mean, or the map is refused.
|
|
44
|
+
refine - number of refinement passes (median of the tight pairs).
|
|
45
|
+
log - callable for progress lines, or None.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict with matrix (3x3), offset (3,), label of the chosen rotation,
|
|
49
|
+
n_pairs (tight pairs of the last pass) and spread (median absolute
|
|
50
|
+
deviation of their differences, per axis).
|
|
51
|
+
"""
|
|
52
|
+
log = log or (lambda *a, **k: None)
|
|
53
|
+
nbins = int(round(2 * search_radius / bin_size))
|
|
54
|
+
best = None
|
|
55
|
+
for label, M in rotations:
|
|
56
|
+
M = np.asarray(M, dtype=float)
|
|
57
|
+
diffs = []
|
|
58
|
+
for pa, pb in pairs:
|
|
59
|
+
if not len(pa) or not len(pb):
|
|
60
|
+
continue
|
|
61
|
+
pbm = pb @ M.T
|
|
62
|
+
for k, js in enumerate(cKDTree(pbm).query_ball_point(pa, search_radius)):
|
|
63
|
+
if js:
|
|
64
|
+
diffs.append(pbm[js] - pa[k])
|
|
65
|
+
if not diffs:
|
|
66
|
+
continue
|
|
67
|
+
d = np.vstack(diffs)
|
|
68
|
+
H, edges = np.histogramdd(d, bins=nbins,
|
|
69
|
+
range=[(-search_radius, search_radius)] * 3)
|
|
70
|
+
i = np.unravel_index(H.argmax(), H.shape)
|
|
71
|
+
peak = np.array([edges[k][i[k]] + bin_size / 2 for k in range(3)])
|
|
72
|
+
score = H.max() / max(H.mean(), 1e-9)
|
|
73
|
+
log(f" rotation {label}: densest {bin_size:g} bin {int(H.max())} "
|
|
74
|
+
f"({score:.0f}x the mean)")
|
|
75
|
+
if best is None or H.max() > best[1]:
|
|
76
|
+
best = (M, H.max(), score, peak, label)
|
|
77
|
+
if best is None or best[1] < min_peak or best[2] < min_ratio:
|
|
78
|
+
raise ValueError("no common particles between the two systems")
|
|
79
|
+
M, _, _, off, label = best
|
|
80
|
+
for _ in range(refine):
|
|
81
|
+
dd = []
|
|
82
|
+
for pa, pb in pairs:
|
|
83
|
+
if not len(pa) or not len(pb):
|
|
84
|
+
continue
|
|
85
|
+
q = pb @ M.T - off
|
|
86
|
+
dist, j = cKDTree(q).query(pa, distance_upper_bound=tight)
|
|
87
|
+
ok = np.isfinite(dist)
|
|
88
|
+
dd.append(pb[j[ok]] @ M.T - pa[ok])
|
|
89
|
+
dd = np.vstack(dd)
|
|
90
|
+
off = np.median(dd, axis=0)
|
|
91
|
+
spread = np.median(np.abs(dd - off), axis=0)
|
|
92
|
+
log(f" B -> A: rotation {label}, offset {np.round(off, 2)}, "
|
|
93
|
+
f"{len(dd)} pairs, spread (MAD) {np.round(spread, 2)}")
|
|
94
|
+
return dict(matrix=M, offset=off, label=label, n_pairs=len(dd), spread=spread)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def apply_rigid(points, matrix, offset=0.0):
|
|
98
|
+
"""``points @ matrix.T - offset``: system-B points (or, with offset 0,
|
|
99
|
+
velocities and accelerations) in system A's frame."""
|
|
100
|
+
return np.asarray(points, dtype=float) @ np.asarray(matrix).T - offset
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def overlap_counts(tracks_a, tracks_b, tol):
|
|
104
|
+
"""Same-frame shared points of every trajectory pair of A and B.
|
|
105
|
+
|
|
106
|
+
Arguments:
|
|
107
|
+
tracks_a, tracks_b - mappings id -> mapping with "time" (n,) and "pos"
|
|
108
|
+
(n, 3), both in one frame of reference.
|
|
109
|
+
tol - a point of A and the nearest point of B in the same frame closer
|
|
110
|
+
than this count as shared.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
{(id_a, id_b): number of shared points}
|
|
114
|
+
"""
|
|
115
|
+
bframe = {}
|
|
116
|
+
for tid, t in tracks_b.items():
|
|
117
|
+
for f, p in zip(t["time"], t["pos"]):
|
|
118
|
+
bframe.setdefault(int(f), []).append((tid, p))
|
|
119
|
+
btree = {f: (cKDTree(np.array([p for _, p in v])), v) for f, v in bframe.items()}
|
|
120
|
+
shared = {}
|
|
121
|
+
for tid, t in tracks_a.items():
|
|
122
|
+
for f, p in zip(t["time"], t["pos"]):
|
|
123
|
+
if int(f) not in btree:
|
|
124
|
+
continue
|
|
125
|
+
tree, v = btree[int(f)]
|
|
126
|
+
dist, j = tree.query(p, k=1)
|
|
127
|
+
if dist < tol:
|
|
128
|
+
key = (tid, v[j][0])
|
|
129
|
+
shared[key] = shared.get(key, 0) + 1
|
|
130
|
+
return shared
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def best_partners(shared):
|
|
134
|
+
"""Each trajectory's partner in the other system: the one it shares most
|
|
135
|
+
points with. Returns ({id_a: (id_b, n)}, {id_b: (id_a, n)})."""
|
|
136
|
+
partner_a, partner_b = {}, {}
|
|
137
|
+
for (atid, btid), n in shared.items():
|
|
138
|
+
if n > partner_a.get(atid, (None, 0))[1]:
|
|
139
|
+
partner_a[atid] = (btid, n)
|
|
140
|
+
if n > partner_b.get(btid, (None, 0))[1]:
|
|
141
|
+
partner_b[btid] = (atid, n)
|
|
142
|
+
return partner_a, partner_b
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def duplicates(shared, len_a, len_b):
|
|
146
|
+
"""Ids of B trajectories that share any point with an A trajectory at
|
|
147
|
+
least as long (``len_a``, ``len_b``: id -> number of points). Dropping
|
|
148
|
+
them keeps one copy of every overlapping particle, the longer one, and
|
|
149
|
+
A on ties."""
|
|
150
|
+
return {btid for (atid, btid) in shared if len_a[atid] >= len_b[btid]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowtracks
|
|
3
|
-
Version: 1.6.2.
|
|
3
|
+
Version: 1.6.2.dev12
|
|
4
4
|
Summary: Library for handling 3D PTV trajectory databases and Lagrangian/Eulerian turbulence post-processing.
|
|
5
5
|
Author-email: Yosef Meller <yosefm@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-only AND BSD-3-Clause
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
flowtracks/__init__.py,sha256=iCexskmRtpNJ0fNZWRFkQAqrku7csbKy5NtL2YQNYeo,1532
|
|
2
|
+
flowtracks/_version.py,sha256=MhSZ23Xb3S2pgcATsSw3UWCpsNnTFAH_LfLqatUUdYs,543
|
|
3
|
+
flowtracks/an_scene.py,sha256=QV_gtzp-5h1CqAW1Y6ieGco9BxXQDE4f_7-fcy3fluI,7361
|
|
4
|
+
flowtracks/analysis.py,sha256=nob5-pKxjPzB34_oQsxmxYALz4Q_s1BTpC5_VgXX3ss,6320
|
|
5
|
+
flowtracks/clean.py,sha256=IFwyoR7IfA98u0jjqp99EyeRqA1sOVlEiNH9My-pjzg,9055
|
|
6
|
+
flowtracks/combine.py,sha256=-UQtbTH4vjQS88_92h5UZlS60OfQofTWEV6SL1prXgI,4455
|
|
7
|
+
flowtracks/eulerian.py,sha256=PMt0_TtjrFwRPEvSEKm_wn8YZdClSux4yXEuxiB2EjM,41872
|
|
8
|
+
flowtracks/flux.py,sha256=w4K_jjDWTBIOj4BkoeNoKjIRcvg_6stCqNF8iivxF-A,15137
|
|
9
|
+
flowtracks/format_docstrings.py,sha256=iJVJmtTUYEDEcmF5XSRZ08zpvlVc9lU66CZeqSpEKic,1987
|
|
10
|
+
flowtracks/future_idea_xarray_dask_zarr.py,sha256=4ZS6QuZ2s_sUaiCW0XBo74GrP5fw_clVyhWNjT5gbds,6101
|
|
11
|
+
flowtracks/graphics.py,sha256=PMyfYPedfBumIJ9cPeTCukkFFPGNr9lyq49BsOyVBzA,23750
|
|
12
|
+
flowtracks/interpolation.py,sha256=njuB-bvWK5M2tc2Ibm6ZWNg6Tm6H-PTn0a63IldlJaA,39414
|
|
13
|
+
flowtracks/io.py,sha256=sujLgtY8WIymMZugDv2jlQeL9QR_hZn-MaZPPKsAPvA,48366
|
|
14
|
+
flowtracks/merge.py,sha256=hg5sYw2oL1YuxpbefiYAESUThKaAXs00RcsV9StIjSc,6445
|
|
15
|
+
flowtracks/nhist.py,sha256=3xdrfSTSpTojXJcyhwBgagHBmKrm0U_AEsP3BjP9H1o,4089
|
|
16
|
+
flowtracks/pairs.py,sha256=ehYDDTmjeRuzkSAMpdlwnwwAZOPNLKGF_e5HmcKw-2g,4034
|
|
17
|
+
flowtracks/particle.py,sha256=sVng1X8Xp3V9_wgJK2EGsDEx1OFQjd3L63Fzn--MWkM,671
|
|
18
|
+
flowtracks/phase_average.py,sha256=jjtrHYg0ukGRnJtRawJ6sWhdyrlV_16qBLlfNVxEQHw,3035
|
|
19
|
+
flowtracks/phasing.py,sha256=us7WfMYrx6dCCPNwucvN34G_0A7EKS9ieH1TrnpwM0I,4320
|
|
20
|
+
flowtracks/pipeline.py,sha256=0wOeeFqQGN_Lhgq-QFeQNg0oEKtJSGDhhZdv81M1PDg,5167
|
|
21
|
+
flowtracks/provenance.py,sha256=8L2BXmLdBcZRncUkr8oJbeU5JKdebj1Cgxrbzdy5o7A,2115
|
|
22
|
+
flowtracks/repair.py,sha256=rxaE1hcxQjNx19tLJOhL619lSfkB7Kfh6kXpJ_fSMfg,19122
|
|
23
|
+
flowtracks/scene.py,sha256=ovR1wXmgLGB9mZo62NPkcgxUzhlqiEcItLYJINAl2zk,19622
|
|
24
|
+
flowtracks/sequence.py,sha256=u_HOev5YydpgoknPQ1lB2WUG5ZZKSu_u9ySNGUu9D38,12984
|
|
25
|
+
flowtracks/smoothing.py,sha256=wXlvi3RBN6iZgaQRAVOxprhsjLMlxc4JIcuQMpRCQvs,7376
|
|
26
|
+
flowtracks/stitching.py,sha256=HFsIRdAfqyhmES1OwIFslyWTF9U7kPJ5OKtjBl4YWqA,16895
|
|
27
|
+
flowtracks/trajectory.py,sha256=uL4-byZ2tycdrAwGoET4AfLHuKaWRnc8VHLO5zv9MOg,11831
|
|
28
|
+
flowtracks/vortex.py,sha256=4lG3QkvvnWhAHy4tl1pJrBGFLdrlP4pxNdhCZI1VZ4Q,2529
|
|
29
|
+
flowtracks/wall_shear.py,sha256=k9Q5FEchIO9J_onXPOw4PNJQfmsrSyVVvX4RdW-rQlE,6788
|
|
30
|
+
flowtracks/writers.py,sha256=0LnRiuqmyhcw8Ju3v9vCOERymNmxHG7cXKy6r8uLOTo,10358
|
|
31
|
+
flowtracks/zarr_scene.py,sha256=PKCtiTT1t0Hj74Q572ra5DM3uM8lQK1A6DzzfUxvIzI,10468
|
|
32
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/batch_Lagrangian_to_Eulerian.py,sha256=e7mQFqyqvCeAHRKiN1IaxdNJi0e2FU84c7QNrx0L-m0,2296
|
|
33
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/hdf5_scene_analysis.ipynb,sha256=jn0Z_ZVL1-iXaiKXwyAeb74NbXsBpnocCxCSDh814eU,243097
|
|
34
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/linking_trajectories.ipynb,sha256=udz0GLDiPv68BYySg4yO1HWzaB9Sdy3YA3IhnIHDeII,8820
|
|
35
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_dual_rig_trajectories.py,sha256=dzeK0C-knwq1bPOwo0p1RhWrb0RPp-T31rBaGEptXcc,6737
|
|
36
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_flowtracks.py,sha256=7FIJNwBx9aBRmaM5ofmYTXSWEjE0wZqDfs8J47Tmw6U,9440
|
|
37
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_hdf5_vs_zarr_scene_analysis.py,sha256=bjXlMsLJTxJ0JkvGcVZbuDOFYZU4OvdabtDUgqb15So,4536
|
|
38
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_linking_trajectories_zarr.py,sha256=Ny_WUbiZWo0aKw-u2KGFWrhMv-8BvNgBy0UHIqCobYg,8210
|
|
39
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_phase_averaging_demo.py,sha256=vg6j2ayKGl-5uj08bdzzb6pujzJJQ_5bUsG7UuPP7dM,9270
|
|
40
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_phase_metrics.py,sha256=kBc2e_b-orDrm3AnuUXCcxsvmfQWyCtE826pFeolMGE,9367
|
|
41
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_vtk_writers_preview.py,sha256=Otrhhp-QFf6F94EOs6rWu5xOCpbS16EfWy_iMFp7e_I,14611
|
|
42
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/marimo_zarr_dashboard.py,sha256=jVIum6hdEelH4ef7vRgsfLhrbmMsuErsJBzxrnEImnw,9268
|
|
43
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/repeated_interpolation.ipynb,sha256=t9SfoIWRu5XP0SrphWbQy508uzyEibVz61FGjl51fjA,8861
|
|
44
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/run_pipeline.py,sha256=5fGE2tIP6XjFZ1sEYmqBujuLaXFfRL-rNnbr2p2CDfM,7205
|
|
45
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/visualize_marimo.py,sha256=jWrg5IWjY-bmSQwQjaO5qTr0t_G48CsqJYpevdQrxeY,13026
|
|
46
|
+
flowtracks-1.6.2.dev12.data/data/flowtracks-examples/visualize_results.py,sha256=SbPg3EoDcuIyo0rUz3E-AnT4sLWDVJFhgC6xVcracW4,7583
|
|
47
|
+
flowtracks-1.6.2.dev12.data/scripts/analyse_fhdf.py,sha256=QH08NdImGukTEGuZEKFxjStcSelXBv-8A-22G6KtXqM,1315
|
|
48
|
+
flowtracks-1.6.2.dev12.dist-info/licenses/LICENSE.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
49
|
+
flowtracks-1.6.2.dev12.dist-info/licenses/LICENSES/BSD-3-Clause.txt,sha256=XiJR9O5snDB6sAz0bcncOU6msceZgjPRxejWEQGg40Q,1505
|
|
50
|
+
flowtracks-1.6.2.dev12.dist-info/licenses/LICENSES/GPL-3.0-only.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
51
|
+
flowtracks-1.6.2.dev12.dist-info/METADATA,sha256=xq3JCpam1JKUGzMP8oeAQAaLdiRoF5LjVsH5nwQytz0,4774
|
|
52
|
+
flowtracks-1.6.2.dev12.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
53
|
+
flowtracks-1.6.2.dev12.dist-info/entry_points.txt,sha256=U6028E7yQZQiZoXWMl7w4xgZUPwPxAGiAuuMB__c-yQ,60
|
|
54
|
+
flowtracks-1.6.2.dev12.dist-info/top_level.txt,sha256=cR54RNb9y2Wfr_BfuOezmNJOMB4GfIHh1-luXTREnQA,11
|
|
55
|
+
flowtracks-1.6.2.dev12.dist-info/RECORD,,
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
flowtracks/__init__.py,sha256=iCexskmRtpNJ0fNZWRFkQAqrku7csbKy5NtL2YQNYeo,1532
|
|
2
|
-
flowtracks/_version.py,sha256=-2KICqAEdPWLo6oH9F7DHgtleuBtdR0ws16EQh2o6wM,541
|
|
3
|
-
flowtracks/an_scene.py,sha256=QV_gtzp-5h1CqAW1Y6ieGco9BxXQDE4f_7-fcy3fluI,7361
|
|
4
|
-
flowtracks/analysis.py,sha256=nob5-pKxjPzB34_oQsxmxYALz4Q_s1BTpC5_VgXX3ss,6320
|
|
5
|
-
flowtracks/combine.py,sha256=-UQtbTH4vjQS88_92h5UZlS60OfQofTWEV6SL1prXgI,4455
|
|
6
|
-
flowtracks/eulerian.py,sha256=PMt0_TtjrFwRPEvSEKm_wn8YZdClSux4yXEuxiB2EjM,41872
|
|
7
|
-
flowtracks/flux.py,sha256=w4K_jjDWTBIOj4BkoeNoKjIRcvg_6stCqNF8iivxF-A,15137
|
|
8
|
-
flowtracks/format_docstrings.py,sha256=iJVJmtTUYEDEcmF5XSRZ08zpvlVc9lU66CZeqSpEKic,1987
|
|
9
|
-
flowtracks/future_idea_xarray_dask_zarr.py,sha256=4ZS6QuZ2s_sUaiCW0XBo74GrP5fw_clVyhWNjT5gbds,6101
|
|
10
|
-
flowtracks/graphics.py,sha256=ulMTpPRGzQanAjna480XLMMQJL92Q4Wdtky8inqnwtE,13909
|
|
11
|
-
flowtracks/interpolation.py,sha256=njuB-bvWK5M2tc2Ibm6ZWNg6Tm6H-PTn0a63IldlJaA,39414
|
|
12
|
-
flowtracks/io.py,sha256=sujLgtY8WIymMZugDv2jlQeL9QR_hZn-MaZPPKsAPvA,48366
|
|
13
|
-
flowtracks/nhist.py,sha256=3xdrfSTSpTojXJcyhwBgagHBmKrm0U_AEsP3BjP9H1o,4089
|
|
14
|
-
flowtracks/pairs.py,sha256=ehYDDTmjeRuzkSAMpdlwnwwAZOPNLKGF_e5HmcKw-2g,4034
|
|
15
|
-
flowtracks/particle.py,sha256=sVng1X8Xp3V9_wgJK2EGsDEx1OFQjd3L63Fzn--MWkM,671
|
|
16
|
-
flowtracks/phase_average.py,sha256=jjtrHYg0ukGRnJtRawJ6sWhdyrlV_16qBLlfNVxEQHw,3035
|
|
17
|
-
flowtracks/phasing.py,sha256=us7WfMYrx6dCCPNwucvN34G_0A7EKS9ieH1TrnpwM0I,4320
|
|
18
|
-
flowtracks/pipeline.py,sha256=0wOeeFqQGN_Lhgq-QFeQNg0oEKtJSGDhhZdv81M1PDg,5167
|
|
19
|
-
flowtracks/provenance.py,sha256=8L2BXmLdBcZRncUkr8oJbeU5JKdebj1Cgxrbzdy5o7A,2115
|
|
20
|
-
flowtracks/repair.py,sha256=rxaE1hcxQjNx19tLJOhL619lSfkB7Kfh6kXpJ_fSMfg,19122
|
|
21
|
-
flowtracks/scene.py,sha256=ovR1wXmgLGB9mZo62NPkcgxUzhlqiEcItLYJINAl2zk,19622
|
|
22
|
-
flowtracks/sequence.py,sha256=u_HOev5YydpgoknPQ1lB2WUG5ZZKSu_u9ySNGUu9D38,12984
|
|
23
|
-
flowtracks/smoothing.py,sha256=wXlvi3RBN6iZgaQRAVOxprhsjLMlxc4JIcuQMpRCQvs,7376
|
|
24
|
-
flowtracks/stitching.py,sha256=HFsIRdAfqyhmES1OwIFslyWTF9U7kPJ5OKtjBl4YWqA,16895
|
|
25
|
-
flowtracks/trajectory.py,sha256=uL4-byZ2tycdrAwGoET4AfLHuKaWRnc8VHLO5zv9MOg,11831
|
|
26
|
-
flowtracks/vortex.py,sha256=4lG3QkvvnWhAHy4tl1pJrBGFLdrlP4pxNdhCZI1VZ4Q,2529
|
|
27
|
-
flowtracks/wall_shear.py,sha256=k9Q5FEchIO9J_onXPOw4PNJQfmsrSyVVvX4RdW-rQlE,6788
|
|
28
|
-
flowtracks/writers.py,sha256=0LnRiuqmyhcw8Ju3v9vCOERymNmxHG7cXKy6r8uLOTo,10358
|
|
29
|
-
flowtracks/zarr_scene.py,sha256=PKCtiTT1t0Hj74Q572ra5DM3uM8lQK1A6DzzfUxvIzI,10468
|
|
30
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/batch_Lagrangian_to_Eulerian.py,sha256=e7mQFqyqvCeAHRKiN1IaxdNJi0e2FU84c7QNrx0L-m0,2296
|
|
31
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/hdf5_scene_analysis.ipynb,sha256=jn0Z_ZVL1-iXaiKXwyAeb74NbXsBpnocCxCSDh814eU,243097
|
|
32
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/linking_trajectories.ipynb,sha256=udz0GLDiPv68BYySg4yO1HWzaB9Sdy3YA3IhnIHDeII,8820
|
|
33
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_dual_rig_trajectories.py,sha256=dzeK0C-knwq1bPOwo0p1RhWrb0RPp-T31rBaGEptXcc,6737
|
|
34
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_flowtracks.py,sha256=7FIJNwBx9aBRmaM5ofmYTXSWEjE0wZqDfs8J47Tmw6U,9440
|
|
35
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_hdf5_vs_zarr_scene_analysis.py,sha256=bjXlMsLJTxJ0JkvGcVZbuDOFYZU4OvdabtDUgqb15So,4536
|
|
36
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_linking_trajectories_zarr.py,sha256=Ny_WUbiZWo0aKw-u2KGFWrhMv-8BvNgBy0UHIqCobYg,8210
|
|
37
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_phase_averaging_demo.py,sha256=vg6j2ayKGl-5uj08bdzzb6pujzJJQ_5bUsG7UuPP7dM,9270
|
|
38
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_phase_metrics.py,sha256=kBc2e_b-orDrm3AnuUXCcxsvmfQWyCtE826pFeolMGE,9367
|
|
39
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_vtk_writers_preview.py,sha256=Otrhhp-QFf6F94EOs6rWu5xOCpbS16EfWy_iMFp7e_I,14611
|
|
40
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/marimo_zarr_dashboard.py,sha256=jVIum6hdEelH4ef7vRgsfLhrbmMsuErsJBzxrnEImnw,9268
|
|
41
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/repeated_interpolation.ipynb,sha256=t9SfoIWRu5XP0SrphWbQy508uzyEibVz61FGjl51fjA,8861
|
|
42
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/run_pipeline.py,sha256=5fGE2tIP6XjFZ1sEYmqBujuLaXFfRL-rNnbr2p2CDfM,7205
|
|
43
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/visualize_marimo.py,sha256=jWrg5IWjY-bmSQwQjaO5qTr0t_G48CsqJYpevdQrxeY,13026
|
|
44
|
-
flowtracks-1.6.2.dev6.data/data/flowtracks-examples/visualize_results.py,sha256=SbPg3EoDcuIyo0rUz3E-AnT4sLWDVJFhgC6xVcracW4,7583
|
|
45
|
-
flowtracks-1.6.2.dev6.data/scripts/analyse_fhdf.py,sha256=QH08NdImGukTEGuZEKFxjStcSelXBv-8A-22G6KtXqM,1315
|
|
46
|
-
flowtracks-1.6.2.dev6.dist-info/licenses/LICENSE.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
47
|
-
flowtracks-1.6.2.dev6.dist-info/licenses/LICENSES/BSD-3-Clause.txt,sha256=XiJR9O5snDB6sAz0bcncOU6msceZgjPRxejWEQGg40Q,1505
|
|
48
|
-
flowtracks-1.6.2.dev6.dist-info/licenses/LICENSES/GPL-3.0-only.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
49
|
-
flowtracks-1.6.2.dev6.dist-info/METADATA,sha256=jdbbG4k4qcw6lRlEaa1CvQrpWv21S7wDBffoIcyH3Cs,4773
|
|
50
|
-
flowtracks-1.6.2.dev6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
51
|
-
flowtracks-1.6.2.dev6.dist-info/entry_points.txt,sha256=U6028E7yQZQiZoXWMl7w4xgZUPwPxAGiAuuMB__c-yQ,60
|
|
52
|
-
flowtracks-1.6.2.dev6.dist-info/top_level.txt,sha256=cR54RNb9y2Wfr_BfuOezmNJOMB4GfIHh1-luXTREnQA,11
|
|
53
|
-
flowtracks-1.6.2.dev6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowtracks-1.6.2.dev6.data → flowtracks-1.6.2.dev12.data}/data/flowtracks-examples/run_pipeline.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|