Skip to content

Commit

Permalink
Refactor code to handle text detection target result states and updat…
Browse files Browse the repository at this point in the history
…e UI accordingly
  • Loading branch information
royshil committed Apr 28, 2024
1 parent 846f80b commit 514fadd
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 8 deletions.
21 changes: 20 additions & 1 deletion camera_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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],
Expand All @@ -335,6 +350,7 @@ def run(self):
result.extra,
)
)
detectionTargets[i].last_text = result.text

# emit the results
self.ocr_result_signal.emit(results)
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 10 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions mainwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1370,11 +1370,27 @@
<property name="invertedControls">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_updateOnchange">
<property name="toolTip">
<string>Only send an update if the field value has changed</string>
</property>
<property name="text">
<string>Update on change</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
5 changes: 5 additions & 0 deletions source_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions tesseract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions text_detection_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ 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):
class ResultState(enum.Enum):
Success = 0
FailedFilter = 1
Empty = 2
SameNoChange = 3

def __init__(
self,
Expand Down
11 changes: 11 additions & 0 deletions ui_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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))
Expand Down
5 changes: 3 additions & 2 deletions vmix_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 514fadd

Please sign in to comment.