pivtools 0.1.3__cp311-cp311-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.
- pivtools-0.1.3.dist-info/METADATA +222 -0
- pivtools-0.1.3.dist-info/RECORD +127 -0
- pivtools-0.1.3.dist-info/WHEEL +5 -0
- pivtools-0.1.3.dist-info/entry_points.txt +3 -0
- pivtools-0.1.3.dist-info/top_level.txt +3 -0
- pivtools_cli/__init__.py +5 -0
- pivtools_cli/_build_marker.c +25 -0
- pivtools_cli/_build_marker.cp311-win_amd64.pyd +0 -0
- pivtools_cli/cli.py +225 -0
- pivtools_cli/example.py +139 -0
- pivtools_cli/lib/PIV_2d_cross_correlate.c +334 -0
- pivtools_cli/lib/PIV_2d_cross_correlate.h +22 -0
- pivtools_cli/lib/common.h +36 -0
- pivtools_cli/lib/interp2custom.c +146 -0
- pivtools_cli/lib/interp2custom.h +48 -0
- pivtools_cli/lib/peak_locate_gsl.c +711 -0
- pivtools_cli/lib/peak_locate_gsl.h +40 -0
- pivtools_cli/lib/peak_locate_gsl_print.c +736 -0
- pivtools_cli/lib/peak_locate_lm.c +751 -0
- pivtools_cli/lib/peak_locate_lm.h +27 -0
- pivtools_cli/lib/xcorr.c +342 -0
- pivtools_cli/lib/xcorr.h +31 -0
- pivtools_cli/lib/xcorr_cache.c +78 -0
- pivtools_cli/lib/xcorr_cache.h +26 -0
- pivtools_cli/piv/interp2custom/interp2custom.py +69 -0
- pivtools_cli/piv/piv.py +240 -0
- pivtools_cli/piv/piv_backend/base.py +825 -0
- pivtools_cli/piv/piv_backend/cpu_instantaneous.py +1005 -0
- pivtools_cli/piv/piv_backend/factory.py +28 -0
- pivtools_cli/piv/piv_backend/gpu_instantaneous.py +15 -0
- pivtools_cli/piv/piv_backend/infilling.py +445 -0
- pivtools_cli/piv/piv_backend/outlier_detection.py +306 -0
- pivtools_cli/piv/piv_backend/profile_cpu_instantaneous.py +230 -0
- pivtools_cli/piv/piv_result.py +40 -0
- pivtools_cli/piv/save_results.py +342 -0
- pivtools_cli/piv_cluster/cluster.py +108 -0
- pivtools_cli/preprocessing/filters.py +399 -0
- pivtools_cli/preprocessing/preprocess.py +79 -0
- pivtools_cli/tests/helpers.py +107 -0
- pivtools_cli/tests/instantaneous_piv/test_piv_integration.py +167 -0
- pivtools_cli/tests/instantaneous_piv/test_piv_integration_multi.py +553 -0
- pivtools_cli/tests/preprocessing/test_filters.py +41 -0
- pivtools_core/__init__.py +5 -0
- pivtools_core/config.py +703 -0
- pivtools_core/config.yaml +135 -0
- pivtools_core/image_handling/__init__.py +0 -0
- pivtools_core/image_handling/load_images.py +464 -0
- pivtools_core/image_handling/readers/__init__.py +53 -0
- pivtools_core/image_handling/readers/generic_readers.py +50 -0
- pivtools_core/image_handling/readers/lavision_reader.py +190 -0
- pivtools_core/image_handling/readers/registry.py +24 -0
- pivtools_core/paths.py +49 -0
- pivtools_core/vector_loading.py +248 -0
- pivtools_gui/__init__.py +3 -0
- pivtools_gui/app.py +687 -0
- pivtools_gui/calibration/__init__.py +0 -0
- pivtools_gui/calibration/app/__init__.py +0 -0
- pivtools_gui/calibration/app/views.py +1186 -0
- pivtools_gui/calibration/calibration_planar/planar_calibration_production.py +570 -0
- pivtools_gui/calibration/vector_calibration_production.py +544 -0
- pivtools_gui/config.py +703 -0
- pivtools_gui/image_handling/__init__.py +0 -0
- pivtools_gui/image_handling/load_images.py +464 -0
- pivtools_gui/image_handling/readers/__init__.py +53 -0
- pivtools_gui/image_handling/readers/generic_readers.py +50 -0
- pivtools_gui/image_handling/readers/lavision_reader.py +190 -0
- pivtools_gui/image_handling/readers/registry.py +24 -0
- pivtools_gui/masking/__init__.py +0 -0
- pivtools_gui/masking/app/__init__.py +0 -0
- pivtools_gui/masking/app/views.py +123 -0
- pivtools_gui/paths.py +49 -0
- pivtools_gui/piv_runner.py +261 -0
- pivtools_gui/pivtools.py +58 -0
- pivtools_gui/plotting/__init__.py +0 -0
- pivtools_gui/plotting/app/__init__.py +0 -0
- pivtools_gui/plotting/app/views.py +1671 -0
- pivtools_gui/plotting/plot_maker.py +220 -0
- pivtools_gui/post_processing/POD/__init__.py +0 -0
- pivtools_gui/post_processing/POD/app/__init__.py +0 -0
- pivtools_gui/post_processing/POD/app/views.py +647 -0
- pivtools_gui/post_processing/POD/pod_decompose.py +979 -0
- pivtools_gui/post_processing/POD/views.py +1096 -0
- pivtools_gui/post_processing/__init__.py +0 -0
- pivtools_gui/static/404.html +1 -0
- pivtools_gui/static/_next/static/chunks/117-d5793c8e79de5511.js +2 -0
- pivtools_gui/static/_next/static/chunks/484-cfa8b9348ce4f00e.js +1 -0
- pivtools_gui/static/_next/static/chunks/869-320a6b9bdafbb6d3.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/_not-found/page-12f067ceb7415e55.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/layout-b907d5f31ac82e9d.js +1 -0
- pivtools_gui/static/_next/static/chunks/app/page-334cc4e8444cde2f.js +1 -0
- pivtools_gui/static/_next/static/chunks/fd9d1056-ad15f396ddf9b7e5.js +1 -0
- pivtools_gui/static/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- pivtools_gui/static/_next/static/chunks/main-a1b3ced4d5f6d998.js +1 -0
- pivtools_gui/static/_next/static/chunks/main-app-8a63c6f5e7baee11.js +1 -0
- pivtools_gui/static/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- pivtools_gui/static/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- pivtools_gui/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- pivtools_gui/static/_next/static/chunks/webpack-4a8ca7c99e9bb3d8.js +1 -0
- pivtools_gui/static/_next/static/css/7d3f2337d7ea12a5.css +3 -0
- pivtools_gui/static/_next/static/vQeR20OUdSSKlK4vukC4q/_buildManifest.js +1 -0
- pivtools_gui/static/_next/static/vQeR20OUdSSKlK4vukC4q/_ssgManifest.js +1 -0
- pivtools_gui/static/file.svg +1 -0
- pivtools_gui/static/globe.svg +1 -0
- pivtools_gui/static/grid.svg +8 -0
- pivtools_gui/static/index.html +1 -0
- pivtools_gui/static/index.txt +8 -0
- pivtools_gui/static/next.svg +1 -0
- pivtools_gui/static/vercel.svg +1 -0
- pivtools_gui/static/window.svg +1 -0
- pivtools_gui/stereo_reconstruction/__init__.py +0 -0
- pivtools_gui/stereo_reconstruction/app/__init__.py +0 -0
- pivtools_gui/stereo_reconstruction/app/views.py +1985 -0
- pivtools_gui/stereo_reconstruction/stereo_calibration_production.py +606 -0
- pivtools_gui/stereo_reconstruction/stereo_reconstruction_production.py +544 -0
- pivtools_gui/utils.py +63 -0
- pivtools_gui/vector_loading.py +248 -0
- pivtools_gui/vector_merging/__init__.py +1 -0
- pivtools_gui/vector_merging/app/__init__.py +1 -0
- pivtools_gui/vector_merging/app/views.py +759 -0
- pivtools_gui/vector_statistics/app/__init__.py +1 -0
- pivtools_gui/vector_statistics/app/views.py +710 -0
- pivtools_gui/vector_statistics/ensemble_statistics.py +49 -0
- pivtools_gui/vector_statistics/instantaneous_statistics.py +311 -0
- pivtools_gui/video_maker/__init__.py +0 -0
- pivtools_gui/video_maker/app/__init__.py +0 -0
- pivtools_gui/video_maker/app/views.py +436 -0
- pivtools_gui/video_maker/video_maker.py +662 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..config import Config
|
|
4
|
+
from ..paths import get_data_paths
|
|
5
|
+
from ..vector_loading import load_vectors_from_directory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def ensemble_statistics(cam_num: int, config: Config, base):
|
|
9
|
+
"""
|
|
10
|
+
Placeholder ensemble stats: compute global mean (ux, uy) across all frames (same as instantaneous for now).
|
|
11
|
+
Extend later with RMS, turbulence quantities, etc.
|
|
12
|
+
"""
|
|
13
|
+
if config.statistics_extraction is None:
|
|
14
|
+
return
|
|
15
|
+
for entry in config.statistics_extraction:
|
|
16
|
+
if entry.get("type") != "ensemble":
|
|
17
|
+
continue
|
|
18
|
+
endpoint = entry.get("endpoint", "")
|
|
19
|
+
use_merged = entry.get("use_merged", False)
|
|
20
|
+
if use_merged and cam_num != config.camera_numbers[0]:
|
|
21
|
+
continue
|
|
22
|
+
cam_folder_eff = "Merged" if use_merged else f"Cam{cam_num}"
|
|
23
|
+
paths = get_data_paths(
|
|
24
|
+
base_dir=base,
|
|
25
|
+
num_images=config.num_images,
|
|
26
|
+
cam_folder=cam_folder_eff,
|
|
27
|
+
type="ensemble",
|
|
28
|
+
endpoint=endpoint,
|
|
29
|
+
use_merged=use_merged,
|
|
30
|
+
)
|
|
31
|
+
if not paths["data_dir"].exists():
|
|
32
|
+
print(f"[ensemble] Data dir missing: {paths['data_dir']}")
|
|
33
|
+
continue
|
|
34
|
+
paths["stats_dir"].mkdir(parents=True, exist_ok=True)
|
|
35
|
+
arr = load_vectors_from_directory(paths["data_dir"], config)
|
|
36
|
+
ux = arr[:, 0]
|
|
37
|
+
uy = arr[:, 1]
|
|
38
|
+
mean_ux = ux.mean(axis=0).compute()
|
|
39
|
+
mean_uy = uy.mean(axis=0).compute()
|
|
40
|
+
out_file = paths["stats_dir"] / (
|
|
41
|
+
f"{'merged' if use_merged else f'Cam{cam_num}'}_ensemble_mean.npz"
|
|
42
|
+
)
|
|
43
|
+
np.savez_compressed(
|
|
44
|
+
out_file,
|
|
45
|
+
mean_ux=mean_ux,
|
|
46
|
+
mean_uy=mean_uy,
|
|
47
|
+
meta=dict(endpoint=endpoint, use_merged=use_merged, camera=cam_folder_eff),
|
|
48
|
+
)
|
|
49
|
+
print(f"[ensemble] Saved ensemble mean -> {out_file}")
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import dask # added
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.io import savemat
|
|
5
|
+
|
|
6
|
+
from ..config import Config
|
|
7
|
+
from ..paths import get_data_paths
|
|
8
|
+
from ..plotting.plot_maker import make_scalar_settings, plot_scalar_field
|
|
9
|
+
from ..vector_loading import (
|
|
10
|
+
load_coords_from_directory,
|
|
11
|
+
load_vectors_from_directory,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def instantaneous_statistics(cam_num: int, config: Config, base):
|
|
16
|
+
"""
|
|
17
|
+
Compute mean ux, uy for each instantaneous statistics_extraction entry for a given camera.
|
|
18
|
+
Saves npz in stats_dir.
|
|
19
|
+
Skips duplicated merged runs (only first camera processed for merged).
|
|
20
|
+
"""
|
|
21
|
+
print(f"[instantaneous] Starting statistics for cam_num={cam_num}")
|
|
22
|
+
if config.statistics_extraction is None:
|
|
23
|
+
return
|
|
24
|
+
for entry in config.statistics_extraction:
|
|
25
|
+
print(f"[instantaneous] Processing entry: {entry}")
|
|
26
|
+
if entry.get("type") != "instantaneous":
|
|
27
|
+
print("[instantaneous] Skipping entry (not instantaneous type)")
|
|
28
|
+
continue
|
|
29
|
+
endpoint = entry.get("endpoint", "")
|
|
30
|
+
use_merged = entry.get("use_merged", False)
|
|
31
|
+
# Avoid repeating merged computation per camera
|
|
32
|
+
if use_merged and cam_num != config.camera_numbers[0]:
|
|
33
|
+
print(
|
|
34
|
+
f"[instantaneous] Skipping merged for cam_num={cam_num} (only first camera processes merged)"
|
|
35
|
+
)
|
|
36
|
+
continue
|
|
37
|
+
cam_folder_eff = "Merged" if use_merged else f"Cam{cam_num}"
|
|
38
|
+
print(f"[instantaneous] cam_folder_eff: {cam_folder_eff}")
|
|
39
|
+
paths = get_data_paths(
|
|
40
|
+
base_dir=base,
|
|
41
|
+
num_images=config.num_images,
|
|
42
|
+
cam_folder=cam_folder_eff,
|
|
43
|
+
type_name="instantaneous", # renamed from type
|
|
44
|
+
endpoint=endpoint,
|
|
45
|
+
use_merged=use_merged,
|
|
46
|
+
)
|
|
47
|
+
print(f"[instantaneous] Data dir: {paths['data_dir']}")
|
|
48
|
+
if not paths["data_dir"].exists():
|
|
49
|
+
print(f"[instantaneous] Data dir missing: {paths['data_dir']}")
|
|
50
|
+
continue
|
|
51
|
+
paths["stats_dir"].mkdir(parents=True, exist_ok=True)
|
|
52
|
+
# Create nested mean_stats directory
|
|
53
|
+
mean_stats_dir = paths["stats_dir"] / "mean_stats"
|
|
54
|
+
mean_stats_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
print(f"[instantaneous] Loading vectors from {paths['data_dir']}")
|
|
56
|
+
# Load all requested passes at once; config.instantaneous_runs is 1-based
|
|
57
|
+
selected_runs_1based = (
|
|
58
|
+
list(config.instantaneous_runs) if config.instantaneous_runs else []
|
|
59
|
+
)
|
|
60
|
+
print(f"[instantaneous] Selected runs/passes (1-based): {selected_runs_1based}")
|
|
61
|
+
arr = load_vectors_from_directory(
|
|
62
|
+
paths["data_dir"], config, runs=selected_runs_1based
|
|
63
|
+
) # (N,R,3,H,W) or (N,R,4,H,W) if stereo
|
|
64
|
+
# co-rodinates need to be loaded
|
|
65
|
+
print(f"[instantaneous] Loaded array shape: {arr.shape}")
|
|
66
|
+
|
|
67
|
+
# Load coordinates for the same selected runs
|
|
68
|
+
coords_x_list, coords_y_list = load_coords_from_directory(
|
|
69
|
+
paths["data_dir"], runs=selected_runs_1based
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Check for stereo (assume config.stereo is a boolean flag)
|
|
73
|
+
stereo = getattr(config, "stereo", False)
|
|
74
|
+
if stereo:
|
|
75
|
+
if arr.shape[2] < 4:
|
|
76
|
+
print("[instantaneous] Stereo enabled but array has fewer than 4 components; skipping uz stresses")
|
|
77
|
+
stereo = False
|
|
78
|
+
else:
|
|
79
|
+
print("[instantaneous] Stereo enabled; computing uz stresses")
|
|
80
|
+
|
|
81
|
+
# Components: 0=ux, 1=uy, 2=uz (if stereo), 3=b_mask (if stereo) or 2=b_mask
|
|
82
|
+
ux = arr[:, :, 0] # (N,R,H,W)
|
|
83
|
+
uy = arr[:, :, 1] # (N,R,H,W)
|
|
84
|
+
if stereo:
|
|
85
|
+
uz = arr[:, :, 2] # (N,R,H,W)
|
|
86
|
+
bmask = arr[:, :, 3] # (N,R,H,W)
|
|
87
|
+
else:
|
|
88
|
+
bmask = arr[:, :, 2] # (N,R,H,W)
|
|
89
|
+
|
|
90
|
+
print("[instantaneous] Computing mean ux and uy per selected pass")
|
|
91
|
+
# Build lazy reductions
|
|
92
|
+
mean_ux_da = ux.mean(axis=0) # (R,H,W)
|
|
93
|
+
mean_uy_da = uy.mean(axis=0) # (R,H,W)
|
|
94
|
+
|
|
95
|
+
# b_masks are identical across time -> take first time instance lazily
|
|
96
|
+
print("[instantaneous] Using b_mask from first time instance per selected pass")
|
|
97
|
+
b_mask_da = bmask[0] # (R,H,W)
|
|
98
|
+
|
|
99
|
+
# Compute Reynolds stress ingredients lazily
|
|
100
|
+
print("[instantaneous] Computing Reynolds stresses per selected pass")
|
|
101
|
+
E_ux2_da = (ux**2).mean(axis=0) # (R,H,W)
|
|
102
|
+
E_uy2_da = (uy**2).mean(axis=0) # (R,H,W)
|
|
103
|
+
E_uxuy_da = (ux * uy).mean(axis=0) # (R,H,W)
|
|
104
|
+
if stereo:
|
|
105
|
+
mean_uz_da = uz.mean(axis=0) # (R,H,W)
|
|
106
|
+
E_uz2_da = (uz**2).mean(axis=0) # (R,H,W)
|
|
107
|
+
E_uxuz_da = (ux * uz).mean(axis=0) # (R,H,W)
|
|
108
|
+
E_uyuz_da = (uy * uz).mean(axis=0) # (R,H,W)
|
|
109
|
+
|
|
110
|
+
# Execute all reductions in one graph run
|
|
111
|
+
if stereo:
|
|
112
|
+
mean_ux_all, mean_uy_all, mean_uz_all, b_mask_all, E_ux2, E_uy2, E_uxuy, E_uz2, E_uxuz, E_uyuz = dask.compute(
|
|
113
|
+
mean_ux_da, mean_uy_da, mean_uz_da, b_mask_da, E_ux2_da, E_uy2_da, E_uxuy_da, E_uz2_da, E_uxuz_da, E_uyuz_da
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
mean_ux_all, mean_uy_all, b_mask_all, E_ux2, E_uy2, E_uxuy = dask.compute(
|
|
117
|
+
mean_ux_da, mean_uy_da, b_mask_da, E_ux2_da, E_uy2_da, E_uxuy_da
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Finish Reynolds stresses on NumPy arrays
|
|
121
|
+
uu_all = E_ux2 - mean_ux_all**2 # (R,H,W)
|
|
122
|
+
uv_all = E_uxuy - (mean_ux_all * mean_uy_all)
|
|
123
|
+
vv_all = E_uy2 - mean_uy_all**2
|
|
124
|
+
if stereo:
|
|
125
|
+
uw_all = E_uxuz - (mean_ux_all * mean_uz_all)
|
|
126
|
+
vw_all = E_uyuz - (mean_uy_all * mean_uz_all)
|
|
127
|
+
ww_all = E_uz2 - mean_uz_all**2
|
|
128
|
+
|
|
129
|
+
# Determine labels (1-based) for selected passes
|
|
130
|
+
if selected_runs_1based:
|
|
131
|
+
pass_labels = selected_runs_1based
|
|
132
|
+
else:
|
|
133
|
+
# No passes specified: assume all passes present in files
|
|
134
|
+
R = mean_ux_all.shape[0]
|
|
135
|
+
pass_labels = list(range(1, R + 1))
|
|
136
|
+
|
|
137
|
+
# Plot mean scalar fields for each selected pass (ux and uy)
|
|
138
|
+
print("[instantaneous] Generating mean scalar plots for ux and uy")
|
|
139
|
+
for lbl in pass_labels:
|
|
140
|
+
idx = lbl - 1 # aligns with array indexing when all passes selected
|
|
141
|
+
# If a subset was selected, map label to local index
|
|
142
|
+
if selected_runs_1based:
|
|
143
|
+
local_idx = selected_runs_1based.index(lbl)
|
|
144
|
+
else:
|
|
145
|
+
local_idx = idx
|
|
146
|
+
# Build boolean mask
|
|
147
|
+
mask_bool = np.asarray(b_mask_all[local_idx]).astype(bool)
|
|
148
|
+
|
|
149
|
+
# Per-pass coordinates if available
|
|
150
|
+
cx = coords_x_list[local_idx] if local_idx < len(coords_x_list) else None
|
|
151
|
+
cy = coords_y_list[local_idx] if local_idx < len(coords_y_list) else None
|
|
152
|
+
|
|
153
|
+
# ux
|
|
154
|
+
save_base_ux = mean_stats_dir / f"ux_{lbl}"
|
|
155
|
+
settings_ux = make_scalar_settings(
|
|
156
|
+
config,
|
|
157
|
+
variable="ux",
|
|
158
|
+
run_label=lbl,
|
|
159
|
+
save_basepath=save_base_ux, # used only for naming below
|
|
160
|
+
variable_units="m/s",
|
|
161
|
+
coords_x=cx,
|
|
162
|
+
coords_y=cy,
|
|
163
|
+
)
|
|
164
|
+
fig_ux, _, _ = plot_scalar_field(
|
|
165
|
+
mean_ux_all[local_idx], mask_bool, settings_ux
|
|
166
|
+
)
|
|
167
|
+
fig_ux.savefig(
|
|
168
|
+
f"{save_base_ux}{config.plot_save_extension}",
|
|
169
|
+
dpi=1200,
|
|
170
|
+
bbox_inches="tight",
|
|
171
|
+
)
|
|
172
|
+
if config.plot_save_pickle:
|
|
173
|
+
import pickle
|
|
174
|
+
|
|
175
|
+
with open(f"{save_base_ux}.pkl", "wb") as f:
|
|
176
|
+
pickle.dump(mean_ux_all[local_idx], f)
|
|
177
|
+
plt.close(fig_ux)
|
|
178
|
+
|
|
179
|
+
# uy
|
|
180
|
+
save_base_uy = mean_stats_dir / f"uy_{lbl}"
|
|
181
|
+
settings_uy = make_scalar_settings(
|
|
182
|
+
config,
|
|
183
|
+
variable="uy",
|
|
184
|
+
run_label=lbl,
|
|
185
|
+
save_basepath=save_base_uy, # used only for naming below
|
|
186
|
+
variable_units="m/s",
|
|
187
|
+
coords_x=cx,
|
|
188
|
+
coords_y=cy,
|
|
189
|
+
)
|
|
190
|
+
fig_uy, _, _ = plot_scalar_field(
|
|
191
|
+
mean_uy_all[local_idx], mask_bool, settings_uy
|
|
192
|
+
)
|
|
193
|
+
fig_uy.savefig(
|
|
194
|
+
f"{save_base_uy}{config.plot_save_extension}",
|
|
195
|
+
dpi=1200,
|
|
196
|
+
bbox_inches="tight",
|
|
197
|
+
)
|
|
198
|
+
if config.plot_save_pickle:
|
|
199
|
+
import pickle
|
|
200
|
+
|
|
201
|
+
with open(f"{save_base_uy}.pkl", "wb") as f:
|
|
202
|
+
pickle.dump(mean_uy_all[local_idx], f)
|
|
203
|
+
plt.close(fig_uy)
|
|
204
|
+
|
|
205
|
+
# Build piv_result as n-pass-deep MATLAB struct array; populate only selected passes
|
|
206
|
+
n_passes_cfg = len(config.instantaneous_window_sizes) or mean_ux_all.shape[0]
|
|
207
|
+
print(f"[instantaneous] Building piv_result with n_passes={n_passes_cfg}")
|
|
208
|
+
# Create a structured array with object-typed fields so each element can hold arrays
|
|
209
|
+
dt_fields = [
|
|
210
|
+
("ux", object),
|
|
211
|
+
("uy", object),
|
|
212
|
+
("b_mask", object),
|
|
213
|
+
("uu", object),
|
|
214
|
+
("uv", object),
|
|
215
|
+
("vv", object),
|
|
216
|
+
]
|
|
217
|
+
if stereo:
|
|
218
|
+
dt_fields.extend(
|
|
219
|
+
[
|
|
220
|
+
("uz", object),
|
|
221
|
+
("uw", object),
|
|
222
|
+
("vw", object),
|
|
223
|
+
("ww", object),
|
|
224
|
+
]
|
|
225
|
+
)
|
|
226
|
+
dt = np.dtype(dt_fields)
|
|
227
|
+
piv_result = np.empty((n_passes_cfg,), dtype=dt)
|
|
228
|
+
|
|
229
|
+
# Initialize all passes with empty 0x0 arrays
|
|
230
|
+
empty = np.empty((0, 0), dtype=mean_ux_all.dtype)
|
|
231
|
+
for p in range(n_passes_cfg):
|
|
232
|
+
piv_result["ux"][p] = empty
|
|
233
|
+
piv_result["uy"][p] = empty
|
|
234
|
+
piv_result["b_mask"][p] = empty
|
|
235
|
+
piv_result["uu"][p] = empty
|
|
236
|
+
piv_result["uv"][p] = empty
|
|
237
|
+
piv_result["vv"][p] = empty
|
|
238
|
+
if stereo:
|
|
239
|
+
piv_result["uz"][p] = empty
|
|
240
|
+
piv_result["uw"][p] = empty
|
|
241
|
+
piv_result["vw"][p] = empty
|
|
242
|
+
piv_result["ww"][p] = empty
|
|
243
|
+
|
|
244
|
+
# Fill only the selected passes
|
|
245
|
+
label_to_idx = {
|
|
246
|
+
lbl: i for i, lbl in enumerate(pass_labels)
|
|
247
|
+
} # 1-based label -> local index (selected order)
|
|
248
|
+
for lbl in pass_labels:
|
|
249
|
+
local_idx = label_to_idx[lbl]
|
|
250
|
+
pass_zero_based = lbl - 1
|
|
251
|
+
if 0 <= pass_zero_based < n_passes_cfg:
|
|
252
|
+
piv_result["ux"][pass_zero_based] = mean_ux_all[local_idx]
|
|
253
|
+
piv_result["uy"][pass_zero_based] = mean_uy_all[local_idx]
|
|
254
|
+
piv_result["b_mask"][pass_zero_based] = b_mask_all[local_idx]
|
|
255
|
+
piv_result["uu"][pass_zero_based] = uu_all[local_idx]
|
|
256
|
+
piv_result["uv"][pass_zero_based] = uv_all[local_idx]
|
|
257
|
+
piv_result["vv"][pass_zero_based] = vv_all[local_idx]
|
|
258
|
+
if stereo:
|
|
259
|
+
piv_result["uz"][pass_zero_based] = mean_uz_all[local_idx]
|
|
260
|
+
piv_result["uw"][pass_zero_based] = uw_all[local_idx]
|
|
261
|
+
piv_result["vw"][pass_zero_based] = vw_all[local_idx]
|
|
262
|
+
piv_result["ww"][pass_zero_based] = ww_all[local_idx]
|
|
263
|
+
|
|
264
|
+
# Build coordinates struct array (fields: x, y), aligned to n_passes_cfg; fill only selected passes
|
|
265
|
+
dt_coords = np.dtype([("x", object), ("y", object)])
|
|
266
|
+
coordinates = np.empty((n_passes_cfg,), dtype=dt_coords)
|
|
267
|
+
# Initialize empties
|
|
268
|
+
empty_xy = np.empty((0, 0), dtype=empty.dtype)
|
|
269
|
+
for p in range(n_passes_cfg):
|
|
270
|
+
coordinates["x"][p] = empty_xy
|
|
271
|
+
coordinates["y"][p] = empty_xy
|
|
272
|
+
# Fill selected using the same label order
|
|
273
|
+
for lbl in pass_labels:
|
|
274
|
+
local_idx = label_to_idx[lbl]
|
|
275
|
+
pass_zero_based = lbl - 1
|
|
276
|
+
if 0 <= pass_zero_based < n_passes_cfg and local_idx < len(coords_x_list):
|
|
277
|
+
coordinates["x"][pass_zero_based] = coords_x_list[local_idx]
|
|
278
|
+
coordinates["y"][pass_zero_based] = coords_y_list[local_idx]
|
|
279
|
+
|
|
280
|
+
# Save a single file per camera/merged with piv_result, coordinates and meta
|
|
281
|
+
out_file = mean_stats_dir / (
|
|
282
|
+
f"{'merged' if use_merged else f'Cam{cam_num}'}_mean.mat"
|
|
283
|
+
)
|
|
284
|
+
print(
|
|
285
|
+
f"[instantaneous] Saving piv_result (means and Reynolds stresses) -> {out_file}"
|
|
286
|
+
)
|
|
287
|
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
# Save piv_result and meta to main file
|
|
289
|
+
meta_dict = {
|
|
290
|
+
"endpoint": endpoint,
|
|
291
|
+
"use_merged": use_merged,
|
|
292
|
+
"camera": cam_folder_eff,
|
|
293
|
+
"selected_passes": pass_labels,
|
|
294
|
+
"n_passes": int(n_passes_cfg),
|
|
295
|
+
"stereo": stereo,
|
|
296
|
+
"definitions": "ux=<u>, uy=<v>, uu=<u'^2>, uv=<u'v'>, vv=<v'^2>"
|
|
297
|
+
+ (", uz=<w>, uw=<u'w'>, vw=<v'w'>, ww=<w'^2>" if stereo else ""),
|
|
298
|
+
}
|
|
299
|
+
savemat(
|
|
300
|
+
out_file,
|
|
301
|
+
{
|
|
302
|
+
"piv_result": piv_result,
|
|
303
|
+
"meta": meta_dict,
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
# Save coordinates as a separate file into mean_stats folder
|
|
307
|
+
coords_file = mean_stats_dir / (
|
|
308
|
+
f"{'merged' if use_merged else f'Cam{cam_num}'}_coordinates.mat"
|
|
309
|
+
)
|
|
310
|
+
savemat(coords_file, {"coordinates": coordinates})
|
|
311
|
+
print(f"[instantaneous] Saved -> {out_file}")
|
|
File without changes
|
|
File without changes
|