Skip to content

Commit 25bc0df

Browse files
committed
Added frame range to export functionality
1 parent fcdc2ed commit 25bc0df

File tree

1 file changed

+114
-5
lines changed

1 file changed

+114
-5
lines changed

pyidi/GUIs/result_viewer.py

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,40 @@ def init_ui(self):
248248
self.export_resolution_combo.setCurrentText("4x pixel scale")
249249
export_layout.addWidget(self.export_resolution_combo)
250250

251+
# Frame range controls (only for non-mode shape videos)
252+
if not self.is_mode_shape:
253+
self.frame_range_label = QtWidgets.QLabel("Frame Range:")
254+
export_layout.addWidget(self.frame_range_label)
255+
256+
frame_range_layout = QtWidgets.QHBoxLayout()
257+
258+
# Start frame
259+
self.start_frame_spin = QtWidgets.QSpinBox()
260+
self.start_frame_spin.setRange(0, self.video.shape[0] - 1)
261+
self.start_frame_spin.setValue(0)
262+
self.start_frame_spin.setFixedWidth(80)
263+
self.start_frame_spin.valueChanged.connect(self.on_start_frame_changed)
264+
frame_range_layout.addWidget(self.start_frame_spin)
265+
266+
# Stop frame
267+
self.stop_frame_spin = QtWidgets.QSpinBox()
268+
self.stop_frame_spin.setRange(0, self.video.shape[0] - 1)
269+
self.stop_frame_spin.setValue(self.video.shape[0] - 1)
270+
self.stop_frame_spin.setFixedWidth(80)
271+
self.stop_frame_spin.valueChanged.connect(self.on_stop_frame_changed)
272+
frame_range_layout.addWidget(self.stop_frame_spin)
273+
274+
export_layout.addLayout(frame_range_layout)
275+
276+
# Update the label with initial frame count
277+
self.update_frame_range_label()
278+
279+
# Full range checkbox
280+
self.full_range_checkbox = QtWidgets.QCheckBox("Full Range")
281+
self.full_range_checkbox.setChecked(True) # Initially checked since defaults are full range
282+
self.full_range_checkbox.stateChanged.connect(self.on_full_range_checkbox_changed)
283+
export_layout.addWidget(self.full_range_checkbox)
284+
251285
# Duration for mode shapes
252286
if self.is_mode_shape:
253287
export_layout.addWidget(QtWidgets.QLabel("Duration (seconds):"))
@@ -409,6 +443,68 @@ def update_point_size(self):
409443
size = self.point_size_spin.value()
410444
self.scatter.setSize(size)
411445

446+
def on_start_frame_changed(self, value):
447+
# Ensure start frame is not greater than stop frame
448+
if hasattr(self, 'stop_frame_spin') and value > self.stop_frame_spin.value():
449+
self.stop_frame_spin.setValue(value)
450+
451+
# Update the frame range label
452+
self.update_frame_range_label()
453+
454+
# Update checkbox state based on whether we have full range
455+
self.update_full_range_checkbox_state()
456+
457+
def on_stop_frame_changed(self, value):
458+
# Ensure stop frame is not less than start frame
459+
if hasattr(self, 'start_frame_spin') and value < self.start_frame_spin.value():
460+
self.start_frame_spin.setValue(value)
461+
462+
# Update the frame range label
463+
self.update_frame_range_label()
464+
465+
# Update checkbox state based on whether we have full range
466+
self.update_full_range_checkbox_state()
467+
468+
def update_frame_range_label(self):
469+
"""Update the frame range label with current frame count."""
470+
if not self.is_mode_shape and hasattr(self, 'frame_range_label'):
471+
start_frame = self.start_frame_spin.value()
472+
stop_frame = self.stop_frame_spin.value()
473+
total_frames = stop_frame - start_frame + 1
474+
self.frame_range_label.setText(f"Frame Range: ({total_frames} frames)")
475+
476+
def on_full_range_checkbox_changed(self, state):
477+
"""Handle full range checkbox state changes."""
478+
if not self.is_mode_shape:
479+
if state == QtCore.Qt.CheckState.Checked.value:
480+
# Set to full range
481+
self.start_frame_spin.blockSignals(True)
482+
self.stop_frame_spin.blockSignals(True)
483+
self.start_frame_spin.setValue(0)
484+
self.stop_frame_spin.setValue(self.video.shape[0] - 1)
485+
self.start_frame_spin.blockSignals(False)
486+
self.stop_frame_spin.blockSignals(False)
487+
488+
# Update the frame range label
489+
self.update_frame_range_label()
490+
491+
def update_full_range_checkbox_state(self):
492+
"""Update the checkbox state based on current spinbox values."""
493+
if not self.is_mode_shape and hasattr(self, 'full_range_checkbox'):
494+
is_full_range = (self.start_frame_spin.value() == 0 and
495+
self.stop_frame_spin.value() == self.video.shape[0] - 1)
496+
497+
# Block signals to prevent recursive calls
498+
self.full_range_checkbox.blockSignals(True)
499+
self.full_range_checkbox.setChecked(is_full_range)
500+
self.full_range_checkbox.blockSignals(False)
501+
502+
def set_full_range(self):
503+
"""Set the frame range to cover the full video."""
504+
if not self.is_mode_shape:
505+
self.start_frame_spin.setValue(0)
506+
self.stop_frame_spin.setValue(self.video.shape[0] - 1)
507+
412508
def next_frame(self):
413509
if self.is_mode_shape:
414510
self.current_frame = (self.current_frame + 1) % int(self.fps * self.time_per_period)
@@ -537,8 +633,12 @@ def export_video(self):
537633
if self.is_mode_shape:
538634
duration = self.duration_spin.value()
539635
total_frames = int(export_fps * duration)
636+
start_frame = 0
637+
stop_frame = total_frames - 1
540638
else:
541-
total_frames = self.video.shape[0]
639+
start_frame = self.start_frame_spin.value()
640+
stop_frame = self.stop_frame_spin.value()
641+
total_frames = stop_frame - start_frame + 1
542642

543643
# Initialize video writer
544644
writer = cv2.VideoWriter(file_path, fourcc, export_fps, (export_width, export_height))
@@ -578,9 +678,11 @@ def export_video(self):
578678
phase = np.angle(displ_raw)
579679
displ = scale * amplitude * np.sin(2 * np.pi * t - phase)
580680
else:
581-
self.current_frame = frame_idx
582-
base_frame = self.video[self.current_frame]
583-
displ = self.displacements[:, self.current_frame, :] * scale
681+
# For regular videos, use the actual frame index within the specified range
682+
actual_frame_idx = start_frame + frame_idx
683+
self.current_frame = actual_frame_idx
684+
base_frame = self.video[actual_frame_idx]
685+
displ = self.displacements[:, actual_frame_idx, :] * scale
584686

585687
# Create the export frame by scaling the video frame pixel-perfectly
586688
# Convert to RGB for proper color handling
@@ -638,10 +740,17 @@ def export_video(self):
638740

639741
writer.release()
640742

743+
# Create success message with frame range info
744+
if self.is_mode_shape:
745+
frame_info = f"Duration: {self.duration_spin.value():.1f}s"
746+
else:
747+
frame_info = f"Frames: {start_frame} to {stop_frame} ({total_frames} total)"
748+
641749
QtWidgets.QMessageBox.information(self, "Export Complete",
642750
f"Video exported successfully to:\n{file_path}\n"
643751
f"Resolution: {export_width}x{export_height} "
644-
f"(pixel scale: {pixel_scale}x)")
752+
f"(pixel scale: {pixel_scale}x)\n"
753+
f"{frame_info}")
645754

646755
except Exception as e:
647756
import traceback

0 commit comments

Comments
 (0)