First semi-working version of coin
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@ downloaded_files/
|
|||||||
testing/
|
testing/
|
||||||
cropped.png
|
cropped.png
|
||||||
backup/
|
backup/
|
||||||
|
__pycache__/
|
||||||
|
page.html
|
||||||
|
13
coin.ui
13
coin.ui
@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1280</width>
|
<width>1695</width>
|
||||||
<height>720</height>
|
<height>720</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>594</width>
|
<width>802</width>
|
||||||
<height>644</height>
|
<height>644</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -101,7 +101,14 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="saveButton">
|
<widget class="QPushButton" name="saveFileButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save to file</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="saveDbButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save to database</string>
|
<string>Save to database</string>
|
||||||
</property>
|
</property>
|
||||||
|
168
gui.py
168
gui.py
@ -9,20 +9,30 @@ from PyQt6.QtWidgets import *
|
|||||||
from PyQt6 import uic
|
from PyQt6 import uic
|
||||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||||
import glob
|
import glob
|
||||||
|
import upc
|
||||||
|
from upc import get_name_from_upc
|
||||||
|
|
||||||
# [!!!] Change this if tesseract or coin can't find any data
|
# [!!!] Change this if tesseract or coin can't find any data
|
||||||
MODELS_PATH = '/usr/share/tessdata/'
|
MODELS_PATH = '/usr/share/tessdata/'
|
||||||
|
|
||||||
# [!!!] Sane values for scaling
|
# [!!!] Sane values for scaling
|
||||||
SCALE_MIN = 0.3
|
SCALE_MIN = 0.3
|
||||||
SCALE_MAX = 4.0
|
SCALE_MAX = 4.0
|
||||||
# How much (in decimal) to change the scale for every degree
|
# How much (in decimal) to change the scale for every degree
|
||||||
SCALE_DELTA = 8 * 360 * 1
|
SCALE_DELTA = 8 * 360 * 1
|
||||||
|
|
||||||
# [!!!] Sane values for recognition
|
# [!!!] Sane values for recognition
|
||||||
WIDTH_MIN = 10
|
WIDTH_MIN = 10
|
||||||
HEIGHT_MIN = 10
|
HEIGHT_MIN = 10
|
||||||
|
|
||||||
|
# Column Information
|
||||||
|
TABLE_COL_CNT = 5
|
||||||
|
TABLE_COL_GET = 0
|
||||||
|
TABLE_COL_DEL = 1
|
||||||
|
TABLE_COL_UPC = 2
|
||||||
|
TABLE_COL_NAME = 3
|
||||||
|
TABLE_COL_PRICE = 4
|
||||||
|
|
||||||
# Automatically finds all available language models
|
# Automatically finds all available language models
|
||||||
models_list = glob.glob(MODELS_PATH + "*.traineddata")
|
models_list = glob.glob(MODELS_PATH + "*.traineddata")
|
||||||
model_names = []
|
model_names = []
|
||||||
@ -34,7 +44,6 @@ if len(model_names) == 0:
|
|||||||
print("No tesseract models found at", MODELS_PATH, "!", file=sys.stderr)
|
print("No tesseract models found at", MODELS_PATH, "!", file=sys.stderr)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
class coin_app(QtWidgets.QMainWindow):
|
class coin_app(QtWidgets.QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QMainWindow.__init__(self)
|
QtWidgets.QMainWindow.__init__(self)
|
||||||
@ -42,6 +51,10 @@ class coin_app(QtWidgets.QMainWindow):
|
|||||||
self.image = None
|
self.image = None
|
||||||
|
|
||||||
self.ui.openButton.clicked.connect(self.open)
|
self.ui.openButton.clicked.connect(self.open)
|
||||||
|
self.ui.getNameButton.clicked.connect(self.get_all)
|
||||||
|
self.ui.saveFileButton.clicked.connect(self.save_file)
|
||||||
|
self.ui.saveDbButton.clicked.connect(self.save_database)
|
||||||
|
|
||||||
self.rubberBand = QRubberBand(QRubberBand.Shape.Rectangle, self)
|
self.rubberBand = QRubberBand(QRubberBand.Shape.Rectangle, self)
|
||||||
self.ui.photo.setMouseTracking(True)
|
self.ui.photo.setMouseTracking(True)
|
||||||
self.ui.photo.installEventFilter(self)
|
self.ui.photo.installEventFilter(self)
|
||||||
@ -53,23 +66,52 @@ class coin_app(QtWidgets.QMainWindow):
|
|||||||
self.models.currentTextChanged.connect(self.update_now)
|
self.models.currentTextChanged.connect(self.update_now)
|
||||||
self.models.setCurrentIndex(model_names.index(self.model))
|
self.models.setCurrentIndex(model_names.index(self.model))
|
||||||
|
|
||||||
self.items = QStandardItemModel()
|
|
||||||
self.itemsTable.setModel(self.items)
|
|
||||||
self.items.setColumnCount(1)
|
|
||||||
|
|
||||||
self.scale = 1.0
|
self.scale = 1.0
|
||||||
|
|
||||||
|
self.items = None
|
||||||
|
|
||||||
def update_now(self, value):
|
def update_now(self, value):
|
||||||
self.model = value
|
self.model = value
|
||||||
print("Model selected as:", self.model)
|
print("Model selected as:", self.model)
|
||||||
|
|
||||||
|
def set_table_header(self):
|
||||||
|
item = QStandardItem(" Get ")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
item.setEditable(False)
|
||||||
|
self.items.setItem(0, TABLE_COL_GET, item)
|
||||||
|
item = QStandardItem(" Del ")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
item.setEditable(False)
|
||||||
|
self.items.setItem(0, TABLE_COL_DEL, item)
|
||||||
|
item = QStandardItem("UPC")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
item.setEditable(False)
|
||||||
|
self.items.setItem(0, TABLE_COL_UPC, item)
|
||||||
|
item = QStandardItem("Item Name")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
item.setEditable(False)
|
||||||
|
self.items.setItem(0, TABLE_COL_NAME, item)
|
||||||
|
item = QStandardItem("Price")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
item.setEditable(False)
|
||||||
|
self.items.setItem(0, TABLE_COL_PRICE, item)
|
||||||
|
self.itemsTable.resizeColumnsToContents()
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
filename = QFileDialog.getOpenFileName(self, 'Select File')
|
filename = QFileDialog.getOpenFileName(self, 'Select File')
|
||||||
|
if filename[0] == "":
|
||||||
|
print("No file was selected or opened!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.items is not None:
|
||||||
|
self.items.deleteLater()
|
||||||
|
|
||||||
print("Opening file: ", filename[0])
|
print("Opening file: ", filename[0])
|
||||||
self.image = cv2.imread(str(filename[0]))
|
self.image = cv2.imread(str(filename[0]))
|
||||||
|
|
||||||
if self.image.size == 0:
|
if self.image is None:
|
||||||
print("Error: image is empty!", file=sys.stderr)
|
print("Error: image is empty!", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
frame = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
|
frame = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
|
||||||
image = QImage(frame, frame.shape[1], frame.shape[0],
|
image = QImage(frame, frame.shape[1], frame.shape[0],
|
||||||
@ -81,20 +123,123 @@ class coin_app(QtWidgets.QMainWindow):
|
|||||||
print("Updated photo!")
|
print("Updated photo!")
|
||||||
print("Photo scale set to:", self.scale)
|
print("Photo scale set to:", self.scale)
|
||||||
|
|
||||||
|
self.items = QStandardItemModel()
|
||||||
|
self.itemsTable.setModel(self.items)
|
||||||
|
self.items.setColumnCount(TABLE_COL_CNT)
|
||||||
|
|
||||||
|
self.set_table_header()
|
||||||
|
print("Initialized new table!")
|
||||||
|
|
||||||
|
self.statusBar().showMessage("Opened file: " + filename[0])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_file(self):
|
||||||
|
filename = QFileDialog.getSaveFileName(self, 'Select File')
|
||||||
|
|
||||||
|
if filename[0] == "":
|
||||||
|
print("No file was selected or opened!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
out = open(filename[0], "w")
|
||||||
|
|
||||||
|
for i in range(self.items.rowCount()):
|
||||||
|
print(self.items.item(i,TABLE_COL_UPC).text(), file=out, end=", ")
|
||||||
|
name = self.items.item(i,TABLE_COL_NAME).text()
|
||||||
|
name = name.replace("\n", " ").replace('"', '""')
|
||||||
|
print(name, file=out, end=", ")
|
||||||
|
print(self.items.item(i,TABLE_COL_PRICE).text().replace("\n", " "),
|
||||||
|
file=out)
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
self.statusBar().showMessage("Saved to: " + filename[0])
|
||||||
|
|
||||||
|
def save_database(self):
|
||||||
|
print("Not implemented!")
|
||||||
|
|
||||||
def scale_update(self):
|
def scale_update(self):
|
||||||
self.ui.photo.setPixmap(self.pixmap.scaled(
|
self.ui.photo.setPixmap(self.pixmap.scaled(
|
||||||
int(self.scale * self.pixmap.width()),
|
int(self.scale * self.pixmap.width()),
|
||||||
int(self.scale * self.pixmap.height())))
|
int(self.scale * self.pixmap.height())))
|
||||||
print("Photo scale set to:", self.scale)
|
print("Photo scale set to:", self.scale)
|
||||||
|
|
||||||
|
self.statusBar().showMessage("Photo scale set to: " + str(self.scale))
|
||||||
|
|
||||||
def image_to_text(self, cropped_image):
|
def image_to_text(self, cropped_image):
|
||||||
gray = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)
|
gray = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)
|
||||||
gray = cv2.medianBlur(gray, 1)
|
gray = cv2.medianBlur(gray, 1)
|
||||||
crop = Image.fromarray(gray)
|
crop = Image.fromarray(gray)
|
||||||
text = pytesseract.image_to_string(crop, lang=self.model)
|
text = pytesseract.image_to_string(crop, lang=self.model).strip()
|
||||||
print("Text selected:", text)
|
print("Text selected:", text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
def get_row(self):
|
||||||
|
button = self.sender()
|
||||||
|
row = self.itemsTable.indexAt(button.pos()).row()
|
||||||
|
upc = self.items.item(row, TABLE_COL_UPC).text()
|
||||||
|
print("Started getting name of UPC:", upc)
|
||||||
|
name = get_name_from_upc(upc, max_tries=3)
|
||||||
|
print("Got name", name, "from UPC:", upc)
|
||||||
|
item = QStandardItem(name)
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.items.setItem(row, TABLE_COL_NAME, item)
|
||||||
|
self.itemsTable.resizeColumnToContents(TABLE_COL_NAME)
|
||||||
|
self.itemsTable.resizeRowToContents(row)
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
for i in range(1, self.items.rowCount()):
|
||||||
|
|
||||||
|
if self.items.item(i, TABLE_COL_NAME) is None or self.items.item(i, TABLE_COL_NAME).text() == "N/A":
|
||||||
|
upc = self.items.item(i, TABLE_COL_UPC).text()
|
||||||
|
print("Started getting name of UPC:", upc)
|
||||||
|
self.statusBar().showMessage("Started getting name of UPC: " + upc)
|
||||||
|
name = get_name_from_upc(upc, wait_interval=1, max_tries=30)
|
||||||
|
print("Got name", name, "from UPC:", upc)
|
||||||
|
item = QStandardItem(name)
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.items.setItem(i, TABLE_COL_NAME, item)
|
||||||
|
self.itemsTable.resizeColumnToContents(TABLE_COL_NAME)
|
||||||
|
self.itemsTable.resizeRowsToContents()
|
||||||
|
|
||||||
|
def del_row(self):
|
||||||
|
button = self.sender()
|
||||||
|
index = self.itemsTable.indexAt(button.pos())
|
||||||
|
self.items.removeRow(index.row())
|
||||||
|
print("Removed row", index.row())
|
||||||
|
|
||||||
|
def populate_buttons(self):
|
||||||
|
del_button = QPushButton("Del")
|
||||||
|
self.itemsTable.setIndexWidget(
|
||||||
|
self.items.index(self.items.rowCount() - 1, TABLE_COL_DEL),
|
||||||
|
del_button)
|
||||||
|
del_button.clicked.connect(self.del_row)
|
||||||
|
|
||||||
|
get_button = QPushButton("Get")
|
||||||
|
self.itemsTable.setIndexWidget(
|
||||||
|
self.items.index(self.items.rowCount() - 1, TABLE_COL_GET),
|
||||||
|
get_button)
|
||||||
|
get_button.clicked.connect(self.get_row)
|
||||||
|
|
||||||
|
def add_upc(self, text: str):
|
||||||
|
if text == "":
|
||||||
|
print("No text was recognized, will not add a new item!")
|
||||||
|
return None
|
||||||
|
self.statusBar().showMessage("Text selected: " + text)
|
||||||
|
item = QStandardItem(text)
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.items.setItem(self.items.rowCount(), TABLE_COL_UPC, item)
|
||||||
|
|
||||||
|
item = QStandardItem("N/A")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.items.setItem(self.items.rowCount() - 1, TABLE_COL_NAME, item)
|
||||||
|
item = QStandardItem("N/A")
|
||||||
|
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.items.setItem(self.items.rowCount() - 1, TABLE_COL_PRICE, item)
|
||||||
|
|
||||||
|
self.populate_buttons()
|
||||||
|
self.itemsTable.resizeColumnToContents(TABLE_COL_UPC)
|
||||||
|
return None
|
||||||
|
|
||||||
def eventFilter(self, source, event):
|
def eventFilter(self, source, event):
|
||||||
# Handling the selection box
|
# Handling the selection box
|
||||||
if event.type() == QEvent.Type.MouseButtonPress and source is self.ui.photo:
|
if event.type() == QEvent.Type.MouseButtonPress and source is self.ui.photo:
|
||||||
@ -132,8 +277,9 @@ class coin_app(QtWidgets.QMainWindow):
|
|||||||
if width >= WIDTH_MIN and height >= HEIGHT_MIN and self.image is not None:
|
if width >= WIDTH_MIN and height >= HEIGHT_MIN and self.image is not None:
|
||||||
print("Cropped image:", self.x1, self.y1, self.x2, self.y2)
|
print("Cropped image:", self.x1, self.y1, self.x2, self.y2)
|
||||||
self.crop = self.image[self.y1:self.y2, self.x1:self.x2]
|
self.crop = self.image[self.y1:self.y2, self.x1:self.x2]
|
||||||
cv2.imwrite("cropped.png", self.crop)
|
# cv2.imwrite("cropped.png", self.crop)
|
||||||
self.text = self.image_to_text(self.crop)
|
self.text = self.image_to_text(self.crop)
|
||||||
|
self.add_upc(self.text)
|
||||||
##self.ui.textEdit()
|
##self.ui.textEdit()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
34
upc.py
34
upc.py
@ -4,27 +4,37 @@ from seleniumbase import Driver
|
|||||||
driver = Driver(uc=True)
|
driver = Driver(uc=True)
|
||||||
driver.implicitly_wait(5)
|
driver.implicitly_wait(5)
|
||||||
|
|
||||||
def get_name_from_upc(upc: str, wait_interval=10) -> str:
|
def get_name_from_upc(upc: str, wait_interval=3, max_tries=10) -> str:
|
||||||
url = "https://stocktrack.ca/wm/index.php?s=wm&upc=" + upc
|
url = "https://stocktrack.ca/wm/index.php?s=wm&upc=" + upc
|
||||||
driver.get(url)
|
driver.get(url)
|
||||||
|
|
||||||
|
upc = upc.lstrip("0")
|
||||||
|
|
||||||
|
print("Removed leading 0's:", upc)
|
||||||
|
|
||||||
# Change the cookies here to match that of yours when you lookup
|
# Change the cookies here to match that of yours when you lookup
|
||||||
# any item on stocktrack.ca to bypass the Cloudflare checks
|
# any item on stocktrack.ca to bypass the Cloudflare checks
|
||||||
driver.add_cookie({
|
driver.add_cookie({
|
||||||
"name": "PHPSESSID",
|
"name": "PHPSESSID",
|
||||||
"value": "835ttgvbgncf4uq82dpagmk688",
|
"value": "9n1ic479r1bteiv758gm9hk65p",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"domain": "stocktrack.ca"
|
"domain": "stocktrack.ca"
|
||||||
})
|
})
|
||||||
driver.add_cookie({
|
driver.add_cookie({
|
||||||
"name": "cf_chl_3",
|
"name": "cf_chl_3",
|
||||||
"value": "1fad2e046cb9148",
|
"value": "1d706187484b25c",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"domain": "stocktrack.ca"
|
"domain": "stocktrack.ca"
|
||||||
})
|
})
|
||||||
driver.add_cookie({
|
driver.add_cookie({
|
||||||
"name": "cf_clearance",
|
"name": "cf_clearance",
|
||||||
"value": "LUZoWRwn3n1wBEv7bY1XinLQbX0ZTAnggLIfkoy2S60-1708745781-1.0-AXqZtJSIQrq/6/H03NLlHwbpLA1tJmPjgjMJhzaczZjGabhR6ow16wMK5pTwilZiqK7C7fdcYvM0qsbAARkLGEU=",
|
"value": "Wp8tAMUKLdS3a4Y9AT09BIlZKx4x120uC1QzBQTUluQ-1710517775-1.0.1.1-hMEP8oeggZHBkylkwkQfi2p57H6zUUvGG40d_M4vGqOqg2Zh7wZsg6KrGl3XkDUn3mXAqyZrTqlQfd5pgHCZWQ",
|
||||||
|
"path": "/",
|
||||||
|
"domain": "stocktrack.ca"
|
||||||
|
})
|
||||||
|
driver.add_cookie({
|
||||||
|
"name": "fp",
|
||||||
|
"value": "26f4acb9b23415f921bba6977b68d55f",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"domain": "stocktrack.ca"
|
"domain": "stocktrack.ca"
|
||||||
})
|
})
|
||||||
@ -32,25 +42,29 @@ def get_name_from_upc(upc: str, wait_interval=10) -> str:
|
|||||||
driver.refresh()
|
driver.refresh()
|
||||||
name = ""
|
name = ""
|
||||||
str_s = "target=\"_blank\">"
|
str_s = "target=\"_blank\">"
|
||||||
str_t = "</a><br>UPC: " + upc + "<br>"
|
str_t = "<br>UPC: " + upc + "<br>"
|
||||||
|
str_tt = "</a><br>SKU:"
|
||||||
times = 0
|
times = 0
|
||||||
while True:
|
while times < max_tries:
|
||||||
if __debug__:
|
if __debug__:
|
||||||
print("Iteration No. ", times)
|
print("Iteration No. ", times)
|
||||||
times = times + 1
|
|
||||||
|
times = times + 1
|
||||||
|
|
||||||
time.sleep(wait_interval)
|
time.sleep(wait_interval)
|
||||||
page = str(driver.execute_script(
|
page = str(driver.execute_script(
|
||||||
"return document.getElementsByTagName('html')[0].innerHTML"))
|
"return document.getElementsByTagName('html')[0].innerHTML"))
|
||||||
t = page.find(str_t)
|
t = page.find(str_t)
|
||||||
s = page.rfind(str_s, 0, t)
|
s = page.rfind(str_s, 0, t)
|
||||||
|
tt = page.rfind(str_tt, 0, t)
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
print(page[s + len(str_s) : t - 1])
|
p = open("page.html", "w")
|
||||||
|
print(page, file=p)
|
||||||
|
|
||||||
if t == -1 or s == -1:
|
if t == -1 or s == -1:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
name = page[s + len(str_s) : t - 1]
|
name = page[s + len(str_s) : tt]
|
||||||
break
|
break
|
||||||
return name
|
return name.replace(" ", "\n")
|
||||||
|
Reference in New Issue
Block a user