Skip to content

Commit

Permalink
Merge pull request #34 from locaal-ai/roy.template_fields
Browse files Browse the repository at this point in the history
feat: Add template field icon and settings
  • Loading branch information
royshil authored Sep 10, 2024
2 parents de209a4 + 3292f3e commit 2ad1849
Show file tree
Hide file tree
Showing 11 changed files with 714 additions and 350 deletions.
6 changes: 6 additions & 0 deletions defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,10 @@ def normalize_settings_dict(settings, box_info):
if "ordinal_indicator" in settings
else box_info["ordinal_indicator"]
),
"templatefield": (
settings["templatefield"] if "templatefield" in settings else False
),
"templatefield_text": (
settings["templatefield_text"] if "templatefield_text" in settings else ""
),
}
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ If you'd like to donate to help support the project, you can do so on [GitHub](h
- Integrations: OBS (websocket), vMix (API), NewBlue FX Titler (API)
- Up to 30 updates/s
- Unlimited detection boxes
- Template fields: Derived from other fields and optional extra text
- Camera bump and drift correction with stabilization algorithm
- Unlimited devices or open instances on the same device
- Detect any scoreboard fonts, general fonts and even "dot" indicators
Expand Down
4 changes: 4 additions & 0 deletions icons/template-field.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
145 changes: 119 additions & 26 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
update_text_source,
)

from template_fields import evaluate_template_field
from text_detection_target import TextDetectionTarget, TextDetectionTargetWithResult
from sc_logging import logger
from update_check import check_for_updates
Expand Down Expand Up @@ -277,6 +278,10 @@ def __init__(self, translator: QTranslator, parent: QObject):
self.ui.comboBox_binarizationMethod.currentIndexChanged.connect(
partial(self.genericSettingsChanged, "binarization_method")
)
self.ui.checkBox_templatefield.toggled.connect(self.makeTemplateField)
self.ui.lineEdit_templatefield.textChanged.connect(
partial(self.genericSettingsChanged, "templatefield_text")
)
self.ui.comboBox_formatPrefix.currentIndexChanged.connect(
self.formatPrefixChanged
)
Expand Down Expand Up @@ -546,12 +551,12 @@ def clearOutputFolder(self):
def editSettings(self, settingsMutatorCallback):
# update the selected item's settings in the detectionTargetsStorage
item = self.ui.tableWidget_boxes.currentItem()
if not item:
if item is None:
logger.info("no item selected")
return
item_name = item.text()
item_obj = self.detectionTargetsStorage.find_item_by_name(item_name)
if not item_obj:
if item_obj is None:
logger.info("item not found: %s", item_name)
return
item_obj = settingsMutatorCallback(item_obj)
Expand Down Expand Up @@ -725,14 +730,29 @@ def detectionTargetsChanged(self, detectionTargets):
)
else:
item = items[0]
item.setIcon(
QIcon(
path.abspath(
path.join(path.dirname(__file__), "icons/circle-check.svg")

if not box.settings["templatefield"]:
# this is a detection target
item.setIcon(
QIcon(
path.abspath(
path.join(path.dirname(__file__), "icons/circle-check.svg")
)
)
)
)
item.setData(Qt.ItemDataRole.UserRole, "checked")
item.setData(Qt.ItemDataRole.UserRole, "checked")
else:
# this is a template field
item.setIcon(
QIcon(
path.abspath(
path.join(
path.dirname(__file__), "icons/template-field.svg"
)
)
)
)
item.setData(Qt.ItemDataRole.UserRole, "templatefield")

self.updatevMixTable(detectionTargets)

Expand Down Expand Up @@ -768,10 +788,12 @@ def populateSettings(self, name):
self.ui.checkBox_ordinalIndicator.blockSignals(True)
self.ui.comboBox_binarizationMethod.blockSignals(True)
self.ui.comboBox_formatPrefix.blockSignals(True)
self.ui.checkBox_templatefield.blockSignals(True)
self.ui.lineEdit_templatefield.blockSignals(True)

# populate the settings from the detectionTargetsStorage
item_obj = self.detectionTargetsStorage.find_item_by_name(name)
if not item_obj:
if item_obj is None:
self.ui.lineEdit_format.setText("")
self.ui.comboBox_fieldType.setCurrentIndex(0)
self.ui.checkBox_smoothing.setChecked(True)
Expand All @@ -790,6 +812,8 @@ def populateSettings(self, name):
self.ui.checkBox_invertPatch.setChecked(False)
self.ui.checkBox_ordinalIndicator.setChecked(False)
self.ui.comboBox_binarizationMethod.setCurrentIndex(0)
self.ui.checkBox_templatefield.setChecked(False)
self.ui.lineEdit_templatefield.setText("")
else:
item_obj.settings = normalize_settings_dict(
item_obj.settings, default_info_for_box_name(item_obj.name)
Expand Down Expand Up @@ -827,6 +851,12 @@ def populateSettings(self, name):
self.ui.comboBox_binarizationMethod.setCurrentIndex(
item_obj.settings["binarization_method"]
)
self.ui.checkBox_templatefield.setChecked(
item_obj.settings["templatefield"]
)
self.ui.lineEdit_templatefield.setText(
item_obj.settings["templatefield_text"]
)

self.ui.comboBox_formatPrefix.setCurrentIndex(12)

Expand All @@ -848,21 +878,33 @@ def populateSettings(self, name):
self.ui.checkBox_ordinalIndicator.blockSignals(False)
self.ui.comboBox_binarizationMethod.blockSignals(False)
self.ui.comboBox_formatPrefix.blockSignals(False)
self.ui.checkBox_templatefield.blockSignals(False)
self.ui.lineEdit_templatefield.blockSignals(False)

def listItemClicked(self, item):
if item.data(Qt.ItemDataRole.UserRole) == "checked":
user_role = item.data(Qt.ItemDataRole.UserRole)
if user_role in ["checked", "templatefield"] and item.column() == 0:
# enable the remove box button and disable the make box button
self.ui.pushButton_removeBox.setEnabled(True)
self.ui.pushButton_makeBox.setEnabled(False)
self.ui.groupBox_target_settings.setEnabled(True)
self.ui.pushButton_removeBox.setEnabled(user_role == "checked")
self.ui.groupBox_target_settings.setEnabled(user_role == "checked")
self.populateSettings(item.text())
else:
# enable the make box button and disable the remove box button
self.ui.pushButton_removeBox.setEnabled(False)
self.ui.pushButton_makeBox.setEnabled(True)
self.ui.pushButton_makeBox.setEnabled(item.column() == 0)
self.ui.groupBox_target_settings.setEnabled(False)
self.populateSettings("")

if item.column() == 0:
# if this is not a default box - enable the template field checkbox
if item.text() not in [box["name"] for box in default_boxes]:
self.ui.checkBox_templatefield.setEnabled(True)
self.ui.lineEdit_templatefield.setEnabled(True)
else:
self.ui.checkBox_templatefield.setEnabled(False)
self.ui.lineEdit_templatefield.setEnabled(False)

def openOBSConnectModal(self):
# disable OBS options
self.ui.lineEdit_sceneName.setEnabled(False)
Expand Down Expand Up @@ -1187,14 +1229,16 @@ def updateError(self, error):
self.ui.widget_viewTools.setEnabled(False)

def ocrResult(self, results: list[TextDetectionTargetWithResult]):
if not self.updateOCRResults:
# don't update the results, the user has disabled updates
return

update_http_server(results)

# update vmix
self.vmixUpdater.update_vmix(results)
# update template fields
for targetWithResult in results:
if not targetWithResult.settings["templatefield"]:
continue
targetWithResult.result = evaluate_template_field(results, targetWithResult)
targetWithResult.result_state = (
TextDetectionTargetWithResult.ResultState.Success
if targetWithResult.result is not None
else TextDetectionTargetWithResult.ResultState.Empty
)

# update the table widget value items
for targetWithResult in results:
Expand All @@ -1212,6 +1256,15 @@ def ocrResult(self, results: list[TextDetectionTargetWithResult]):
item = self.ui.tableWidget_boxes.item(item.row(), 1)
item.setText(targetWithResult.result)

if not self.updateOCRResults:
# don't update the results, the user has disabled updates
return

update_http_server(results)

# update vmix
self.vmixUpdater.update_vmix(results)

if self.out_folder is None:
return

Expand Down Expand Up @@ -1331,23 +1384,30 @@ def removeCustomBox(self):

def editBoxName(self, item):
if item.text() in [o["name"] for o in default_boxes]:
# dont allow editing default boxes
return
new_name, ok = QInputDialog.getText(
self, "Edit Box Name", "New Name:", text=item.text()
)
if ok and new_name != "" and new_name != item.text():
old_name = item.text()
if ok and new_name != "" and new_name != old_name:
# check if name doesn't exist already
for i in range(self.ui.tableWidget_boxes.rowCount()):
if new_name == self.ui.tableWidget_boxes.item(i, 0).text():
logger.info("Name '%s' already exists", new_name)
return
# rename the item in the tableWidget_boxes
rename_custom_box_name_in_storage(old_name, new_name)
item.setText(new_name)
# rename the item in the detectionTargetsStorage
if not self.detectionTargetsStorage.rename_item(item.text(), new_name):
if not self.detectionTargetsStorage.rename_item(old_name, new_name):
logger.info("Error renaming item in application storage")
return
# rename the item in the tableWidget_boxes
item.setText(new_name)
rename_custom_box_name_in_storage(item.text(), new_name)
else:
# check if the item role isn't "templatefield"
if item.data(Qt.ItemDataRole.UserRole) != "templatefield":
# remove the item from the tableWidget_boxes
self.ui.tableWidget_boxes.removeRow(item.row())

def makeBox(self):
item = self.ui.tableWidget_boxes.currentItem()
Expand Down Expand Up @@ -1391,6 +1451,39 @@ def removeBox(self):
self.listItemClicked(item)
self.detectionTargetsStorage.remove_item(item.text())

def makeTemplateField(self, toggled: bool):
item = self.ui.tableWidget_boxes.currentItem()
if not item:
return

if not toggled:
self.removeBox()
return

# create a new box on self.image_viewer with the name of the selected item from the tableWidget_boxes
# change the list icon to green checkmark
item.setIcon(
QIcon(
path.abspath(
path.join(path.dirname(__file__), "icons/template-field.svg")
)
)
)
item.setData(Qt.ItemDataRole.UserRole, "templatefield")

self.detectionTargetsStorage.add_item(
TextDetectionTarget(
0,
0,
0,
0,
item.text(),
normalize_settings_dict({"templatefield": True}, None),
)
)

self.listItemClicked(item)

def createOBSScene(self):
self.ui.statusbar.showMessage("Creating OBS scene")
# get the scene name from the lineEdit_sceneName
Expand Down
Loading

0 comments on commit 2ad1849

Please sign in to comment.