diff --git a/camera_view.py b/camera_view.py
index 92fea71..41b6887 100644
--- a/camera_view.py
+++ b/camera_view.py
@@ -14,6 +14,7 @@
from ndi import NDICapture
from screen_capture_source import ScreenCapture
+from storage import TextDetectionTargetMemoryStorage
from tesseract import TextDetector
import datetime
from datetime import datetime
@@ -146,7 +147,11 @@ class TimerThread(QThread):
update_error = Signal(object)
ocr_result_signal = Signal(list)
- def __init__(self, camera_info: CameraInfo, detectionTargetsStorage):
+ def __init__(
+ self,
+ camera_info: CameraInfo,
+ detectionTargetsStorage: TextDetectionTargetMemoryStorage,
+ ):
super().__init__()
self.camera_info = camera_info
self.homography = None
@@ -167,6 +172,7 @@ def __init__(self, camera_info: CameraInfo, detectionTargetsStorage):
self.pps = 1000 / self.preview_frame_interval # previews per second
self.ups = 1000 / self.update_frame_interval # updates per second
self.fps_alpha = 0.1 # Smoothing factor
+ self.updateOnChange = True
def connect_video_capture(self) -> bool:
if self.camera_info.type == CameraInfo.CameraType.NDI:
@@ -326,6 +332,15 @@ def run(self):
# augment the text detection targets with the results
results = []
for i, result in enumerate(texts):
+ if self.updateOnChange:
+ if (
+ detectionTargets[i].last_text is not None
+ and detectionTargets[i].last_text == result.text
+ ):
+ result.state = (
+ TextDetectionTargetWithResult.ResultState.SameNoChange
+ )
+
results.append(
TextDetectionTargetWithResult(
detectionTargets[i],
@@ -335,6 +350,7 @@ def run(self):
result.extra,
)
)
+ detectionTargets[i].last_text = result.text
# emit the results
self.ocr_result_signal.emit(results)
@@ -396,6 +412,9 @@ def __init__(self, camera_index, detectionTargetsStorage=None):
self.error_text = None
self.showOSD = True
+ def setUpdateOnChange(self, updateOnChange):
+ self.timerThread.updateOnChange = updateOnChange
+
def toggleOSD(self, state):
self.showOSD = state
if self.fps_text is not None:
diff --git a/main.py b/main.py
index 63c50ef..a92cf86 100644
--- a/main.py
+++ b/main.py
@@ -82,9 +82,7 @@ def __init__(self):
self.ui.setupUi(self)
# load env variables
load_dotenv()
- self.setWindowTitle(
- f"{fetch_data('scoresight.json', 'product_name')} - v{os.getenv('LOCAL_RELEASE_TAG')} - Registered to: {fetch_data('scoresight.json', 'customer_name')}"
- )
+ self.setWindowTitle(f"ScoreSight - v{os.getenv('LOCAL_RELEASE_TAG')}")
if platform.system() == "Windows":
# set the icon
self.setWindowIcon(
@@ -213,6 +211,7 @@ def __init__(self):
self.ui.comboBox_formatPrefix.currentIndexChanged.connect(
self.formatPrefixChanged
)
+ self.ui.checkBox_updateOnchange.toggled.connect(self.toggleUpdateOnChange)
# populate the tableWidget_boxes with the default and custom boxes
custom_boxes_names = fetch_custom_box_names()
@@ -276,11 +275,19 @@ def __init__(self):
self.ui.horizontalSlider_aggsPerSecond.setValue(
fetch_data("scoresight.json", "aggs_per_second", 5)
)
+ self.ui.checkBox_updateOnchange.setChecked(
+ fetch_data("scoresight.json", "update_on_change", True)
+ )
self.update_sources.connect(self.updateSources)
self.get_sources.connect(self.getSources)
self.get_sources.emit()
+ def toggleUpdateOnChange(self, value):
+ store_data("scoresight.json", "update_on_change", value)
+ if self.image_viewer:
+ self.image_viewer.setUpdateOnChange(value)
+
def formatPrefixChanged(self, index):
if index == 12:
return # do nothing if "Select Preset" is selected
diff --git a/mainwindow.ui b/mainwindow.ui
index ee63134..e95e0be 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -1370,11 +1370,27 @@
false
+
+ QSlider::TicksBelow
+
5
+ -
+
+
+ Only send an update if the field value has changed
+
+
+ Update on change
+
+
+ true
+
+
+
diff --git a/source_view.py b/source_view.py
index 5c637f1..42c512a 100644
--- a/source_view.py
+++ b/source_view.py
@@ -213,6 +213,11 @@ def updateResult(self, targetWithResult: TextDetectionTargetWithResult):
== TextDetectionTargetWithResult.ResultState.Success
):
self.resultItem.setBrush(QBrush(QColor("green")))
+ elif (
+ targetWithResult.result_state
+ == TextDetectionTargetWithResult.ResultState.SameNoChange
+ ):
+ self.resultItem.setBrush(QBrush(QColor("lightgreen")))
elif (
targetWithResult.result_state
== TextDetectionTargetWithResult.ResultState.FailedFilter
diff --git a/tesseract.py b/tesseract.py
index 8eafcb3..a737e29 100644
--- a/tesseract.py
+++ b/tesseract.py
@@ -180,7 +180,8 @@ def detect_multi_text(
scale_y = 1.0
if multi_crop:
if (
- rect.x() < 0
+ rect is None
+ or rect.x() < 0
or rect.y() < 0
or rect.width() < 1
or rect.height() < 1
@@ -192,6 +193,12 @@ def detect_multi_text(
)
continue
+ if rect.x() >= binary.shape[1]:
+ # move the rect inside the image
+ rect.setX(binary.shape[1] - rect.width())
+ if rect.y() >= binary.shape[0]:
+ # move the rect inside the image
+ rect.setY(binary.shape[0] - rect.height())
if rect.x() + rect.width() > binary.shape[1]:
rect.setWidth(binary.shape[1] - rect.x())
if rect.y() + rect.height() > binary.shape[0]:
@@ -499,7 +506,7 @@ def detect_multi_text(
box["y"] = int(box["y"] + effectiveRect.y())
extras["boxes"].append(box)
# if char is a "wide character" (like 0,2,3,4,5,6,7,8,9), add the width-to-height ratio
- if char in "023456789":
+ if char in "023456789" and box["h"] > 0:
wh_ratios.append(box["w"] / box["h"])
if (
"normalize_wh_ratio" in rect.settings
diff --git a/text_detection_target.py b/text_detection_target.py
index 5759606..da9a4d4 100644
--- a/text_detection_target.py
+++ b/text_detection_target.py
@@ -45,6 +45,7 @@ def __init__(self, x, y, width, height, name, settings: dict | None = None):
self.settings = settings
self.ocrResultPerCharacterSmoother = OCRResultPerCharacterSmoother()
self.last_image = None
+ self.last_text = None
class TextDetectionTargetWithResult(TextDetectionTarget):
@@ -52,6 +53,7 @@ class ResultState(enum.Enum):
Success = 0
FailedFilter = 1
Empty = 2
+ SameNoChange = 3
def __init__(
self,
diff --git a/ui_mainwindow.py b/ui_mainwindow.py
index d99b70d..b31cd33 100644
--- a/ui_mainwindow.py
+++ b/ui_mainwindow.py
@@ -726,10 +726,17 @@ def setupUi(self, MainWindow):
self.horizontalSlider_detectionCadence.setOrientation(Qt.Horizontal)
self.horizontalSlider_detectionCadence.setInvertedAppearance(False)
self.horizontalSlider_detectionCadence.setInvertedControls(False)
+ self.horizontalSlider_detectionCadence.setTickPosition(QSlider.TicksBelow)
self.horizontalSlider_detectionCadence.setTickInterval(5)
self.horizontalLayout_9.addWidget(self.horizontalSlider_detectionCadence)
+ self.checkBox_updateOnchange = QCheckBox(self.widget_detectionCadence)
+ self.checkBox_updateOnchange.setObjectName(u"checkBox_updateOnchange")
+ self.checkBox_updateOnchange.setChecked(True)
+
+ self.horizontalLayout_9.addWidget(self.checkBox_updateOnchange)
+
self.verticalLayout.addWidget(self.widget_detectionCadence)
@@ -998,6 +1005,10 @@ def retranslateUi(self, MainWindow):
self.tabWidget_outputs.setTabText(self.tabWidget_outputs.indexOf(self.tab_vmix), QCoreApplication.translate("MainWindow", u"VMix", None))
self.pushButton_stopUpdates.setText(QCoreApplication.translate("MainWindow", u"Stop Updates", None))
self.label_detectionCadence.setText(QCoreApplication.translate("MainWindow", u"Detections / s", None))
+#if QT_CONFIG(tooltip)
+ self.checkBox_updateOnchange.setToolTip(QCoreApplication.translate("MainWindow", u"Only send an update if the field value has changed", None))
+#endif // QT_CONFIG(tooltip)
+ self.checkBox_updateOnchange.setText(QCoreApplication.translate("MainWindow", u"Update on change", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"Source", None))
#if QT_CONFIG(tooltip)
self.pushButton_refresh_sources.setToolTip(QCoreApplication.translate("MainWindow", u"Refresh Sources", None))
diff --git a/vmix_output.py b/vmix_output.py
index fecb4d7..c6af5f4 100644
--- a/vmix_output.py
+++ b/vmix_output.py
@@ -26,8 +26,9 @@ def update_vmix(self, detection: list[TextDetectionTargetWithResult]):
# Prepare the data to send
data = {}
for target in detection:
- if target.name in self.field_mapping:
- data[self.field_mapping[target.name]] = target.result
+ if target.result_state == TextDetectionTargetWithResult.ResultState.Success:
+ if target.name in self.field_mapping:
+ data[self.field_mapping[target.name]] = target.result
if not data:
logger.debug("No data to send")