nodekit 0.2.0__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.
- nodekit/.DS_Store +0 -0
- nodekit/__init__.py +53 -0
- nodekit/_internal/__init__.py +0 -0
- nodekit/_internal/ops/__init__.py +0 -0
- nodekit/_internal/ops/build_site/__init__.py +117 -0
- nodekit/_internal/ops/build_site/harness.j2 +158 -0
- nodekit/_internal/ops/concat.py +51 -0
- nodekit/_internal/ops/open_asset_save_asset.py +125 -0
- nodekit/_internal/ops/play/__init__.py +4 -0
- nodekit/_internal/ops/play/local_runner/__init__.py +0 -0
- nodekit/_internal/ops/play/local_runner/main.py +234 -0
- nodekit/_internal/ops/play/local_runner/site-template.j2 +38 -0
- nodekit/_internal/ops/save_graph_load_graph.py +131 -0
- nodekit/_internal/ops/topological_sorting.py +122 -0
- nodekit/_internal/types/__init__.py +0 -0
- nodekit/_internal/types/actions/__init__.py +0 -0
- nodekit/_internal/types/actions/actions.py +89 -0
- nodekit/_internal/types/assets/__init__.py +151 -0
- nodekit/_internal/types/cards/__init__.py +85 -0
- nodekit/_internal/types/events/__init__.py +0 -0
- nodekit/_internal/types/events/events.py +145 -0
- nodekit/_internal/types/expressions/__init__.py +0 -0
- nodekit/_internal/types/expressions/expressions.py +242 -0
- nodekit/_internal/types/graph.py +42 -0
- nodekit/_internal/types/node.py +21 -0
- nodekit/_internal/types/regions/__init__.py +13 -0
- nodekit/_internal/types/sensors/__init__.py +0 -0
- nodekit/_internal/types/sensors/sensors.py +156 -0
- nodekit/_internal/types/trace.py +17 -0
- nodekit/_internal/types/transition.py +68 -0
- nodekit/_internal/types/value.py +145 -0
- nodekit/_internal/utils/__init__.py +0 -0
- nodekit/_internal/utils/get_browser_bundle.py +35 -0
- nodekit/_internal/utils/get_extension_from_media_type.py +15 -0
- nodekit/_internal/utils/hashing.py +46 -0
- nodekit/_internal/utils/iter_assets.py +61 -0
- nodekit/_internal/version.py +1 -0
- nodekit/_static/nodekit.css +10 -0
- nodekit/_static/nodekit.js +59 -0
- nodekit/actions/__init__.py +25 -0
- nodekit/assets/__init__.py +7 -0
- nodekit/cards/__init__.py +15 -0
- nodekit/events/__init__.py +30 -0
- nodekit/experimental/.DS_Store +0 -0
- nodekit/experimental/__init__.py +0 -0
- nodekit/experimental/recruitment_services/__init__.py +0 -0
- nodekit/experimental/recruitment_services/base.py +77 -0
- nodekit/experimental/recruitment_services/mechanical_turk/__init__.py +0 -0
- nodekit/experimental/recruitment_services/mechanical_turk/client.py +359 -0
- nodekit/experimental/recruitment_services/mechanical_turk/models.py +116 -0
- nodekit/experimental/s3.py +219 -0
- nodekit/experimental/turk_helper.py +223 -0
- nodekit/experimental/visualization/.DS_Store +0 -0
- nodekit/experimental/visualization/__init__.py +0 -0
- nodekit/experimental/visualization/pointer.py +443 -0
- nodekit/expressions/__init__.py +55 -0
- nodekit/sensors/__init__.py +25 -0
- nodekit/transitions/__init__.py +15 -0
- nodekit/values/__init__.py +63 -0
- nodekit-0.2.0.dist-info/METADATA +221 -0
- nodekit-0.2.0.dist-info/RECORD +62 -0
- nodekit-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
|
|
5
|
+
import matplotlib
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from nodekit.events import PointerSampledEvent
|
|
9
|
+
|
|
10
|
+
# import matplotlib
|
|
11
|
+
matplotlib.use("Agg") # safe for headless render
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
from matplotlib import animation
|
|
14
|
+
from matplotlib.colors import to_rgba
|
|
15
|
+
|
|
16
|
+
from typing import Dict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_animation(
|
|
20
|
+
events: Dict[str, List[PointerSampledEvent]], # trace_id -> event stream
|
|
21
|
+
savepath: os.PathLike | str,
|
|
22
|
+
accent_rgba: Tuple[float, float, float, float]
|
|
23
|
+
| Dict[str, Tuple[float, float, float, float]] = (
|
|
24
|
+
49 / 255,
|
|
25
|
+
124 / 255,
|
|
26
|
+
245 / 255,
|
|
27
|
+
0.9,
|
|
28
|
+
),
|
|
29
|
+
neutral_rgba: Tuple[float, float, float, float]
|
|
30
|
+
| Dict[str, Tuple[float, float, float, float]] = (0.1, 0.1, 0.1, 0.3),
|
|
31
|
+
movie_size_px: int = 500,
|
|
32
|
+
movie_time_sec: int = 10,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Render multiple pointer streams in a single transparent .mov.
|
|
36
|
+
|
|
37
|
+
Visual semantics (per trace):
|
|
38
|
+
- MOVE: no dots (only influences trails).
|
|
39
|
+
- DOWN/UP: accent-colored dots; alpha and (UP) size decay over time.
|
|
40
|
+
- Persistent trail: single path from start → 'now'; ALWAYS neutral color; no fade; 1/2 width.
|
|
41
|
+
- Following trail: single segment from previous sample → time-interpolated point toward next sample;
|
|
42
|
+
ALWAYS neutral color; fades with the move tau.
|
|
43
|
+
|
|
44
|
+
Z-index:
|
|
45
|
+
- Traces are layered by insertion order of `events` (earlier keys are below later keys),
|
|
46
|
+
and this order is fixed for the whole movie. Within each trace: persistent trail (lowest),
|
|
47
|
+
then following trail, then DOWN/UP dots (highest).
|
|
48
|
+
|
|
49
|
+
Coordinates:
|
|
50
|
+
- Input x,y in (-0.5, 0.5), right is +x, up is +y. Output is a square of size movie_size_px.
|
|
51
|
+
|
|
52
|
+
Timing:
|
|
53
|
+
- 60 fps, length = movie_time_sec. Samples outside [0, movie_time_sec*1000) ms are ignored.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# ----------------------------
|
|
57
|
+
# Params & scaling
|
|
58
|
+
# ----------------------------
|
|
59
|
+
scale = movie_size_px / 768.0 # baseline-normalized scaling
|
|
60
|
+
|
|
61
|
+
fps = 60
|
|
62
|
+
T_ms = int(movie_time_sec * 1000)
|
|
63
|
+
|
|
64
|
+
# Decays (alpha ~ exp(-dt / tau))
|
|
65
|
+
tau_alpha_ms_move = 500.0
|
|
66
|
+
tau_alpha_ms_down = 700.0
|
|
67
|
+
tau_alpha_ms_up = 600.0
|
|
68
|
+
tau_size_ms_up = tau_alpha_ms_up
|
|
69
|
+
|
|
70
|
+
# Sizes (scatter 's' is points^2)
|
|
71
|
+
down_size0 = 90.0 * scale**2
|
|
72
|
+
up_size0 = 400.0 * scale**2
|
|
73
|
+
|
|
74
|
+
# Line widths
|
|
75
|
+
linewidth_follow = 10.0 * scale # following trail
|
|
76
|
+
linewidth_persist = 0.25 * linewidth_follow # persistent trail
|
|
77
|
+
|
|
78
|
+
# Tail window for DOWN/UP visibility
|
|
79
|
+
tail_window_ms = int(6 * max(tau_alpha_ms_move, tau_alpha_ms_down, tau_alpha_ms_up))
|
|
80
|
+
|
|
81
|
+
# ----------------------------
|
|
82
|
+
# Normalize & validate color specs
|
|
83
|
+
# ----------------------------
|
|
84
|
+
trace_ids = list(events.keys()) # insertion order retained
|
|
85
|
+
|
|
86
|
+
def _normalize_color_spec(
|
|
87
|
+
spec, name: str
|
|
88
|
+
) -> Dict[str, Tuple[float, float, float, float]]:
|
|
89
|
+
if isinstance(spec, dict):
|
|
90
|
+
missing = set(trace_ids) - set(spec.keys())
|
|
91
|
+
extra = set(spec.keys()) - set(trace_ids)
|
|
92
|
+
if missing or extra:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"{name} dict must enumerate exactly all traces. "
|
|
95
|
+
f"Missing: {sorted(missing)}; Extra: {sorted(extra)}"
|
|
96
|
+
)
|
|
97
|
+
return {k: to_rgba(v) for k, v in spec.items()}
|
|
98
|
+
else:
|
|
99
|
+
rgba = to_rgba(spec)
|
|
100
|
+
return {k: rgba for k in trace_ids}
|
|
101
|
+
|
|
102
|
+
per_trace_accent = _normalize_color_spec(accent_rgba, "accent_rgba")
|
|
103
|
+
per_trace_neutral = _normalize_color_spec(neutral_rgba, "neutral_rgba")
|
|
104
|
+
|
|
105
|
+
# ----------------------------
|
|
106
|
+
# Preprocess per-trace arrays (sorted, filtered to movie window)
|
|
107
|
+
# ----------------------------
|
|
108
|
+
streams = [] # list of dicts, in z-order
|
|
109
|
+
for tid in trace_ids:
|
|
110
|
+
ev = [e for e in events[tid] if 0 <= e.t < T_ms]
|
|
111
|
+
if not ev:
|
|
112
|
+
# still create an empty stream so z-ordering remains consistent
|
|
113
|
+
streams.append(
|
|
114
|
+
dict(
|
|
115
|
+
trace_id=tid,
|
|
116
|
+
kinds=np.array([], dtype=object),
|
|
117
|
+
xs=np.array([], dtype=float),
|
|
118
|
+
ys=np.array([], dtype=float),
|
|
119
|
+
ts=np.array([], dtype=int),
|
|
120
|
+
is_down=np.array([], dtype=bool),
|
|
121
|
+
is_up=np.array([], dtype=bool),
|
|
122
|
+
accent=per_trace_accent[tid],
|
|
123
|
+
neutral=per_trace_neutral[tid],
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
ev.sort(key=lambda e: e.t)
|
|
129
|
+
|
|
130
|
+
kinds = np.array([e.kind for e in ev], dtype=object)
|
|
131
|
+
xs = np.array([e.x for e in ev], dtype=float)
|
|
132
|
+
ys = np.array([e.y for e in ev], dtype=float)
|
|
133
|
+
ts = np.array([e.t for e in ev], dtype=int)
|
|
134
|
+
|
|
135
|
+
is_down = kinds == "down"
|
|
136
|
+
is_up = kinds == "up"
|
|
137
|
+
|
|
138
|
+
streams.append(
|
|
139
|
+
dict(
|
|
140
|
+
trace_id=tid,
|
|
141
|
+
kinds=kinds,
|
|
142
|
+
xs=xs,
|
|
143
|
+
ys=ys,
|
|
144
|
+
ts=ts,
|
|
145
|
+
is_down=is_down,
|
|
146
|
+
is_up=is_up,
|
|
147
|
+
accent=per_trace_accent[tid],
|
|
148
|
+
neutral=per_trace_neutral[tid],
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# ----------------------------
|
|
153
|
+
# Figure/axes
|
|
154
|
+
# ----------------------------
|
|
155
|
+
dpi = 100
|
|
156
|
+
fig_inch = movie_size_px / dpi
|
|
157
|
+
fig = plt.figure(figsize=(fig_inch, fig_inch), dpi=dpi)
|
|
158
|
+
ax = plt.axes((0, 0, 1, 1))
|
|
159
|
+
ax.set_xlim(-0.5, 0.5)
|
|
160
|
+
ax.set_ylim(-0.5, 0.5)
|
|
161
|
+
ax.set_aspect("equal", adjustable="box")
|
|
162
|
+
|
|
163
|
+
fig.patch.set_alpha(0.0)
|
|
164
|
+
ax.set_facecolor((0, 0, 0, 0))
|
|
165
|
+
for spine in ax.spines.values():
|
|
166
|
+
spine.set_visible(False)
|
|
167
|
+
ax.set_xticks([])
|
|
168
|
+
ax.set_yticks([])
|
|
169
|
+
|
|
170
|
+
# ----------------------------
|
|
171
|
+
# Artists per trace in insertion order (controls z-index)
|
|
172
|
+
# ----------------------------
|
|
173
|
+
# Within each trace: persistent (lowest), following, points (highest)
|
|
174
|
+
trace_artists = []
|
|
175
|
+
for z, S in enumerate(streams):
|
|
176
|
+
base_z = z * 3 # reserve 3 zorders per trace
|
|
177
|
+
# persistent neutral path
|
|
178
|
+
(persist_line,) = ax.plot(
|
|
179
|
+
[],
|
|
180
|
+
[],
|
|
181
|
+
linewidth=linewidth_persist,
|
|
182
|
+
solid_capstyle="round",
|
|
183
|
+
solid_joinstyle="round",
|
|
184
|
+
zorder=base_z + 0,
|
|
185
|
+
)
|
|
186
|
+
persist_line.set_color(S["neutral"])
|
|
187
|
+
|
|
188
|
+
# following neutral segment (fading)
|
|
189
|
+
(follow_line,) = ax.plot(
|
|
190
|
+
[],
|
|
191
|
+
[],
|
|
192
|
+
linewidth=linewidth_follow,
|
|
193
|
+
solid_capstyle="round",
|
|
194
|
+
solid_joinstyle="round",
|
|
195
|
+
zorder=base_z + 1,
|
|
196
|
+
)
|
|
197
|
+
follow_line.set_color((S["neutral"][0], S["neutral"][1], S["neutral"][2], 0.0))
|
|
198
|
+
|
|
199
|
+
# DOWN/UP scatter only
|
|
200
|
+
scatter = ax.scatter(
|
|
201
|
+
[], [], s=[], facecolors=[], edgecolors="none", zorder=base_z + 2
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
trace_artists.append(
|
|
205
|
+
dict(scatter=scatter, persist=persist_line, follow=follow_line, stream=S)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# ----------------------------
|
|
209
|
+
# Frames
|
|
210
|
+
# ----------------------------
|
|
211
|
+
num_frames = fps * movie_time_sec
|
|
212
|
+
frame_times = np.arange(num_frames, dtype=int) * int(1000 / fps)
|
|
213
|
+
|
|
214
|
+
def init():
|
|
215
|
+
for A in trace_artists:
|
|
216
|
+
A["scatter"].set_offsets(np.empty((0, 2)))
|
|
217
|
+
A["scatter"].set_sizes(np.empty((0,)))
|
|
218
|
+
A["scatter"].set_facecolors(np.empty((0, 4)))
|
|
219
|
+
|
|
220
|
+
A["persist"].set_data([], [])
|
|
221
|
+
# persist color already set per trace
|
|
222
|
+
|
|
223
|
+
A["follow"].set_data([], [])
|
|
224
|
+
n = A["stream"]["neutral"]
|
|
225
|
+
A["follow"].set_color((n[0], n[1], n[2], 0.0))
|
|
226
|
+
|
|
227
|
+
# return a flat tuple of all artists for blitting
|
|
228
|
+
out = []
|
|
229
|
+
for A in trace_artists:
|
|
230
|
+
out.extend([A["scatter"], A["persist"], A["follow"]])
|
|
231
|
+
return tuple(out)
|
|
232
|
+
|
|
233
|
+
def update(frame_idx: int):
|
|
234
|
+
t_now = int(frame_times[frame_idx])
|
|
235
|
+
|
|
236
|
+
drawn = []
|
|
237
|
+
for A in trace_artists:
|
|
238
|
+
S = A["stream"]
|
|
239
|
+
kinds, xs, ys, ts = S["kinds"], S["xs"], S["ys"], S["ts"]
|
|
240
|
+
accent, neutral = S["accent"], S["neutral"]
|
|
241
|
+
|
|
242
|
+
# --- DOWN/UP points within tail window ---
|
|
243
|
+
if ts.size == 0:
|
|
244
|
+
A["scatter"].set_offsets(np.empty((0, 2)))
|
|
245
|
+
A["scatter"].set_sizes(np.empty((0,)))
|
|
246
|
+
A["scatter"].set_facecolors(np.empty((0, 4)))
|
|
247
|
+
else:
|
|
248
|
+
t_min = max(0, t_now - tail_window_ms)
|
|
249
|
+
m = (ts >= t_min) & (ts <= t_now)
|
|
250
|
+
if not np.any(m):
|
|
251
|
+
A["scatter"].set_offsets(np.empty((0, 2)))
|
|
252
|
+
A["scatter"].set_sizes(np.empty((0,)))
|
|
253
|
+
A["scatter"].set_facecolors(np.empty((0, 4)))
|
|
254
|
+
else:
|
|
255
|
+
dt = (t_now - ts[m]).astype(float)
|
|
256
|
+
k = kinds[m]
|
|
257
|
+
xm = xs[m]
|
|
258
|
+
ym = ys[m]
|
|
259
|
+
|
|
260
|
+
down_mask = k == "down"
|
|
261
|
+
up_mask = k == "up"
|
|
262
|
+
|
|
263
|
+
alpha = np.zeros_like(dt, dtype=float)
|
|
264
|
+
alpha[down_mask] = np.exp(-dt[down_mask] / tau_alpha_ms_down)
|
|
265
|
+
alpha[up_mask] = np.exp(-dt[up_mask] / tau_alpha_ms_up)
|
|
266
|
+
|
|
267
|
+
size = np.zeros_like(dt, dtype=float)
|
|
268
|
+
size[down_mask] = down_size0
|
|
269
|
+
size[up_mask] = up_size0 * np.exp(-dt[up_mask] / tau_size_ms_up)
|
|
270
|
+
size = np.clip(size, 0.1, None)
|
|
271
|
+
|
|
272
|
+
keep = down_mask | up_mask
|
|
273
|
+
xm, ym, size, alpha_keep = (
|
|
274
|
+
xm[keep],
|
|
275
|
+
ym[keep],
|
|
276
|
+
size[keep],
|
|
277
|
+
alpha[keep],
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
colors = np.tile(accent, (len(alpha_keep), 1)).astype(float)
|
|
281
|
+
colors[:, 3] = np.clip(colors[:, 3] * alpha_keep, 0.0, 1.0)
|
|
282
|
+
|
|
283
|
+
if xm.size == 0:
|
|
284
|
+
A["scatter"].set_offsets(np.empty((0, 2)))
|
|
285
|
+
A["scatter"].set_sizes(np.empty((0,)))
|
|
286
|
+
A["scatter"].set_facecolors(np.empty((0, 4)))
|
|
287
|
+
else:
|
|
288
|
+
A["scatter"].set_offsets(np.column_stack([xm, ym]))
|
|
289
|
+
A["scatter"].set_sizes(size)
|
|
290
|
+
A["scatter"].set_facecolors(colors)
|
|
291
|
+
|
|
292
|
+
# --- Persistent trail: single path up to now (neutral, no fade) ---
|
|
293
|
+
if ts.size >= 1:
|
|
294
|
+
vx, vy = [], []
|
|
295
|
+
for i in range(ts.size):
|
|
296
|
+
if ts[i] > t_now:
|
|
297
|
+
break
|
|
298
|
+
if i == ts.size - 1:
|
|
299
|
+
vx.append(xs[i])
|
|
300
|
+
vy.append(ys[i])
|
|
301
|
+
else:
|
|
302
|
+
t0, t1 = ts[i], ts[i + 1]
|
|
303
|
+
x0, y0 = xs[i], ys[i]
|
|
304
|
+
x1, y1 = xs[i + 1], ys[i + 1]
|
|
305
|
+
vx.append(x0)
|
|
306
|
+
vy.append(y0)
|
|
307
|
+
if t0 <= t_now < t1:
|
|
308
|
+
frac = (t_now - t0) / (t1 - t0)
|
|
309
|
+
vx.append(x0 + frac * (x1 - x0))
|
|
310
|
+
vy.append(y0 + frac * (y1 - y0))
|
|
311
|
+
break
|
|
312
|
+
else:
|
|
313
|
+
vx.append(x1)
|
|
314
|
+
vy.append(y1)
|
|
315
|
+
|
|
316
|
+
if len(vx) >= 2:
|
|
317
|
+
A["persist"].set_data(vx, vy)
|
|
318
|
+
A["persist"].set_color(neutral)
|
|
319
|
+
else:
|
|
320
|
+
A["persist"].set_data([], [])
|
|
321
|
+
else:
|
|
322
|
+
A["persist"].set_data([], [])
|
|
323
|
+
|
|
324
|
+
# --- Following trail: single segment prev -> interpolated now (neutral, fades) ---
|
|
325
|
+
A["follow"].set_data([], [])
|
|
326
|
+
A["follow"].set_color((neutral[0], neutral[1], neutral[2], 0.0))
|
|
327
|
+
if ts.size >= 2:
|
|
328
|
+
idx_prev = np.searchsorted(ts, t_now, side="right") - 1
|
|
329
|
+
if (
|
|
330
|
+
0 <= idx_prev < ts.size - 1
|
|
331
|
+
and ts[idx_prev] <= t_now < ts[idx_prev + 1]
|
|
332
|
+
):
|
|
333
|
+
t0, t1 = ts[idx_prev], ts[idx_prev + 1]
|
|
334
|
+
x0, y0 = xs[idx_prev], ys[idx_prev]
|
|
335
|
+
x1, y1 = xs[idx_prev + 1], ys[idx_prev + 1]
|
|
336
|
+
frac = (t_now - t0) / (t1 - t0)
|
|
337
|
+
x_star = x0 + frac * (x1 - x0)
|
|
338
|
+
y_star = y0 + frac * (y1 - y0)
|
|
339
|
+
|
|
340
|
+
alpha_line = float(np.exp(-(t_now - t0) / float(tau_alpha_ms_move)))
|
|
341
|
+
rgba = (
|
|
342
|
+
neutral[0],
|
|
343
|
+
neutral[1],
|
|
344
|
+
neutral[2],
|
|
345
|
+
np.clip(neutral[3] * alpha_line, 0.0, 1.0),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
A["follow"].set_data([x0, x_star], [y0, y_star])
|
|
349
|
+
A["follow"].set_color(rgba)
|
|
350
|
+
|
|
351
|
+
drawn.extend([A["scatter"], A["persist"], A["follow"]])
|
|
352
|
+
|
|
353
|
+
return tuple(drawn)
|
|
354
|
+
|
|
355
|
+
anim = animation.FuncAnimation(
|
|
356
|
+
fig,
|
|
357
|
+
update,
|
|
358
|
+
init_func=init,
|
|
359
|
+
frames=fps * movie_time_sec,
|
|
360
|
+
interval=1000 / fps,
|
|
361
|
+
blit=True,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# ----------------------------
|
|
365
|
+
# Save with transparent background
|
|
366
|
+
# ----------------------------
|
|
367
|
+
savepath = Path(savepath)
|
|
368
|
+
savepath.parent.mkdir(parents=True, exist_ok=True)
|
|
369
|
+
if savepath.suffix.lower() != ".mov":
|
|
370
|
+
savepath = savepath.with_suffix(".mov")
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
writer = animation.FFMpegWriter(
|
|
374
|
+
fps=fps,
|
|
375
|
+
codec="prores_ks",
|
|
376
|
+
extra_args=[
|
|
377
|
+
"-pix_fmt",
|
|
378
|
+
"yuva444p10le",
|
|
379
|
+
"-profile:v",
|
|
380
|
+
"4444",
|
|
381
|
+
"-vendor",
|
|
382
|
+
"ap10",
|
|
383
|
+
"-bits_per_mb",
|
|
384
|
+
"8000",
|
|
385
|
+
],
|
|
386
|
+
bitrate=-1,
|
|
387
|
+
)
|
|
388
|
+
anim.save(
|
|
389
|
+
str(savepath),
|
|
390
|
+
writer=writer,
|
|
391
|
+
dpi=dpi,
|
|
392
|
+
savefig_kwargs={"transparent": True, "facecolor": (0, 0, 0, 0)},
|
|
393
|
+
)
|
|
394
|
+
except Exception:
|
|
395
|
+
writer = animation.FFMpegWriter(
|
|
396
|
+
fps=fps,
|
|
397
|
+
codec="png",
|
|
398
|
+
extra_args=["-pix_fmt", "rgba"],
|
|
399
|
+
bitrate=-1,
|
|
400
|
+
)
|
|
401
|
+
anim.save(
|
|
402
|
+
str(savepath),
|
|
403
|
+
writer=writer,
|
|
404
|
+
dpi=dpi,
|
|
405
|
+
savefig_kwargs={"transparent": True, "facecolor": (0, 0, 0, 0)},
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
plt.close(fig)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# %%
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
import json
|
|
414
|
+
|
|
415
|
+
payload = json.loads(
|
|
416
|
+
Path("example_pointer_events.json").read_text()
|
|
417
|
+
) # hit_id: List[PointerSampledEvent]
|
|
418
|
+
# payload = json.loads(Path('/Users/mjl/Library/Application Support/JetBrains/PyCharm2025.1/scratches/nodekit-turk/fitts-law-pointers-example_pointer_events.json').read_text()) # hit_id: List[PointerSampledEvent]
|
|
419
|
+
pointer_events = {
|
|
420
|
+
hit_id: [PointerSampledEvent.model_validate(ev) for ev in payload[hit_id]]
|
|
421
|
+
for hit_id in sorted(payload.keys())
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
# Get minimum time and left shift everything
|
|
425
|
+
max_t_grand = 0
|
|
426
|
+
for hit_id in pointer_events.keys():
|
|
427
|
+
min_t = min([ev.t for ev in pointer_events[hit_id]])
|
|
428
|
+
max_t = max([ev.t for ev in pointer_events[hit_id]])
|
|
429
|
+
if max_t > max_t_grand:
|
|
430
|
+
max_t_grand = max_t
|
|
431
|
+
|
|
432
|
+
for ev in pointer_events[hit_id]:
|
|
433
|
+
ev.t -= min_t
|
|
434
|
+
|
|
435
|
+
movie_time_sec = int(min(10, max_t_grand / 1000))
|
|
436
|
+
make_animation(
|
|
437
|
+
events=pointer_events,
|
|
438
|
+
accent_rgba=(49 / 255, 124 / 255, 245 / 255, 0.9),
|
|
439
|
+
neutral_rgba=(0.1, 0.1, 0.1, 0.3),
|
|
440
|
+
savepath="movie.mov",
|
|
441
|
+
movie_size_px=500,
|
|
442
|
+
movie_time_sec=movie_time_sec,
|
|
443
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"Reg",
|
|
3
|
+
"Local",
|
|
4
|
+
"LastAction",
|
|
5
|
+
"GetListItem",
|
|
6
|
+
"GetDictValue",
|
|
7
|
+
"Lit",
|
|
8
|
+
"If",
|
|
9
|
+
"Not",
|
|
10
|
+
"Or",
|
|
11
|
+
"And",
|
|
12
|
+
"Eq",
|
|
13
|
+
"Ne",
|
|
14
|
+
"Gt",
|
|
15
|
+
"Ge",
|
|
16
|
+
"Lt",
|
|
17
|
+
"Le",
|
|
18
|
+
"Add",
|
|
19
|
+
"Sub",
|
|
20
|
+
"Mul",
|
|
21
|
+
"Div",
|
|
22
|
+
"Slice",
|
|
23
|
+
"Map",
|
|
24
|
+
"Filter",
|
|
25
|
+
"Fold",
|
|
26
|
+
"Expression",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
from nodekit._internal.types.expressions.expressions import (
|
|
30
|
+
Reg,
|
|
31
|
+
Local,
|
|
32
|
+
LastAction,
|
|
33
|
+
GetListItem,
|
|
34
|
+
GetDictValue,
|
|
35
|
+
Lit,
|
|
36
|
+
If,
|
|
37
|
+
Not,
|
|
38
|
+
Or,
|
|
39
|
+
And,
|
|
40
|
+
Eq,
|
|
41
|
+
Ne,
|
|
42
|
+
Gt,
|
|
43
|
+
Ge,
|
|
44
|
+
Lt,
|
|
45
|
+
Le,
|
|
46
|
+
Add,
|
|
47
|
+
Sub,
|
|
48
|
+
Mul,
|
|
49
|
+
Div,
|
|
50
|
+
Slice,
|
|
51
|
+
Map,
|
|
52
|
+
Filter,
|
|
53
|
+
Fold,
|
|
54
|
+
Expression,
|
|
55
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"WaitSensor",
|
|
3
|
+
"ClickSensor",
|
|
4
|
+
"KeySensor",
|
|
5
|
+
"SelectSensor",
|
|
6
|
+
"MultiSelectSensor",
|
|
7
|
+
"SliderSensor",
|
|
8
|
+
"TextEntrySensor",
|
|
9
|
+
"ProductSensor",
|
|
10
|
+
"SumSensor",
|
|
11
|
+
"Sensor",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
from nodekit._internal.types.sensors.sensors import (
|
|
15
|
+
WaitSensor,
|
|
16
|
+
ClickSensor,
|
|
17
|
+
KeySensor,
|
|
18
|
+
SelectSensor,
|
|
19
|
+
MultiSelectSensor,
|
|
20
|
+
SliderSensor,
|
|
21
|
+
TextEntrySensor,
|
|
22
|
+
ProductSensor,
|
|
23
|
+
SumSensor,
|
|
24
|
+
Sensor,
|
|
25
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Public value types and aliases used across NodeKit models."""
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"Value",
|
|
5
|
+
"Boolean",
|
|
6
|
+
"Integer",
|
|
7
|
+
"Float",
|
|
8
|
+
"String",
|
|
9
|
+
"List",
|
|
10
|
+
"Dict",
|
|
11
|
+
"LeafValue",
|
|
12
|
+
# Space
|
|
13
|
+
"SpatialSize",
|
|
14
|
+
"SpatialPoint",
|
|
15
|
+
"Mask",
|
|
16
|
+
# Time
|
|
17
|
+
"TimeElapsedMsec",
|
|
18
|
+
"TimeDurationMsec",
|
|
19
|
+
# Text
|
|
20
|
+
"MarkdownString",
|
|
21
|
+
"ColorHexString",
|
|
22
|
+
# Keyboard
|
|
23
|
+
"PressableKey",
|
|
24
|
+
# Assets
|
|
25
|
+
"SHA256",
|
|
26
|
+
"ImageMediaType",
|
|
27
|
+
"VideoMediaType",
|
|
28
|
+
"MediaType",
|
|
29
|
+
# Identifiers
|
|
30
|
+
"NodeId",
|
|
31
|
+
"RegisterId",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
from nodekit._internal.types.value import (
|
|
35
|
+
Value,
|
|
36
|
+
Boolean,
|
|
37
|
+
Integer,
|
|
38
|
+
Float,
|
|
39
|
+
String,
|
|
40
|
+
List,
|
|
41
|
+
Dict,
|
|
42
|
+
LeafValue,
|
|
43
|
+
# Space
|
|
44
|
+
SpatialSize,
|
|
45
|
+
SpatialPoint,
|
|
46
|
+
Mask,
|
|
47
|
+
# Time
|
|
48
|
+
TimeElapsedMsec,
|
|
49
|
+
TimeDurationMsec,
|
|
50
|
+
# Text
|
|
51
|
+
MarkdownString,
|
|
52
|
+
ColorHexString,
|
|
53
|
+
# Keyboard
|
|
54
|
+
PressableKey,
|
|
55
|
+
# Assets
|
|
56
|
+
SHA256,
|
|
57
|
+
ImageMediaType,
|
|
58
|
+
VideoMediaType,
|
|
59
|
+
MediaType,
|
|
60
|
+
# Identifiers
|
|
61
|
+
NodeId,
|
|
62
|
+
RegisterId,
|
|
63
|
+
)
|