geopyv-dev 0.1.0__cp39-cp39-win_amd64.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.
geopyv_dev/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ from ._geopyv_dev import *
2
+ from .plots import (
3
+ inspect_subset,
4
+ inspect_mesh,
5
+ inspect_sequence,
6
+ inspect_particle,
7
+ inspect_field,
8
+ convergence_subset,
9
+ convergence_mesh,
10
+ convergence_sequence,
11
+ contour_mesh,
12
+ contour_field,
13
+ )
14
+ from .wrappers import SubsetWrapper, MeshWrapper, ParticleWrapper, FieldWrapper
geopyv_dev/plots.py ADDED
@@ -0,0 +1,339 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import matplotlib.tri as tri
4
+ from scipy.spatial import Delaunay
5
+
6
+
7
+ def _imshow_or_blank(ax, img_path, **kwargs):
8
+ """Helper: show image from path, or blank background if path is None."""
9
+ kwargs.setdefault("cmap", "gist_gray")
10
+ if img_path is not None:
11
+ import cv2
12
+ img = cv2.imread(img_path, cv2.IMREAD_COLOR)
13
+ img_gs = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(float)
14
+ ax.imshow(img_gs, **kwargs)
15
+ else:
16
+ blank = np.zeros((50, 50), dtype=float)
17
+ ax.imshow(blank, **kwargs)
18
+
19
+
20
+ def _show_save_close(fig, show, block, save):
21
+ if save:
22
+ plt.savefig(save, dpi=600)
23
+ if show:
24
+ plt.show(block=block)
25
+ else:
26
+ plt.close(fig)
27
+
28
+
29
+ def inspect_subset(subset, show=True, block=True, save=False, **kwargs):
30
+ coord = subset.coord
31
+ f_coords = np.asarray(subset.f_coords)
32
+ f = np.asarray(subset.f)
33
+ sssig = subset.sssig
34
+ sigma_intensity = subset.sigma_intensity
35
+
36
+ f_img_path = getattr(subset, 'f_img_path', None)
37
+ template_size = getattr(subset, 'template_size', None)
38
+ if template_size is None:
39
+ template_size = int(np.ceil(np.sqrt(len(f) / np.pi)))
40
+
41
+ # Build display image
42
+ if f_img_path is not None:
43
+ import cv2
44
+ img = cv2.imread(f_img_path, cv2.IMREAD_COLOR)
45
+ img_gs = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY).astype(float)
46
+ x, y = coord
47
+ r = template_size
48
+ x0, x1 = max(0, int(round(x)) - r), min(img_gs.shape[1], int(round(x)) + r + 1)
49
+ y0, y1 = max(0, int(round(y)) - r), min(img_gs.shape[0], int(round(y)) + r + 1)
50
+ display = img_gs[y0:y1, x0:x1]
51
+ else:
52
+ xi = f_coords[:, 0]
53
+ yi = f_coords[:, 1]
54
+ x_min = int(np.floor(xi.min()))
55
+ y_min = int(np.floor(yi.min()))
56
+ x_max = int(np.ceil(xi.max()))
57
+ y_max = int(np.ceil(yi.max()))
58
+ h = y_max - y_min + 1
59
+ w = x_max - x_min + 1
60
+ display = np.full((h, w), float(np.mean(f)))
61
+ for k in range(len(f)):
62
+ ix = int(round(float(xi[k]))) - x_min
63
+ iy = int(round(float(yi[k]))) - y_min
64
+ if 0 <= iy < h and 0 <= ix < w:
65
+ display[iy, ix] = float(f[k])
66
+
67
+ imshow_kwargs = {"cmap": "gist_gray"}
68
+ imshow_kwargs.update(kwargs)
69
+
70
+ fig, ax = plt.subplots()
71
+ ax.imshow(display, **imshow_kwargs)
72
+ ax.text(0.5, -0.05,
73
+ f"Size: {template_size} px; \u03c3_s = {sigma_intensity:.2f}; SSSIG = {sssig:.2E}",
74
+ transform=ax.transAxes, ha="center")
75
+ ax.set_axis_off()
76
+ plt.tight_layout()
77
+ _show_save_close(fig, show, block, save)
78
+ return fig, ax
79
+
80
+
81
+ def inspect_mesh(mesh, subset_idx=None, show_areas=False, show=True, block=True, save=False, **kwargs):
82
+ nodes = np.asarray(mesh.nodes)
83
+ elements = np.asarray(mesh.elements)
84
+ f_img_path = getattr(mesh, 'f_img_path', None)
85
+
86
+ fig, ax = plt.subplots()
87
+
88
+ imshow_kwargs = {"cmap": "gist_gray"}
89
+ imshow_kwargs.update(kwargs)
90
+ _imshow_or_blank(ax, f_img_path, **imshow_kwargs)
91
+
92
+ # Element edges
93
+ n_corner = 3
94
+ for elem in elements:
95
+ corners = nodes[elem[:n_corner]]
96
+ for i in range(n_corner):
97
+ j = (i + 1) % n_corner
98
+ ax.plot([corners[i, 0], corners[j, 0]], [corners[i, 1], corners[j, 1]],
99
+ 'b-', linewidth=0.5)
100
+
101
+ # Element index annotations
102
+ for i, elem in enumerate(elements):
103
+ cx = nodes[elem[:3], 0].mean()
104
+ cy = nodes[elem[:3], 1].mean()
105
+ ax.text(cx, cy, str(i), ha='center', va='center', color='red', fontsize=8)
106
+
107
+ if show_areas:
108
+ from matplotlib.patches import Circle as MplCircle
109
+ radius = 10
110
+ for nd in nodes:
111
+ ax.add_patch(MplCircle((nd[0], nd[1]), radius, alpha=0.2, color='blue'))
112
+
113
+ plt.tight_layout()
114
+ _show_save_close(fig, show, block, save)
115
+ return fig, ax
116
+
117
+
118
+ def inspect_sequence(sequence, mesh_idx=None, subset_idx=None, show=True, block=True, save=False, **kwargs):
119
+ if mesh_idx is None:
120
+ raise ValueError("mesh_idx is required")
121
+ mesh = sequence.mesh_solutions[mesh_idx]
122
+ return inspect_mesh(mesh, subset_idx=subset_idx, show=show, block=block, save=save, **kwargs)
123
+
124
+
125
+ def inspect_particle(particle, show=True, block=True, save=False, **kwargs):
126
+ coords = np.asarray(particle.coordinates)
127
+ initial = coords[0]
128
+ img_path = getattr(particle, 'image_0_path', None)
129
+
130
+ fig, ax = plt.subplots()
131
+ _imshow_or_blank(ax, img_path)
132
+ ax.scatter([initial[0]], [initial[1]], marker='x', color='red', s=100, zorder=10, **kwargs)
133
+ plt.tight_layout()
134
+ _show_save_close(fig, show, block, save)
135
+ return fig, ax
136
+
137
+
138
+ def inspect_field(field, particle_idx=None, show=True, block=True, save=False, **kwargs):
139
+ coords = np.asarray(field.coordinates)
140
+ img_path = getattr(field, 'image_0_path', None)
141
+
142
+ fig, ax = plt.subplots()
143
+ _imshow_or_blank(ax, img_path)
144
+ ax.scatter(coords[:, 0], coords[:, 1], **kwargs)
145
+ if particle_idx is not None:
146
+ ax.scatter([coords[particle_idx, 0]], [coords[particle_idx, 1]],
147
+ color='red', s=100, zorder=10)
148
+ plt.tight_layout()
149
+ _show_save_close(fig, show, block, save)
150
+ return fig, ax
151
+
152
+
153
+ def convergence_subset(subset, show=True, block=True, save=False, **kwargs):
154
+ solve_result = getattr(subset, 'solve_result', None)
155
+ if solve_result is None:
156
+ raise ValueError("Subset has not been solved.")
157
+ history = solve_result["history"]
158
+ max_norm = solve_result.get("max_norm", 1e-3)
159
+ max_iterations = solve_result.get("max_iterations", 50)
160
+
161
+ iters = [h[0] for h in history]
162
+ norms = [h[1] for h in history]
163
+ znccs = [h[2] for h in history]
164
+
165
+ fig, ax = plt.subplots(2, 1, sharex=True)
166
+ ax[0].semilogy(iters, norms, marker="o", **kwargs)
167
+ ax[0].semilogy([min(iters), max(iters)], [max_norm, max_norm], "--r")
168
+ ax[0].set_ylabel(r"$\Delta$ Norm (-)")
169
+ ax[1].plot(iters, znccs, marker="o", **kwargs)
170
+ ax[1].plot([min(iters), max(iters)], [0.75, 0.75], "--r")
171
+ ax[1].set_ylabel(r"$C_{ZNCC}$ (-)")
172
+ ax[1].set_xlabel("Iteration (-)")
173
+ plt.tight_layout()
174
+ _show_save_close(fig, show, block, save)
175
+ return fig, np.array(ax)
176
+
177
+
178
+ def convergence_mesh(mesh, quantity, show=True, block=True, save=False, **kwargs):
179
+ valid = {"C_ZNCC", "iterations", "norm"}
180
+ if quantity not in valid:
181
+ raise ValueError(f"quantity must be one of {sorted(valid)!r}, got {quantity!r}")
182
+
183
+ if quantity == "C_ZNCC":
184
+ data = np.asarray(mesh.c_zncc)
185
+ xlabel = r"$C_{ZNCC}$ (-)"
186
+ elif quantity == "iterations":
187
+ data = np.asarray(mesh.iterations, dtype=float)
188
+ xlabel = "Iterations (-)"
189
+ else:
190
+ data = np.asarray(mesh.norms)
191
+ xlabel = r"$\Delta$ Norm (-)"
192
+
193
+ fig, ax = plt.subplots()
194
+ ax.hist(data, **kwargs)
195
+ ax.set_xlabel(xlabel)
196
+ ax.set_ylabel("Count (-)")
197
+ plt.tight_layout()
198
+ _show_save_close(fig, show, block, save)
199
+ return fig, ax
200
+
201
+
202
+ def convergence_sequence(sequence, mesh_idx=None, subset_idx=None, quantity="C_ZNCC",
203
+ show=True, block=True, save=False, **kwargs):
204
+ if mesh_idx is not None:
205
+ mesh = sequence.mesh_solutions[mesh_idx]
206
+ return convergence_mesh(mesh, quantity, show=show, block=block, save=save, **kwargs)
207
+ else:
208
+ all_data = np.concatenate([np.asarray(m.c_zncc) for m in sequence.mesh_solutions])
209
+ fig, ax = plt.subplots()
210
+ ax.hist(all_data, **kwargs)
211
+ ax.set_xlabel(r"$C_{ZNCC}$ (-)")
212
+ ax.set_ylabel("Count (-)")
213
+ plt.tight_layout()
214
+ _show_save_close(fig, show, block, save)
215
+ return fig, ax
216
+
217
+
218
+ def contour_mesh(mesh, quantity, show=True, block=True, save=False, **kwargs):
219
+ valid = {"C_ZNCC", "iterations", "norm", "u", "v", "R"}
220
+ if quantity not in valid:
221
+ raise ValueError(f"quantity must be one of {sorted(valid)!r}, got {quantity!r}")
222
+
223
+ nodes = np.asarray(mesh.nodes)
224
+ elements = np.asarray(mesh.elements)
225
+ displacements = np.asarray(mesh.displacements)
226
+
227
+ labels = {
228
+ "C_ZNCC": r"$C_{ZNCC}$ (-)",
229
+ "iterations": "Iterations (-)",
230
+ "norm": r"$\Delta$ Norm (-)",
231
+ "u": "u (px)",
232
+ "v": "v (px)",
233
+ "R": "R (px)",
234
+ }
235
+ if quantity == "C_ZNCC":
236
+ values = np.asarray(mesh.c_zncc)
237
+ elif quantity == "iterations":
238
+ values = np.asarray(mesh.iterations, dtype=float)
239
+ elif quantity == "norm":
240
+ values = np.asarray(mesh.norms)
241
+ elif quantity == "u":
242
+ values = displacements[:, 0]
243
+ elif quantity == "v":
244
+ values = displacements[:, 1]
245
+ elif quantity == "R":
246
+ values = np.sqrt(displacements[:, 0]**2 + displacements[:, 1]**2)
247
+
248
+ fig, ax = plt.subplots()
249
+ f_img_path = getattr(mesh, 'f_img_path', None)
250
+ _imshow_or_blank(ax, f_img_path)
251
+
252
+ tri_obj = tri.Triangulation(nodes[:, 0], nodes[:, 1], elements[:, :3])
253
+ cf = ax.tricontourf(tri_obj, values, **kwargs)
254
+ cbar = fig.colorbar(cf, ax=ax)
255
+ cbar.set_label(labels[quantity])
256
+
257
+ plt.tight_layout()
258
+ _show_save_close(fig, show, block, save)
259
+ return fig, ax
260
+
261
+
262
+ def contour_field(field, quantity, window=None, dt=None, absolute=False,
263
+ show=True, block=True, save=False, **kwargs):
264
+ valid = {"u", "v", "R", "ep_xx", "ep_yy", "ep_xy", "ep_vol"}
265
+ if quantity not in valid:
266
+ raise ValueError(f"quantity must be one of {sorted(valid)!r}, got {quantity!r}")
267
+
268
+ particles = field.particles
269
+ n = len(particles)
270
+ coords = np.array([[p.coordinates[0, 0], p.coordinates[0, 1]] for p in particles])
271
+
272
+ strain_col = {"ep_xx": 0, "ep_yy": 1, "ep_xy": 2}
273
+
274
+ # Build window slice
275
+ if window is not None:
276
+ if isinstance(window, (list, tuple)) and len(window) == 2:
277
+ w = slice(window[0], window[1])
278
+ else:
279
+ w = window
280
+ else:
281
+ w = slice(None)
282
+
283
+ values = np.zeros(n)
284
+ for i, p in enumerate(particles):
285
+ warps = np.asarray(p.warps) # (inc_no, warp_len)
286
+ vol_strains = np.asarray(p.vol_strains)
287
+ strains = np.asarray(p.strains) # (inc_no, 6)
288
+
289
+ if quantity == "u":
290
+ v = warps[w, 0]
291
+ elif quantity == "v":
292
+ v = warps[w, 1]
293
+ elif quantity == "R":
294
+ v = np.sqrt(warps[w, 0]**2 + warps[w, 1]**2)
295
+ elif quantity in strain_col:
296
+ v = strains[w, strain_col[quantity]]
297
+ elif quantity == "ep_vol":
298
+ v = vol_strains[w]
299
+
300
+ v = np.atleast_1d(v)
301
+ if dt is None:
302
+ if absolute:
303
+ values[i] = float(np.sum(np.abs(np.diff(v)))) if len(v) > 1 else 0.0
304
+ else:
305
+ values[i] = float(v[-1] - v[0]) if len(v) > 1 else float(v[-1])
306
+ else:
307
+ if len(v) > 1:
308
+ values[i] = float((v[-1] - v[0]) / (len(v) * dt))
309
+ else:
310
+ values[i] = 0.0
311
+
312
+ # Delaunay triangulation
313
+ if n >= 3:
314
+ delaunay = Delaunay(coords)
315
+ triangles = delaunay.simplices
316
+ else:
317
+ triangles = None
318
+
319
+ fig, ax = plt.subplots()
320
+ img_path = getattr(field, 'image_0_path', None)
321
+ _imshow_or_blank(ax, img_path)
322
+
323
+ if triangles is not None and len(triangles) > 0:
324
+ mpl_tri = tri.Triangulation(coords[:, 0], coords[:, 1], triangles)
325
+ cf = ax.tricontourf(mpl_tri, values, **kwargs)
326
+ else:
327
+ cf = ax.scatter(coords[:, 0], coords[:, 1], c=values, **kwargs)
328
+
329
+ if dt is None:
330
+ label = f"{quantity}" + (" (abs)" if absolute else "")
331
+ else:
332
+ label = f"{quantity} /s"
333
+
334
+ cbar = fig.colorbar(cf, ax=ax)
335
+ cbar.set_label(label)
336
+
337
+ plt.tight_layout()
338
+ _show_save_close(fig, show, block, save)
339
+ return fig, ax
geopyv_dev/wrappers.py ADDED
@@ -0,0 +1,57 @@
1
+ from geopyv_dev import plots
2
+
3
+
4
+ class SubsetWrapper:
5
+ def __init__(self, inner):
6
+ self._inner = inner
7
+
8
+ def __getattr__(self, name):
9
+ return getattr(self._inner, name)
10
+
11
+ def inspect(self, **kwargs):
12
+ return plots.inspect_subset(self._inner, **kwargs)
13
+
14
+ def convergence(self, **kwargs):
15
+ return plots.convergence_subset(self._inner, **kwargs)
16
+
17
+
18
+ class MeshWrapper:
19
+ def __init__(self, inner):
20
+ self._inner = inner
21
+
22
+ def __getattr__(self, name):
23
+ return getattr(self._inner, name)
24
+
25
+ def inspect(self, **kwargs):
26
+ return plots.inspect_mesh(self._inner, **kwargs)
27
+
28
+ def convergence(self, quantity="C_ZNCC", **kwargs):
29
+ return plots.convergence_mesh(self._inner, quantity, **kwargs)
30
+
31
+ def contour(self, quantity, **kwargs):
32
+ return plots.contour_mesh(self._inner, quantity, **kwargs)
33
+
34
+
35
+ class ParticleWrapper:
36
+ def __init__(self, inner):
37
+ self._inner = inner
38
+
39
+ def __getattr__(self, name):
40
+ return getattr(self._inner, name)
41
+
42
+ def inspect(self, **kwargs):
43
+ return plots.inspect_particle(self._inner, **kwargs)
44
+
45
+
46
+ class FieldWrapper:
47
+ def __init__(self, inner):
48
+ self._inner = inner
49
+
50
+ def __getattr__(self, name):
51
+ return getattr(self._inner, name)
52
+
53
+ def inspect(self, **kwargs):
54
+ return plots.inspect_field(self._inner, **kwargs)
55
+
56
+ def contour(self, quantity, **kwargs):
57
+ return plots.contour_field(self._inner, quantity, **kwargs)
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: geopyv_dev
3
+ Version: 0.1.0
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Rust
6
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Topic :: Scientific/Engineering
9
+ License-File: LICENSE
10
+ Summary: A PIV/DIC analysis package for geotechnics.
11
+ Author-email: Sam Stanier <sas229@cam.ac.uk>, Jonathan Smith <jdks2@cam.ac.uk>
12
+ License: GPL-3.0-or-later
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
+ Project-URL: Bug Tracker, https://github.com/jdks2/geopyv-dev/issues
16
+ Project-URL: Homepage, https://github.com/jdks2/geopyv-dev
17
+
18
+ # geopyv-dev
19
+ PIV/DIC package for geotechnics. Under active development.
20
+
@@ -0,0 +1,9 @@
1
+ geopyv_dev/__init__.py,sha256=zZ22CcRbt9vlLjmdfOQPTnTAvl0TCVpD6H0rhfSJcN4,354
2
+ geopyv_dev/_geopyv_dev.cp39-win_amd64.pyd,sha256=v6DkvaQo_7HbHIkeH-42KPYJx2QziuUj2Xaw7rD_-uc,4067840
3
+ geopyv_dev/plots.py,sha256=TIxO5gr5AzZyjl_qYNyk3pw9du8uKuPpNHWPDo5aVJA,11989
4
+ geopyv_dev/wrappers.py,sha256=u3vsgrDd3Hc8MffLn8oKGdnBUlYEzNGqCgN92Vl9KhQ,1540
5
+ geopyv_dev-0.1.0.dist-info/METADATA,sha256=VDAfBzOUQv3DGwJ1JnMwUAoj2Hyi_Ve4c3yq2ecNuE8,786
6
+ geopyv_dev-0.1.0.dist-info/WHEEL,sha256=DJX_XFogDpLjEnUqFWUEworxKj63T7Pk_Z9glsI6giw,95
7
+ geopyv_dev-0.1.0.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
8
+ geopyv_dev-0.1.0.dist-info/sboms/geopyv-dev-python.cyclonedx.json,sha256=cIqL2mcOySKIUE5QqA2x9fNQ9waz9PR469tECun1yuQ,178931
9
+ geopyv_dev-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.13.1)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-win_amd64