Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
healthonrails authored Apr 17, 2024
2 parents ce0c844 + e634721 commit 33156eb
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 12 deletions.
31 changes: 28 additions & 3 deletions annolid/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import qimage2ndarray
from annolid.gui.widgets.video_slider import VideoSlider, VideoSliderMark
from annolid.gui.widgets.step_size_widget import StepSizeWidget
from annolid.gui.widgets.downsample_videos_dialog import VideoRescaleWidget
from annolid.postprocessing.quality_control import pred_dict_to_labelme
from annolid.annotation.timestamps import convert_frame_number_to_time
from annolid.segmentation.SAM.edge_sam_bg import VideoProcessor
Expand Down Expand Up @@ -348,6 +349,14 @@ def __init__(self,
self.tr("Open Audio")
)

downsample_video = action(
self.tr("&Downsample Videos"),
self.downsample_videos,
None,
"Downsample Videos",
self.tr("Downsample Videos")
)

step_size = QtWidgets.QWidgetAction(self)
step_size.setIcon(QtGui.QIcon(
str(
Expand Down Expand Up @@ -500,6 +509,7 @@ def __init__(self,
utils.addActions(self.menus.file, (tracks,))
utils.addActions(self.menus.file, (quality_control,))
utils.addActions(self.menus.file, (segment_cells,))
utils.addActions(self.menus.file, (downsample_video,))
utils.addActions(self.menus.file, (advance_params,))

utils.addActions(self.menus.view, (glitter2,))
Expand Down Expand Up @@ -543,7 +553,8 @@ def __init__(self,
self._selectAiModelComboBox.setCurrentIndex(model_index)
self._selectAiModelComboBox.currentIndexChanged.connect(
lambda: self.canvas.initializeAiModel(
name=self._selectAiModelComboBox.currentText()
name=self._selectAiModelComboBox.currentText(),
_custom_ai_models=self.custom_ai_model_names,
)
if self.canvas.createMode in ["ai_polygon", "ai_mask"]
else None
Expand All @@ -553,8 +564,17 @@ def __init__(self,

def _grounding_sam(self):
self.toggleDrawMode(False, createMode="grounding_sam")
self.canvas.predictAiRectangle(
self.aiRectangle._aiRectanglePrompt.text())
prompt_text = self.aiRectangle._aiRectanglePrompt.text().lower()
if len(prompt_text) < 1:
logger.info(f"Invalid text prompt {prompt_text}")
return
if prompt_text.startswith('flags:') and ',' in prompt_text:
flags = {k: False for k in prompt_text.replace(
'flags:', '').split(',') or []}
self.loadFlags(flags)
else:
self.canvas.predictAiRectangle(
self.aiRectangle._aiRectanglePrompt.text())

def update_step_size(self, value):
self.step_size = value
Expand All @@ -563,6 +583,11 @@ def update_step_size(self, value):
def flag_item_clicked(self, item):
item_text = item.text()
self.event_type = item_text
logger.info(f"Selected event {self.event_type}.")

def downsample_videos(self):
video_downsample_widget = VideoRescaleWidget()
video_downsample_widget.exec_()

def openAudio(self):
if self.video_file:
Expand Down
5 changes: 3 additions & 2 deletions annolid/gui/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ def createMode(self, value):
self.sam_mask = MaskShape()
self.current = None

def initializeAiModel(self, name):
if name not in [model.name for model in labelme.ai.MODELS]:
def initializeAiModel(self, name, _custom_ai_models=None):
if (name not in [model.name for model in labelme.ai.MODELS]
and name not in _custom_ai_models):
logger.warning("Unsupported ai model: %s" % name)
model = labelme.ai.MODELS[3]
else:
Expand Down
154 changes: 154 additions & 0 deletions annolid/gui/widgets/downsample_videos_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from annolid.utils.videos import (collect_video_metadata,
compress_and_rescale_video,
save_metadata_to_csv)
from qtpy.QtWidgets import (QApplication, QDialog,
QLabel, QPushButton,
QVBoxLayout, QFileDialog,
QCheckBox, QSlider,
QLineEdit, QMessageBox
)
from qtpy.QtCore import Qt


class VideoRescaleWidget(QDialog):
"""
Widget for rescaling video files.
Allows users to select input and output folders, specify a scale factor,
choose whether to rescale or downsample the video, and collect metadata.
"""

def __init__(self):
super().__init__()
self.setWindowTitle('Video Rescaling')

self.init_ui()

def init_ui(self):
"""
Initialize the user interface.
"""
self.input_folder_label = QLabel('Input Folder:')
self.input_folder_button = QPushButton('Select Folder')
self.input_folder_button.clicked.connect(self.select_input_folder)

self.output_folder_label = QLabel('Output Folder:')
self.output_folder_button = QPushButton('Select Folder')
self.output_folder_button.clicked.connect(self.select_output_folder)

self.scale_factor_label = QLabel('Scale Factor:')
self.scale_factor_slider = QSlider(Qt.Horizontal)
self.scale_factor_slider.setMinimum(0)
self.scale_factor_slider.setMaximum(100)
self.scale_factor_slider.setValue(25)
self.scale_factor_slider.setTickInterval(25)
self.scale_factor_slider.setTickPosition(QSlider.TicksBelow)
self.scale_factor_slider.valueChanged.connect(
self.update_scale_factor_from_slider)

self.scale_factor_text = QLineEdit()
self.scale_factor_text.setText("0.25") # Default scale factor
self.scale_factor_text.editingFinished.connect(
self.update_scale_factor_from_text)

self.rescale_checkbox = QCheckBox('Rescale Video')

self.collect_only_checkbox = QCheckBox('Collect Metadata Only')

self.run_button = QPushButton('Run Rescaling')
self.run_button.clicked.connect(self.run_rescaling)

layout = QVBoxLayout()
layout.addWidget(self.input_folder_label)
layout.addWidget(self.input_folder_button)
layout.addWidget(self.output_folder_label)
layout.addWidget(self.output_folder_button)
layout.addWidget(self.scale_factor_label)
layout.addWidget(self.scale_factor_slider)
layout.addWidget(self.scale_factor_text)
layout.addWidget(self.rescale_checkbox)
layout.addWidget(self.collect_only_checkbox)
layout.addWidget(self.run_button)

self.setLayout(layout)

def select_input_folder(self):
"""
Open a file dialog to select the input folder.
"""
folder = QFileDialog.getExistingDirectory(self, 'Select Input Folder')
if folder:
self.input_folder_label.setText(f'Input Folder: {folder}')

def select_output_folder(self):
"""
Open a file dialog to select the output folder.
"""
folder = QFileDialog.getExistingDirectory(self, 'Select Output Folder')
if folder:
self.output_folder_label.setText(f'Output Folder: {folder}')

def update_scale_factor_from_slider(self):
"""
Update the scale factor when the slider is dragged.
"""
scale_factor = self.scale_factor_slider.value() / 100
self.scale_factor_text.setText(str(scale_factor))

def update_scale_factor_from_text(self):
"""
Update the slider when the text field is edited.
"""
scale_factor_text = self.scale_factor_text.text()
try:
scale_factor = float(scale_factor_text)
if 0.0 <= scale_factor <= 1.0:
self.scale_factor_slider.setValue(int(scale_factor * 100))
else:
self.scale_factor_text.setText("Invalid Value")
except ValueError:
self.scale_factor_text.setText("Invalid Value")

def run_rescaling(self):
"""
Run the rescaling process based on user inputs.
"""
# Disable the button during processing
self.run_button.setEnabled(False)
self.run_button.setText('Processing...')

input_folder = self.input_folder_label.text().split(': ')[-1]
output_folder = self.output_folder_label.text().split(': ')[-1]
scale_factor = float(self.scale_factor_text.text())
rescale = self.rescale_checkbox.isChecked()
collect_only = self.collect_only_checkbox.isChecked()

if collect_only:
metadata = collect_video_metadata(input_folder)
output_csv, _ = QFileDialog.getSaveFileName(
self, 'Select Output CSV')
if output_csv:
save_metadata_to_csv(metadata, output_csv)
QMessageBox.information(
self, 'Done', 'Metadata collection is done.')
elif rescale:
compress_and_rescale_video(
input_folder, output_folder, scale_factor)
if output_folder:
metadata = collect_video_metadata(output_folder)
output_csv, _ = QFileDialog.getSaveFileName(
self, 'Select Output CSV')
if output_csv:
save_metadata_to_csv(metadata, output_csv)
QMessageBox.information(self, 'Done', 'Rescaling is done.')

# Enable the button and change its text back to original
self.run_button.setEnabled(True)
self.run_button.setText('Run Rescaling')


if __name__ == '__main__':
app = QApplication([])
widget = VideoRescaleWidget()
widget.exec_()
app.exec_()
29 changes: 29 additions & 0 deletions annolid/gui/widgets/video_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,31 @@ def __init__(
self.headerSeries = dict()
self._draw_header()

# Adding QLineEdit for input value
self.input_value = QtWidgets.QLineEdit(str(self.value()), self)
self.input_value.setFixedWidth(60)
self.input_value.setAlignment(QtCore.Qt.AlignCenter)
self.input_value.editingFinished.connect(self.updateValueFromInput)
self.input_value.move(2, 2)

# Methods to match API for QSlider

def updateValueFromInput(self):
# Get the input text
input_text = self.input_value.text()
try:
# Try converting the input to float
input_val = float(input_text)
# Check if the input value is within the range
if self._val_min <= input_val <= self._val_max:
self.setValue(input_val)
else:
# Reset the input text if it's out of range
self.input_value.setText(str(self._val_main))
except ValueError:
# Reset the input text if it's not a valid float
self.input_value.setText(str(self._val_main))

def value(self) -> float:
"""Returns value of slider."""
return self._val_main
Expand All @@ -250,6 +273,9 @@ def setValue(self, val: float) -> float:
x = self._toPos(val)
self.handle.setPos(x, 0)
self.ensureVisible(x, 0, self._handle_width, 0, 3, 0)
if hasattr(self, 'input_value'):
# Update input text value with slider's current value
self.input_value.setText(str(self._val_main))

def setMinimum(self, min: float) -> float:
"""Sets minimum value for slider."""
Expand Down Expand Up @@ -1064,6 +1090,9 @@ def done(x, y):
self.mouseMoved.emit(scenePos.x(), scenePos.y())
self.mousePressed.emit(scenePos.x(), scenePos.y())

# Update input text value with slider's current value
self.input_value.setText(str(self._val_main))

def mouseMoveEvent(self, event):
"""Override method to emit mouseMoved signal on drag."""
scenePos = self.mapToScene(event.pos())
Expand Down
8 changes: 4 additions & 4 deletions annolid/postprocessing/glitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def animal_in_zone(animal_mask,
):
if animal_mask is not None and zone_mask is not None:
overlap = mask_util.iou([animal_mask], [zone_mask], [
False, False]).flatten()[0]
0]).flatten()[0]
return overlap > threshold
else:
return False
Expand Down Expand Up @@ -148,7 +148,7 @@ def keypoint_in_body_mask(

if keypoint_seg and body_seg:
overlap = mask_util.iou([body_seg], [keypoint_seg], [
False, False]).flatten()[0]
0]).flatten()[0]
return overlap > 0
else:
return False
Expand All @@ -172,15 +172,15 @@ def left_right_interact(fn,
left_instance]['segmentation'].values[0]
left_instance_seg = ast.literal_eval(left_instance_seg)
left_interact = mask_util.iou([left_instance_seg], [subject_instance_seg], [
False, False]).flatten()[0]
0]).flatten()[0]
except IndexError:
left_interact = 0.0
try:
right_instance_seg = _df_top[_df_top.instance_name ==
right_instance]['segmentation'].values[0]
right_instance_seg = ast.literal_eval(right_instance_seg)
right_interact = mask_util.iou([right_instance_seg], [subject_instance_seg], [
False, False]).flatten()[0]
0]).flatten()[0]
except IndexError:
right_interact = 0.0

Expand Down
Loading

0 comments on commit 33156eb

Please sign in to comment.