fix: made sure the highest value are shown in cross correlation
This commit is contained in:
1 parent
b76b7394cf
commit
a01cf79447
1 file changed
+77
-5
+77
-5
@@ -59,6 +59,25 @@ def decimate_for_plot(arr: np.ndarray, max_pts: int = 120_000) -> tuple[np.ndarr
|
||||
stride = int(math.ceil(len(arr) / max_pts))
|
||||
return arr[::stride], stride
|
||||
|
||||
def decimate_with_peak(arr: np.ndarray, peak_idx: int, max_pts: int = 200_000) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Decimate array for plotting using max-pooling to preserve all peaks."""
|
||||
n = len(arr)
|
||||
if n <= max_pts:
|
||||
return arr, np.arange(n, dtype=np.float64)
|
||||
stride = int(math.ceil(n / max_pts))
|
||||
abs_arr = np.abs(arr)
|
||||
n_blocks = (n + stride - 1) // stride
|
||||
out = np.empty(n_blocks, dtype=arr.dtype)
|
||||
idx_out = np.empty(n_blocks, dtype=np.float64)
|
||||
for i in range(n_blocks):
|
||||
start = i * stride
|
||||
end = min(start + stride, n)
|
||||
block_abs = abs_arr[start:end]
|
||||
local_peak = int(np.argmax(block_abs))
|
||||
out[i] = arr[start + local_peak]
|
||||
idx_out[i] = float(start + local_peak)
|
||||
return out, idx_out
|
||||
|
||||
|
||||
def find_wavs_from_config(cfg: dict) -> tuple[str, str, int, float, float]:
|
||||
audio_cfg = cfg.get("audio", {})
|
||||
@@ -128,13 +147,14 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
# xcorr display — corr is already fftshifted
|
||||
N = len(self.corr)
|
||||
# lags in seconds for display
|
||||
# raw corr was shifted: center at N//2, range [- (len(b)-1) .. len(a)-1]
|
||||
# for display we map indices to lag
|
||||
total = len(self.a) + len(self.b) - 1
|
||||
n_pow2 = N
|
||||
# shifted lags: -N//2 .. N//2-1
|
||||
self.corr_lags = np.arange(N) - N//2
|
||||
self.corr_lags_sec = self.corr_lags / sr
|
||||
# find peak index in raw (unshifted) for decimation
|
||||
peak_raw_idx = int(np.argmax(np.abs(self.corr))) # this is fftshifted, peak near center
|
||||
# for decimation we need the index in the shifted array
|
||||
|
||||
central = QtWidgets.QWidget()
|
||||
self.setCentralWidget(central)
|
||||
@@ -169,9 +189,10 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.p3 = self.gw.addPlot(row=2, col=0, title="Cross-correlation (GCC-PHAT)" if method=="phat" else "Cross-correlation")
|
||||
self.p3.showGrid(x=True, y=True)
|
||||
self.p3.setLabel("bottom", "Lag", "s")
|
||||
# decimate corr for perf if huge
|
||||
corr_dec, corr_stride = decimate_for_plot(self.corr, max_pts=200_000)
|
||||
corr_lags_dec = self.corr_lags_sec[::corr_stride] if corr_stride>1 else self.corr_lags_sec
|
||||
# decimate corr with peak awareness
|
||||
peak_in_shifted = int(np.argmax(np.abs(self.corr)))
|
||||
corr_dec, corr_idx = decimate_with_peak(self.corr, peak_in_shifted, max_pts=200_000)
|
||||
corr_lags_dec = self.corr_lags_sec[corr_idx.astype(int)]
|
||||
self.curve_corr = self.p3.plot(corr_lags_dec, np.abs(corr_dec), pen=pg.mkPen("#ffd166", width=1))
|
||||
self.peak_line = pg.InfiniteLine(pos=self.lag_sec, angle=90, pen=pg.mkPen("#06d6a0", width=2, style=QtCore.Qt.PenStyle.DashLine))
|
||||
self.p3.addItem(self.peak_line)
|
||||
@@ -213,6 +234,9 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
btn_swap = QtWidgets.QPushButton("Swap A/B")
|
||||
ctr.addWidget(btn_swap)
|
||||
|
||||
btn_export = QtWidgets.QPushButton("Export data")
|
||||
ctr.addWidget(btn_export)
|
||||
|
||||
# volume + playback
|
||||
ctr2 = QtWidgets.QHBoxLayout()
|
||||
lay.addLayout(ctr2)
|
||||
@@ -249,6 +273,7 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.vol.valueChanged.connect(lambda v: self.vol_label.setText(f"{v}%"))
|
||||
btn_auto.clicked.connect(self.on_auto)
|
||||
btn_swap.clicked.connect(self.on_swap)
|
||||
btn_export.clicked.connect(self.on_export)
|
||||
self.btn_play_a.clicked.connect(lambda: self.play("a"))
|
||||
self.btn_play_b.clicked.connect(lambda: self.play("b"))
|
||||
self.btn_play_mix.clicked.connect(lambda: self.play("mix"))
|
||||
@@ -321,6 +346,53 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.corr = corr
|
||||
self._set_lag(lag/self.sr, "swap")
|
||||
|
||||
def on_export(self):
|
||||
import json
|
||||
path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self, "Export sync data", "sync_data.json", "JSON (*.json)")
|
||||
if not path:
|
||||
return
|
||||
# export decimated correlation (full res is too large)
|
||||
peak_in_shifted = int(np.argmax(np.abs(self.corr)))
|
||||
corr_dec, corr_idx = decimate_with_peak(self.corr, peak_in_shifted, max_pts=200_000)
|
||||
corr_lags_sec = self.corr_lags_sec[corr_idx.astype(int)]
|
||||
export = {
|
||||
"file_a": self.a_path,
|
||||
"file_b": self.b_path,
|
||||
"sr": self.sr,
|
||||
"method": self.method,
|
||||
"device": self.device_pref,
|
||||
"lag_samples": self.lag_samples,
|
||||
"lag_sec": self.lag_sec,
|
||||
"peak_value": float(self.peak),
|
||||
"a_samples": len(self.a_orig),
|
||||
"a_duration_sec": len(self.a_orig) / self.sr,
|
||||
"b_samples": len(self.b_orig),
|
||||
"b_duration_sec": len(self.b_orig) / self.sr,
|
||||
"corr_peak_index": peak_in_shifted,
|
||||
"corr_length": len(self.corr),
|
||||
# decimated correlation for plotting (not full 4M points)
|
||||
"corr_lags_sec": corr_lags_sec.tolist(),
|
||||
"corr_magnitude": np.abs(corr_dec).tolist(),
|
||||
# waveform stats (full res waveforms too large for JSON)
|
||||
"a_stats": {
|
||||
"mean": float(self.a_orig.mean()),
|
||||
"std": float(self.a_orig.std()),
|
||||
"min": float(self.a_orig.min()),
|
||||
"max": float(self.a_orig.max()),
|
||||
},
|
||||
"b_stats": {
|
||||
"mean": float(self.b_orig.mean()),
|
||||
"std": float(self.b_orig.std()),
|
||||
"min": float(self.b_orig.min()),
|
||||
"max": float(self.b_orig.max()),
|
||||
},
|
||||
}
|
||||
with open(path, "w") as f:
|
||||
json.dump(export, f, indent=2)
|
||||
print(f"exported to {path}")
|
||||
self.setWindowTitle(f"movie_sync — exported to {os.path.basename(path)}")
|
||||
|
||||
def play(self, which: str):
|
||||
if not HAS_SD:
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user