Skip to content

Commit

Permalink
Merge pull request #21 from occ-ai/roy.crop_function
Browse files Browse the repository at this point in the history
Crop function
  • Loading branch information
royshil authored Jul 20, 2024
2 parents 134b7f2 + e1ab8c1 commit c22794d
Show file tree
Hide file tree
Showing 8 changed files with 1,131 additions and 412 deletions.
58 changes: 55 additions & 3 deletions camera_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from camera_info import CameraInfo
from ndi import NDICapture
from screen_capture_source import ScreenCapture
from storage import TextDetectionTargetMemoryStorage
from storage import TextDetectionTargetMemoryStorage, subscribe_to_data, fetch_data
from tesseract import TextDetector
from text_detection_target import TextDetectionTargetWithResult
from sc_logging import logger
Expand Down Expand Up @@ -81,6 +81,35 @@ def set_camera_highest_resolution(cap):
set_resolution(cap, *highest_res)


class FrameCrop:
def __init__(self):
self.isCropSet = fetch_data("scoresight.json", "crop_mode", False)
self.cropTop = fetch_data("scoresight.json", "top_crop", 0)
self.cropBottom = fetch_data("scoresight.json", "bottom_crop", 0)
self.cropLeft = fetch_data("scoresight.json", "left_crop", 0)
self.cropRight = fetch_data("scoresight.json", "right_crop", 0)
subscribe_to_data("scoresight.json", "crop_mode", self.setCropMode)
subscribe_to_data("scoresight.json", "top_crop", self.setCropTop)
subscribe_to_data("scoresight.json", "bottom_crop", self.setCropBottom)
subscribe_to_data("scoresight.json", "left_crop", self.setCropLeft)
subscribe_to_data("scoresight.json", "right_crop", self.setCropRight)

def setCropMode(self, crop_mode):
self.isCropSet = crop_mode

def setCropTop(self, crop_top):
self.cropTop = crop_top

def setCropBottom(self, crop_bottom):
self.cropBottom = crop_bottom

def setCropLeft(self, crop_left):
self.cropLeft = crop_left

def setCropRight(self, crop_right):
self.cropRight = crop_right


class TimerThread(QThread):
update_signal = Signal(object)
update_error = Signal(object)
Expand Down Expand Up @@ -112,6 +141,7 @@ def __init__(
self.ups = 1000 / self.update_frame_interval # updates per second
self.fps_alpha = 0.1 # Smoothing factor
self.updateOnChange = True
self.crop = FrameCrop()

def connect_video_capture(self) -> bool:
if self.camera_info.type == CameraInfo.CameraType.NDI:
Expand Down Expand Up @@ -248,6 +278,13 @@ def run(self):
+ (1.0 - self.fps_alpha) * self.ups
)

# apply top-level crop if set
if self.crop.isCropSet:
frame_rgb = frame_rgb[
self.crop.cropTop : frame_rgb.shape[0] - self.crop.cropBottom,
self.crop.cropLeft : frame_rgb.shape[1] - self.crop.cropRight,
]

# Stabilize the frame
if self.stabilizationEnabled:
frame_rgb = self.framestabilizer.stabilize_frame(frame_rgb)
Expand Down Expand Up @@ -363,6 +400,10 @@ def update_pixmap(self, frame):
if self.timerThread is None:
return

# check if frame is not contiguous
if not frame.flags["C_CONTIGUOUS"]:
frame = np.ascontiguousarray(frame)

# Create a QImage from the frame data
image = QImage(
frame.data,
Expand All @@ -387,9 +428,20 @@ def update_pixmap(self, frame):
self.scenePixmapItem = QGraphicsPixmapItem(pixmap)
self.scene.addItem(self.scenePixmapItem)
self.scenePixmapItem.setZValue(0)
self.fitInView(self.scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
self.fitInView(self.scenePixmapItem, Qt.AspectRatioMode.KeepAspectRatio)
else:
refit = False
# check if the pixmap is the same size as the current one
if self.scenePixmapItem.pixmap().size() != pixmap.size():
logger.info(f"scene size: {self.scene.sceneRect()}")
refit = True
self.scenePixmapItem.setPixmap(pixmap)
if refit:
self.scene.setSceneRect(0, 0, pixmap.width(), pixmap.height())
logger.info(f"scene size: {self.scene.sceneRect()}")
logger.info(f"Refitting view to new pixmap size: {pixmap.size()}")
self.fitInView(self.scenePixmapItem, Qt.AspectRatioMode.KeepAspectRatio)
self.centerOn(self.scenePixmapItem)

if not self.firstFrameReceived:
self.firstFrameReceived = True
Expand All @@ -403,9 +455,9 @@ def update_pixmap(self, frame):
self.fps_text.setZValue(2)
self.fps_text.setDefaultTextColor(Qt.GlobalColor.white)
# scale the text according to the view size so its always the same size
self.fps_text.setScale(0.004 * self.width())
else:
self.fps_text.setPlainText(fps_text)
self.fps_text.setScale(0.002 * self.scenePixmapItem.boundingRect().width())

def error_event(self, error):
if self.error_text is not None:
Expand Down
12 changes: 12 additions & 0 deletions log_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from PySide6.QtUiTools import QUiLoader
from sc_logging import log_file_path
from ui_log_view import Ui_Dialog as Ui_LogViewerDialog
from storage import store_data, fetch_data


class LogViewerDialog(QDialog):
Expand All @@ -17,6 +18,17 @@ def __init__(self):
self.timer.start(1000) # Update UI every 1 second
self.current_log_data = ""
self.ui.pushButton_openlogfolder.clicked.connect(self.open_log_folder)
self.ui.checkBox_openOnStartup.clicked.connect(self.open_on_startup)
self.ui.checkBox_openOnStartup.setChecked(
fetch_data("scoresight.json", "open_on_startup", False)
)

def open_on_startup(self):
store_data(
"scoresight.json",
"open_on_startup",
self.ui.checkBox_openOnStartup.isChecked(),
)

def open_log_folder(self):
# Open the folder containing the log file
Expand Down
11 changes: 9 additions & 2 deletions log_view.ui
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_openOnStartup">
<property name="text">
<string>Open on startup</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
Expand All @@ -61,8 +68,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>533</width>
<height>250</height>
<width>537</width>
<height>255</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
Expand Down
87 changes: 62 additions & 25 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,34 @@ def __init__(self, translator: QTranslator, parent: QObject):
self.ui.pushButton_stabilize.setEnabled(True)
self.ui.pushButton_stabilize.clicked.connect(self.toggleStabilize)

self.ui.toolButton_topCrop.clicked.connect(self.cropMode)
# check configuation if crop is enabled
self.ui.toolButton_topCrop.setChecked(
fetch_data("scoresight.json", "crop_mode", False)
)
self.ui.widget_cropPanel.setVisible(self.ui.toolButton_topCrop.isChecked())
self.ui.widget_cropPanel.setEnabled(self.ui.toolButton_topCrop.isChecked())
self.ui.spinBox_leftCrop.valueChanged.connect(
partial(self.globalSettingsChanged, "left_crop")
)
self.ui.spinBox_leftCrop.setValue(fetch_data("scoresight.json", "left_crop", 0))
self.ui.spinBox_rightCrop.valueChanged.connect(
partial(self.globalSettingsChanged, "right_crop")
)
self.ui.spinBox_rightCrop.setValue(
fetch_data("scoresight.json", "right_crop", 0)
)
self.ui.spinBox_topCrop.valueChanged.connect(
partial(self.globalSettingsChanged, "top_crop")
)
self.ui.spinBox_topCrop.setValue(fetch_data("scoresight.json", "top_crop", 0))
self.ui.spinBox_bottomCrop.valueChanged.connect(
partial(self.globalSettingsChanged, "bottom_crop")
)
self.ui.spinBox_bottomCrop.setValue(
fetch_data("scoresight.json", "bottom_crop", 0)
)

self.ui.widget_detectionCadence.setVisible(True)
self.ui.horizontalSlider_detectionCadence.setValue(
fetch_data("scoresight.json", "detection_cadence", 5)
Expand Down Expand Up @@ -279,6 +307,9 @@ def __init__(self, translator: QTranslator, parent: QObject):
self.source_name = None
self.updateOCRResults = True
self.log_dialog = None
if fetch_data("scoresight.json", "open_on_startup", False):
logger.info("Opening log dialog on startup")
self.openLogsDialog()

if fetch_data("scoresight.json", "obs"):
self.connectObs()
Expand All @@ -293,19 +324,23 @@ def __init__(self, translator: QTranslator, parent: QObject):

self.first_csv_append = True
self.last_aggregate_save = datetime.datetime.now()
self.ui.checkBox_saveCsv.toggled.connect(self.save_csv_toggled)
self.ui.checkBox_saveCsv.toggled.connect(
partial(self.globalSettingsChanged, "save_csv")
)
self.ui.checkBox_saveCsv.setChecked(
fetch_data("scoresight.json", "save_csv", False)
)
self.ui.checkBox_saveXML.toggled.connect(self.save_xml_toggled)
self.ui.checkBox_saveXML.toggled.connect(
partial(self.globalSettingsChanged, "save_xml")
)
self.ui.checkBox_saveXML.setChecked(
fetch_data("scoresight.json", "save_xml", False)
)
self.ui.comboBox_appendMethod.currentIndexChanged.connect(
self.appendMethodChanged
partial(self.globalSettingsChanged, "append_method")
)
self.ui.horizontalSlider_aggsPerSecond.valueChanged.connect(
self.aggsPerSecondChanged
partial(self.globalSettingsChanged, "aggs_per_second")
)
self.ui.comboBox_appendMethod.setCurrentIndex(
fetch_data("scoresight.json", "append_method", 3)
Expand All @@ -321,6 +356,20 @@ def __init__(self, translator: QTranslator, parent: QObject):
self.get_sources.connect(self.getSources)
self.get_sources.emit()

def cropMode(self):
# if the toolButton_topCrop is unchecked, go to crop mode
if self.ui.toolButton_topCrop.isChecked():
self.ui.widget_cropPanel.setVisible(True)
self.ui.widget_cropPanel.setEnabled(True)
self.globalSettingsChanged("crop_mode", True)
else:
self.ui.widget_cropPanel.setVisible(False)
self.ui.widget_cropPanel.setEnabled(False)
self.globalSettingsChanged("crop_mode", False)

def globalSettingsChanged(self, settingName, value):
store_data("scoresight.json", settingName, value)

def eventFilter(self, obj, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Alt:
self.menubar.setVisible(True)
Expand Down Expand Up @@ -358,7 +407,7 @@ def addLanguageOption(self, menu: QMenu, language_name: str, locale: str):
menu.addAction(language_name, lambda: self.changeLanguage(locale))

def toggleUpdateOnChange(self, value):
store_data("scoresight.json", "update_on_change", value)
self.globalSettingsChanged("update_on_change", value)
if self.image_viewer:
self.image_viewer.setUpdateOnChange(value)

Expand Down Expand Up @@ -399,32 +448,20 @@ def toggleOCRRects(self, value):
if self.image_viewer:
self.image_viewer.toggleOCRRects(value)

def save_csv_toggled(self, value):
store_data("scoresight.json", "save_csv", value)

def save_xml_toggled(self, value):
store_data("scoresight.json", "save_xml", value)

def appendMethodChanged(self, index):
store_data("scoresight.json", "append_method", index)

def aggsPerSecondChanged(self, value):
store_data("scoresight.json", "aggs_per_second", value)

def resetZoom(self):
if self.image_viewer:
self.image_viewer.resetZoom()

def detectionCadenceChanged(self, detections_per_second):
store_data("scoresight.json", "detection_cadence", detections_per_second)
self.globalSettingsChanged("detection_cadence", detections_per_second)
if self.image_viewer and self.image_viewer.timerThread:
# convert the detections_per_second to milliseconds
self.image_viewer.timerThread.update_frame_interval = (
1000 / detections_per_second
)

def ocrModelChanged(self, index):
store_data("scoresight.json", "ocr_model", index)
self.globalSettingsChanged("ocr_model", index)
# update the ocr model in the text detector
if (
self.image_viewer
Expand Down Expand Up @@ -481,7 +518,7 @@ def selectOutputFolder(self):
if folder and len(folder) > 0:
self.ui.lineEdit_folder.setText(folder)
self.out_folder = folder
store_data("scoresight.json", "output_folder", folder)
self.globalSettingsChanged("output_folder", folder)

def clearOutputFolder(self):
# clear the output folder
Expand Down Expand Up @@ -545,9 +582,9 @@ def vmixConnectionChanged(self):
self.ui.inputLineEdit_vmix.text(),
{},
)
store_data("scoresight.json", "vmix_host", self.ui.lineEdit_vmixHost.text())
store_data("scoresight.json", "vmix_port", self.ui.lineEdit_vmixPort.text())
store_data("scoresight.json", "vmix_input", self.ui.inputLineEdit_vmix.text())
self.globalSettingsChanged("vmix_host", self.ui.lineEdit_vmixHost.text())
self.globalSettingsChanged("vmix_port", self.ui.lineEdit_vmixPort.text())
self.globalSettingsChanged("vmix_input", self.ui.inputLineEdit_vmix.text())

def vmixMappingChanged(self, _):
# store entire mapping data in scoresight.json
Expand All @@ -559,7 +596,7 @@ def vmixMappingChanged(self, _):
value = model.item(i, 1)
if item and value:
mapping[item.text()] = value.text()
store_data("scoresight.json", "vmix_mapping", mapping)
self.globalSettingsChanged("vmix_mapping", mapping)
self.vmixUpdater.set_field_mapping(mapping)

def vmixUiSetup(self):
Expand Down Expand Up @@ -993,7 +1030,7 @@ def sourceChanged(self, index):
self.source_name = window_id

# store the source selection in scoresight.json
store_data("scoresight.json", "source_selected", self.source_name)
self.globalSettingsChanged("source_selected", self.source_name)
self.sourceSelectionSucessful()

def itemSelected(self, item_name):
Expand Down
Loading

0 comments on commit c22794d

Please sign in to comment.