|
@@ -0,0 +1,642 @@
|
|
|
|
|
+# pip install pillow
|
|
|
|
|
+import tkinter as tk
|
|
|
|
|
+from tkinter import filedialog, messagebox
|
|
|
|
|
+from PIL import Image, ImageTk
|
|
|
|
|
+import sqlite3
|
|
|
|
|
+import datetime
|
|
|
|
|
+import os
|
|
|
|
|
+
|
|
|
|
|
+# Подключение к бд
|
|
|
|
|
+conn = sqlite3.connect('sqlite.db')
|
|
|
|
|
+cursor = conn.cursor()
|
|
|
|
|
+
|
|
|
|
|
+cursor.execute('''
|
|
|
|
|
+CREATE TABLE IF NOT EXISTS annotations (
|
|
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
+ filename TEXT NOT NULL,
|
|
|
|
|
+ image_width INTEGER,
|
|
|
|
|
+ image_height INTEGER,
|
|
|
|
|
+ x1 INTEGER,
|
|
|
|
|
+ y1 INTEGER,
|
|
|
|
|
+ x2 INTEGER,
|
|
|
|
|
+ y2 INTEGER,
|
|
|
|
|
+ rectangle_width INTEGER,
|
|
|
|
|
+ rectangle_height INTEGER,
|
|
|
|
|
+ image_left INTEGER,
|
|
|
|
|
+ image_bottom INTEGER,
|
|
|
|
|
+ image_top INTEGER,
|
|
|
|
|
+ image_right INTEGER,
|
|
|
|
|
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
+)
|
|
|
|
|
+''')
|
|
|
|
|
+conn.commit()
|
|
|
|
|
+
|
|
|
|
|
+root = tk.Tk()
|
|
|
|
|
+root.title("Image Annotation Tool")
|
|
|
|
|
+root.state('zoomed')
|
|
|
|
|
+root.configure(bg='#2b2b2b')
|
|
|
|
|
+
|
|
|
|
|
+bg_color = '#2b2b2b'
|
|
|
|
|
+frame_color = '#3c3c3c'
|
|
|
|
|
+text_color = '#ffffff'
|
|
|
|
|
+accent_color = '#4a90d9'
|
|
|
|
|
+
|
|
|
|
|
+panel = tk.Frame(root, width=200, height=600, bg=frame_color)
|
|
|
|
|
+panel.pack(side='right', fill='y')
|
|
|
|
|
+panel.pack_propagate(False)
|
|
|
|
|
+
|
|
|
|
|
+zagolovok = tk.Label(panel, text="Координаты \nизображения:",
|
|
|
|
|
+font=("Arial", 12, "bold"), bg=frame_color, fg=accent_color)
|
|
|
|
|
+zagolovok.pack(pady=20)
|
|
|
|
|
+
|
|
|
|
|
+levo_metka = tk.Label(panel, text="Left: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
|
|
|
|
|
+levo_metka.pack(pady=8)
|
|
|
|
|
+niz_metka = tk.Label(panel, text="Bottom: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
|
|
|
|
|
+niz_metka.pack(pady=8)
|
|
|
|
|
+verh_metka = tk.Label(panel, text="Top: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
|
|
|
|
|
+verh_metka.pack(pady=8)
|
|
|
|
|
+pravo_metka = tk.Label(panel, text="Right: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
|
|
|
|
|
+pravo_metka.pack(pady=8)
|
|
|
|
|
+
|
|
|
|
|
+razdelitel = tk.Frame(panel, height=2, bg=accent_color)
|
|
|
|
|
+razdelitel.pack(fill='x', padx=20, pady=20)
|
|
|
|
|
+
|
|
|
|
|
+ramka_pryam = tk.Frame(panel, bg=frame_color)
|
|
|
|
|
+ramka_pryam.pack(pady=10)
|
|
|
|
|
+
|
|
|
|
|
+metka_pryam = tk.Label(ramka_pryam, text="Координаты прямоугольника:",
|
|
|
|
|
+font=("Arial", 10, "bold"), bg=frame_color, fg=accent_color)
|
|
|
|
|
+metka_pryam.pack(pady=5)
|
|
|
|
|
+
|
|
|
|
|
+vvod_koord = {}
|
|
|
|
|
+metki_koord = ["X1:", "Y1:", "X2:", "Y2:"]
|
|
|
|
|
+znach_po_umolch = ["100", "100", "300", "300"]
|
|
|
|
|
+
|
|
|
|
|
+for i, metka in enumerate(metki_koord):
|
|
|
|
|
+ ramochka = tk.Frame(ramka_pryam, bg=frame_color)
|
|
|
|
|
+ ramochka.pack(pady=2)
|
|
|
|
|
+ tk.Label(ramochka, text=metka, font=("Arial", 9), bg=frame_color, fg=text_color, width=4).pack(side='left')
|
|
|
|
|
+ pole = tk.Entry(ramochka, width=8, font=("Arial", 9))
|
|
|
|
|
+ pole.insert(0, znach_po_umolch[i])
|
|
|
|
|
+ pole.pack(side='left')
|
|
|
|
|
+ vvod_koord[metka.replace(":", "")] = pole
|
|
|
|
|
+
|
|
|
|
|
+peremesh_aktivno = False
|
|
|
|
|
+ugol_dlya_peremesh = None
|
|
|
|
|
+nach_koord_peremesh = None
|
|
|
|
|
+tekush_pryam = None
|
|
|
|
|
+
|
|
|
|
|
+tekush_izobr = None
|
|
|
|
|
+poz_izobr = None
|
|
|
|
|
+tekush_put_k_file = None
|
|
|
|
|
+pil_izobr_obj = None
|
|
|
|
|
+
|
|
|
|
|
+def obnovit_poly_vvoda():
|
|
|
|
|
+ if tekush_pryam and poz_izobr:
|
|
|
|
|
+ koord = holst.coords(tekush_pryam)
|
|
|
|
|
+ if koord and len(koord) >= 4:
|
|
|
|
|
+ img_x, img_y = poz_izobr
|
|
|
|
|
+ vvod_koord["X1"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["X1"].insert(0, str(int(koord[0] - img_x)))
|
|
|
|
|
+ vvod_koord["Y1"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["Y1"].insert(0, str(int(koord[1] - img_y)))
|
|
|
|
|
+ vvod_koord["X2"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["X2"].insert(0, str(int(koord[2] - img_x)))
|
|
|
|
|
+ vvod_koord["Y2"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["Y2"].insert(0, str(int(koord[3] - img_y)))
|
|
|
|
|
+
|
|
|
|
|
+def narisovat_pryam():
|
|
|
|
|
+ global tekush_pryam
|
|
|
|
|
+ try:
|
|
|
|
|
+ x1 = int(vvod_koord["X1"].get())
|
|
|
|
|
+ y1 = int(vvod_koord["Y1"].get())
|
|
|
|
|
+ x2 = int(vvod_koord["X2"].get())
|
|
|
|
|
+ y2 = int(vvod_koord["Y2"].get())
|
|
|
|
|
+
|
|
|
|
|
+ holst.delete("pryamougolnik")
|
|
|
|
|
+ holst.delete("ugol")
|
|
|
|
|
+
|
|
|
|
|
+ if poz_izobr:
|
|
|
|
|
+ img_x, img_y = poz_izobr
|
|
|
|
|
+ abs_x1 = img_x + x1
|
|
|
|
|
+ abs_y1 = img_y + y1
|
|
|
|
|
+ abs_x2 = img_x + x2
|
|
|
|
|
+ abs_y2 = img_y + y2
|
|
|
|
|
+
|
|
|
|
|
+ if abs_x1 > abs_x2:
|
|
|
|
|
+ abs_x1, abs_x2 = abs_x2, abs_x1
|
|
|
|
|
+ if abs_y1 > abs_y2:
|
|
|
|
|
+ abs_y1, abs_y2 = abs_y2, abs_y1
|
|
|
|
|
+
|
|
|
|
|
+ tekush_pryam = holst.create_rectangle(
|
|
|
|
|
+ abs_x1, abs_y1, abs_x2, abs_y2,
|
|
|
|
|
+ outline="red", width=2, tags="pryamougolnik",
|
|
|
|
|
+ dash=(5, 2)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ narisovat_uglovye_markery(abs_x1, abs_y1, abs_x2, abs_y2)
|
|
|
|
|
+ sohranit_koord_pryam(abs_x1, abs_y1, abs_x2, abs_y2)
|
|
|
|
|
+
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+def sohranit_koord_pryam(x1, y1, x2, y2):
|
|
|
|
|
+ global sohran_koord_pryam
|
|
|
|
|
+ sohran_koord_pryam = [x1, y1, x2, y2]
|
|
|
|
|
+
|
|
|
|
|
+def narisovat_uglovye_markery(x1, y1, x2, y2):
|
|
|
|
|
+ razmer_markera = 8
|
|
|
|
|
+
|
|
|
|
|
+ # Левый верхний
|
|
|
|
|
+ holst.create_oval(
|
|
|
|
|
+ x1 - razmer_markera, y1 - razmer_markera,
|
|
|
|
|
+ x1 + razmer_markera, y1 + razmer_markera,
|
|
|
|
|
+ fill="red", outline="white", width=1, tags="ugol"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Правый верхний
|
|
|
|
|
+ holst.create_oval(
|
|
|
|
|
+ x2 - razmer_markera, y1 - razmer_markera,
|
|
|
|
|
+ x2 + razmer_markera, y1 + razmer_markera,
|
|
|
|
|
+ fill="red", outline="white", width=1, tags="ugol"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Левый нижний
|
|
|
|
|
+ holst.create_oval(
|
|
|
|
|
+ x1 - razmer_markera, y2 - razmer_markera,
|
|
|
|
|
+ x1 + razmer_markera, y2 + razmer_markera,
|
|
|
|
|
+ fill="red", outline="white", width=1, tags="ugol"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # Правый нижний
|
|
|
|
|
+ holst.create_oval(
|
|
|
|
|
+ x2 - razmer_markera, y2 - razmer_markera,
|
|
|
|
|
+ x2 + razmer_markera, y2 + razmer_markera,
|
|
|
|
|
+ fill="red", outline="white", width=1, tags="ugol"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+def ochistit_pryam():
|
|
|
|
|
+ global tekush_pryam
|
|
|
|
|
+ holst.delete("pryamougolnik")
|
|
|
|
|
+ holst.delete("ugol")
|
|
|
|
|
+ tekush_pryam = None
|
|
|
|
|
+
|
|
|
|
|
+def nachalo_peremesh(event):
|
|
|
|
|
+ global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
|
|
|
|
|
+
|
|
|
|
|
+ if not tekush_pryam:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ x, y = event.x, event.y
|
|
|
|
|
+ koord_pryam = holst.coords(tekush_pryam)
|
|
|
|
|
+
|
|
|
|
|
+ if not koord_pryam:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ tolshina_klika = 15
|
|
|
|
|
+
|
|
|
|
|
+ ugli = [
|
|
|
|
|
+ (koord_pryam[0], koord_pryam[1], "levyy_verhniy"),
|
|
|
|
|
+ (koord_pryam[2], koord_pryam[1], "pravyy_verhniy"),
|
|
|
|
|
+ (koord_pryam[0], koord_pryam[3], "levyy_nizhniy"),
|
|
|
|
|
+ (koord_pryam[2], koord_pryam[3], "pravyy_nizhniy")
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ for ugol_x, ugol_y, tip_ugla in ugli:
|
|
|
|
|
+ if abs(x - ugol_x) <= tolshina_klika and abs(y - ugol_y) <= tolshina_klika:
|
|
|
|
|
+ peremesh_aktivno = True
|
|
|
|
|
+ ugol_dlya_peremesh = tip_ugla
|
|
|
|
|
+ nach_koord_peremesh = (x, y, koord_pryam[0], koord_pryam[1], koord_pryam[2], koord_pryam[3])
|
|
|
|
|
+ holst.config(cursor="crosshair")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+def peremeshchenie(event):
|
|
|
|
|
+ global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
|
|
|
|
|
+
|
|
|
|
|
+ if not peremesh_aktivno or not tekush_pryam:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ x, y = event.x, event.y
|
|
|
|
|
+ nach_x, nach_y, x1, y1, x2, y2 = nach_koord_peremesh
|
|
|
|
|
+
|
|
|
|
|
+ dx = x - nach_x
|
|
|
|
|
+ dy = y - nach_y
|
|
|
|
|
+
|
|
|
|
|
+ x = max(0, min(x, holst.winfo_width()))
|
|
|
|
|
+ y = max(0, min(y, holst.winfo_height()))
|
|
|
|
|
+
|
|
|
|
|
+ if ugol_dlya_peremesh == "levyy_verhniy":
|
|
|
|
|
+ nov_koord = [x, y, x2, y2]
|
|
|
|
|
+ if nov_koord[2] - nov_koord[0] < 10:
|
|
|
|
|
+ nov_koord[0] = nov_koord[2] - 10
|
|
|
|
|
+ if nov_koord[3] - nov_koord[1] < 10:
|
|
|
|
|
+ nov_koord[1] = nov_koord[3] - 10
|
|
|
|
|
+ elif ugol_dlya_peremesh == "pravyy_verhniy":
|
|
|
|
|
+ nov_koord = [x1, y, x, y2]
|
|
|
|
|
+ if nov_koord[2] - nov_koord[0] < 10:
|
|
|
|
|
+ nov_koord[2] = nov_koord[0] + 10
|
|
|
|
|
+ if nov_koord[3] - nov_koord[1] < 10:
|
|
|
|
|
+ nov_koord[1] = nov_koord[3] - 10
|
|
|
|
|
+ elif ugol_dlya_peremesh == "levyy_nizhniy":
|
|
|
|
|
+ nov_koord = [x, y1, x2, y]
|
|
|
|
|
+ if nov_koord[2] - nov_koord[0] < 10:
|
|
|
|
|
+ nov_koord[0] = nov_koord[2] - 10
|
|
|
|
|
+ if nov_koord[3] - nov_koord[1] < 10:
|
|
|
|
|
+ nov_koord[3] = nov_koord[1] + 10
|
|
|
|
|
+ elif ugol_dlya_peremesh == "pravyy_nizhniy":
|
|
|
|
|
+ nov_koord = [x1, y1, x, y]
|
|
|
|
|
+ if nov_koord[2] - nov_koord[0] < 10:
|
|
|
|
|
+ nov_koord[2] = nov_koord[0] + 10
|
|
|
|
|
+ if nov_koord[3] - nov_koord[1] < 10:
|
|
|
|
|
+ nov_koord[3] = nov_koord[1] + 10
|
|
|
|
|
+ else:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ holst.coords(tekush_pryam, *nov_koord)
|
|
|
|
|
+ holst.delete("ugol")
|
|
|
|
|
+ narisovat_uglovye_markery(*nov_koord)
|
|
|
|
|
+ obnovit_poly_vvoda()
|
|
|
|
|
+ sohranit_koord_pryam(*nov_koord)
|
|
|
|
|
+
|
|
|
|
|
+def konets_peremesh(event):
|
|
|
|
|
+ global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
|
|
|
|
|
+
|
|
|
|
|
+ if peremesh_aktivno:
|
|
|
|
|
+ peremesh_aktivno = False
|
|
|
|
|
+ ugol_dlya_peremesh = None
|
|
|
|
|
+ nach_koord_peremesh = None
|
|
|
|
|
+ holst.config(cursor="")
|
|
|
|
|
+
|
|
|
|
|
+def sohranit_v_bazu():
|
|
|
|
|
+ if not tekush_put_k_file or not tekush_pryam:
|
|
|
|
|
+ messagebox.showwarning("Нет данных", "Нет изображения или прямоугольника для сохранения")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ koord = holst.coords(tekush_pryam)
|
|
|
|
|
+ if not koord or len(koord) < 4:
|
|
|
|
|
+ messagebox.showwarning("Ошибка", "Нет координат прямоугольника")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ if poz_izobr:
|
|
|
|
|
+ img_x, img_y = poz_izobr
|
|
|
|
|
+ x1 = int(koord[0] - img_x)
|
|
|
|
|
+ y1 = int(koord[1] - img_y)
|
|
|
|
|
+ x2 = int(koord[2] - img_x)
|
|
|
|
|
+ y2 = int(koord[3] - img_y)
|
|
|
|
|
+
|
|
|
|
|
+ rect_width = abs(x2 - x1)
|
|
|
|
|
+ rect_height = abs(y2 - y1)
|
|
|
|
|
+
|
|
|
|
|
+ img_width = pil_izobr_obj.width if pil_izobr_obj else 0
|
|
|
|
|
+ img_height = pil_izobr_obj.height if pil_izobr_obj else 0
|
|
|
|
|
+
|
|
|
|
|
+ cursor.execute('''
|
|
|
|
|
+ INSERT INTO annotations
|
|
|
|
|
+ (filename, image_width, image_height, x1, y1, x2, y2,
|
|
|
|
|
+ rectangle_width, rectangle_height, image_left, image_bottom,
|
|
|
|
|
+ image_top, image_right, created_at)
|
|
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
+ ''', (
|
|
|
|
|
+ os.path.basename(tekush_put_k_file),
|
|
|
|
|
+ img_width,
|
|
|
|
|
+ img_height,
|
|
|
|
|
+ x1,
|
|
|
|
|
+ y1,
|
|
|
|
|
+ x2,
|
|
|
|
|
+ y2,
|
|
|
|
|
+ rect_width,
|
|
|
|
|
+ rect_height,
|
|
|
|
|
+ int(levo_metka.cget("text").split(": ")[1]) if "Left: " in levo_metka.cget("text") else 0,
|
|
|
|
|
+ int(niz_metka.cget("text").split(": ")[1]) if "Bottom: " in niz_metka.cget("text") else 0,
|
|
|
|
|
+ int(verh_metka.cget("text").split(": ")[1]) if "Top: " in verh_metka.cget("text") else 0,
|
|
|
|
|
+ int(pravo_metka.cget("text").split(": ")[1]) if "Right: " in pravo_metka.cget("text") else 0,
|
|
|
|
|
+ datetime.datetime.now()
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+ messagebox.showinfo("Успех", "Данные успешно сохранены!")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+def zagruzit_iz_istorii(id_zapisi):
|
|
|
|
|
+ """Загрузить изображение и прямоугольник из записи истории"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ cursor.execute("SELECT * FROM annotations WHERE id = ?", (id_zapisi,))
|
|
|
|
|
+ record = cursor.fetchone()
|
|
|
|
|
+
|
|
|
|
|
+ if not record:
|
|
|
|
|
+ messagebox.showwarning("Ошибка", "Запись не найдена")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ id_num, filename, img_w, img_h, x1, y1, x2, y2, rect_w, rect_h, img_l, img_b, img_t, img_r, created_at = record
|
|
|
|
|
+
|
|
|
|
|
+ # Ищем файл изображения
|
|
|
|
|
+ file_found = False
|
|
|
|
|
+ possible_paths = [
|
|
|
|
|
+ os.path.join(os.path.dirname(tekush_put_k_file) if tekush_put_k_file else "", filename),
|
|
|
|
|
+ filename,
|
|
|
|
|
+ os.path.join("images", filename),
|
|
|
|
|
+ os.path.join(os.getcwd(), filename)
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ for path in possible_paths:
|
|
|
|
|
+ if os.path.exists(path):
|
|
|
|
|
+ # Загружаем изображение
|
|
|
|
|
+ zagruzit(path)
|
|
|
|
|
+
|
|
|
|
|
+ # Ждем пока изображение загрузится
|
|
|
|
|
+ root.update()
|
|
|
|
|
+
|
|
|
|
|
+ # Устанавливаем координаты изображения
|
|
|
|
|
+ levo_metka.config(text=f"Left: {img_l}")
|
|
|
|
|
+ niz_metka.config(text=f"Bottom: {img_b}")
|
|
|
|
|
+ verh_metka.config(text=f"Top: {img_t}")
|
|
|
|
|
+ pravo_metka.config(text=f"Right: {img_r}")
|
|
|
|
|
+
|
|
|
|
|
+ # Устанавливаем координаты прямоугольника
|
|
|
|
|
+ vvod_koord["X1"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["X1"].insert(0, str(x1))
|
|
|
|
|
+ vvod_koord["Y1"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["Y1"].insert(0, str(y1))
|
|
|
|
|
+ vvod_koord["X2"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["X2"].insert(0, str(x2))
|
|
|
|
|
+ vvod_koord["Y2"].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord["Y2"].insert(0, str(y2))
|
|
|
|
|
+
|
|
|
|
|
+ # Рисуем прямоугольник
|
|
|
|
|
+ narisovat_pryam()
|
|
|
|
|
+
|
|
|
|
|
+ file_found = True
|
|
|
|
|
+ messagebox.showinfo("Успех", f"Загружена запись ID: {id_num}\nФайл: {filename}")
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ if not file_found:
|
|
|
|
|
+ messagebox.showwarning("Файл не найден",
|
|
|
|
|
+ f"Не удалось найти файл: {filename}\n\n"
|
|
|
|
|
+ f"Пожалуйста, выберите файл вручную.")
|
|
|
|
|
+ # Открываем диалог выбора файла
|
|
|
|
|
+ put_k_file = filedialog.askopenfilename(
|
|
|
|
|
+ title=f"Найдите файл: {filename}",
|
|
|
|
|
+ filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")]
|
|
|
|
|
+ )
|
|
|
|
|
+ if put_k_file:
|
|
|
|
|
+ zagruzit(put_k_file)
|
|
|
|
|
+ # Повторно применяем параметры
|
|
|
|
|
+ zagruzit_iz_istorii(id_zapisi)
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ messagebox.showerror("Ошибка", f"Не удалось загрузить данные: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+def pokazat_istoriju():
|
|
|
|
|
+ try:
|
|
|
|
|
+ cursor.execute("SELECT * FROM annotations ORDER BY id DESC")
|
|
|
|
|
+ records = cursor.fetchall()
|
|
|
|
|
+
|
|
|
|
|
+ if not records:
|
|
|
|
|
+ messagebox.showinfo("История", "База данных пуста")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ history_window = tk.Toplevel(root)
|
|
|
|
|
+ history_window.title("История")
|
|
|
|
|
+ history_window.geometry("1000x550")
|
|
|
|
|
+ history_window.configure(bg=bg_color)
|
|
|
|
|
+
|
|
|
|
|
+ text_frame = tk.Frame(history_window, bg=bg_color)
|
|
|
|
|
+ text_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ text_widget = tk.Text(text_frame, bg='#1e1e1e', fg=text_color,
|
|
|
|
|
+ font=("Courier", 10), wrap='none')
|
|
|
|
|
+ scrollbar_y = tk.Scrollbar(text_frame, orient='vertical', command=text_widget.yview)
|
|
|
|
|
+ scrollbar_x = tk.Scrollbar(text_frame, orient='horizontal', command=text_widget.xview)
|
|
|
|
|
+ text_widget.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
|
|
|
|
|
+
|
|
|
|
|
+ text_widget.grid(row=0, column=0, sticky='nsew')
|
|
|
|
|
+ scrollbar_y.grid(row=0, column=1, sticky='ns')
|
|
|
|
|
+ scrollbar_x.grid(row=1, column=0, sticky='ew')
|
|
|
|
|
+
|
|
|
|
|
+ text_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
|
+ text_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
|
+
|
|
|
|
|
+ col_widths = {'id': 4, 'filename': 25, 'img_size': 20, 'rect_coords': 25, 'rect_size': 20, 'date': 20}
|
|
|
|
|
+
|
|
|
|
|
+ header_format = "{:>{id_width}} | {:<{filename_width}} | {:^{img_size_width}} | {:<{rect_coords_width}} | {:^{rect_size_width}} | {:<{date_width}} | {:^15}"
|
|
|
|
|
+
|
|
|
|
|
+ headers = header_format.format(
|
|
|
|
|
+ "ID", "Файл", "Размер изображения", "Прямоугольник", "Размер прямоуг.", "Дата создания", "Действия",
|
|
|
|
|
+ id_width=col_widths['id'],
|
|
|
|
|
+ filename_width=col_widths['filename'],
|
|
|
|
|
+ img_size_width=col_widths['img_size'],
|
|
|
|
|
+ rect_coords_width=col_widths['rect_coords'],
|
|
|
|
|
+ rect_size_width=col_widths['rect_size'],
|
|
|
|
|
+ date_width=col_widths['date']
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ text_widget.insert('end', headers + "\n")
|
|
|
|
|
+ text_widget.insert('end', "-" * len(headers) + "\n")
|
|
|
|
|
+
|
|
|
|
|
+ record_format = "{:>{id_width}} | {:<{filename_width}.{filename_width}} | {:^{img_size_width}} | {:<{rect_coords_width}} | {:^{rect_size_width}} | {:<{date_width}}"
|
|
|
|
|
+
|
|
|
|
|
+ for record in records:
|
|
|
|
|
+ id_num, filename, img_w, img_h, x1, y1, x2, y2, rect_w, rect_h, img_l, img_b, img_t, img_r, created_at = record
|
|
|
|
|
+ img_size = f"{img_w}x{img_h}"
|
|
|
|
|
+ rect_coords = f"({x1},{y1})-({x2},{y2})"
|
|
|
|
|
+ rect_size = f"{rect_w}x{rect_h}"
|
|
|
|
|
+ date_str = created_at[:19] if isinstance(created_at, str) else str(created_at)[:19]
|
|
|
|
|
+
|
|
|
|
|
+ line = record_format.format(
|
|
|
|
|
+ id_num, filename, img_size, rect_coords, rect_size, date_str,
|
|
|
|
|
+ id_width=col_widths['id'],
|
|
|
|
|
+ filename_width=col_widths['filename'],
|
|
|
|
|
+ img_size_width=col_widths['img_size'],
|
|
|
|
|
+ rect_coords_width=col_widths['rect_coords'],
|
|
|
|
|
+ rect_size_width=col_widths['rect_size'],
|
|
|
|
|
+ date_width=col_widths['date']
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ line_with_id = line + f" | ID:{id_num:>5}"
|
|
|
|
|
+ text_widget.insert('end', line_with_id + "\n")
|
|
|
|
|
+
|
|
|
|
|
+ text_widget.config(state='disabled')
|
|
|
|
|
+
|
|
|
|
|
+ button_frame = tk.Frame(history_window, bg=bg_color)
|
|
|
|
|
+ button_frame.pack(pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ id_frame = tk.Frame(button_frame, bg=bg_color)
|
|
|
|
|
+ id_frame.pack(pady=5)
|
|
|
|
|
+
|
|
|
|
|
+ tk.Label(id_frame, text="ID записи:", bg=bg_color, fg=text_color).pack(side='left', padx=5)
|
|
|
|
|
+ entry_id = tk.Entry(id_frame, width=10, font=("Arial", 10))
|
|
|
|
|
+ entry_id.pack(side='left', padx=5)
|
|
|
|
|
+
|
|
|
|
|
+ def zagruzit_po_id():
|
|
|
|
|
+ try:
|
|
|
|
|
+ id_zapisi = int(entry_id.get())
|
|
|
|
|
+ zagruzit_iz_istorii(id_zapisi)
|
|
|
|
|
+ history_window.destroy() # Закрываем окно истории
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ messagebox.showwarning("Ошибка", "Введите корректный ID записи")
|
|
|
|
|
+
|
|
|
|
|
+ btn_open = tk.Button(button_frame, text="Загрузить выбранную запись",
|
|
|
|
|
+ command=zagruzit_po_id,
|
|
|
|
|
+ bg=accent_color, fg=text_color,
|
|
|
|
|
+ font=("Arial", 10), relief='flat', padx=10, pady=5)
|
|
|
|
|
+ btn_open.pack(pady=5)
|
|
|
|
|
+
|
|
|
|
|
+ btn_close = tk.Button(button_frame, text="Закрыть",
|
|
|
|
|
+ command=history_window.destroy,
|
|
|
|
|
+ bg='#d94a4a', fg=text_color,
|
|
|
|
|
+ font=("Arial", 10), relief='flat', padx=10, pady=5)
|
|
|
|
|
+ btn_close.pack(pady=5)
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ messagebox.showerror("Ошибка", f"Не удалось загрузить историю: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ramka_knopok = tk.Frame(ramka_pryam, bg=frame_color)
|
|
|
|
|
+ramka_knopok.pack(pady=10)
|
|
|
|
|
+knopka_ris = tk.Button(ramka_knopok, text="Нарисовать",
|
|
|
|
|
+command=narisovat_pryam, bg=accent_color, fg=text_color,
|
|
|
|
|
+font=("Arial", 9), relief='flat', padx=5, pady=3)
|
|
|
|
|
+knopka_ris.pack(side='left', padx=5)
|
|
|
|
|
+knopka_ochist = tk.Button(ramka_knopok, text="Очистить",
|
|
|
|
|
+command=ochistit_pryam, bg='#d94a4a', fg=text_color,
|
|
|
|
|
+font=("Arial", 9), relief='flat', padx=5, pady=3)
|
|
|
|
|
+knopka_ochist.pack(side='left', padx=5)
|
|
|
|
|
+
|
|
|
|
|
+razdelitel2 = tk.Frame(panel, height=2, bg=accent_color)
|
|
|
|
|
+razdelitel2.pack(fill='x', padx=20, pady=20)
|
|
|
|
|
+
|
|
|
|
|
+def vibor():
|
|
|
|
|
+ global tekush_put_k_file, pil_izobr_obj
|
|
|
|
|
+ put_k_file = filedialog.askopenfilename(
|
|
|
|
|
+ title="Выберите изображение",
|
|
|
|
|
+ filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")]
|
|
|
|
|
+ )
|
|
|
|
|
+ if put_k_file:
|
|
|
|
|
+ tekush_put_k_file = put_k_file
|
|
|
|
|
+ zagruzit(put_k_file)
|
|
|
|
|
+
|
|
|
|
|
+knopka_vibor = tk.Button(panel, text="Выбрать изображение",
|
|
|
|
|
+command=vibor, bg=accent_color, fg=text_color,
|
|
|
|
|
+font=("Arial", 10), relief='flat', padx=10, pady=5)
|
|
|
|
|
+knopka_vibor.pack(pady=10)
|
|
|
|
|
+
|
|
|
|
|
+knopka_sohranit = tk.Button(panel, text="Сохранить в базу данных",
|
|
|
|
|
+command=sohranit_v_bazu, bg='#2ecc71', fg=text_color,
|
|
|
|
|
+font=("Arial", 10, "bold"), relief='flat', padx=10, pady=5)
|
|
|
|
|
+knopka_sohranit.pack(pady=10)
|
|
|
|
|
+
|
|
|
|
|
+knopka_istorija = tk.Button(panel, text="Показать историю",
|
|
|
|
|
+command=pokazat_istoriju, bg='#9b59b6', fg=text_color,
|
|
|
|
|
+font=("Arial", 10), relief='flat', padx=10, pady=5)
|
|
|
|
|
+knopka_istorija.pack(pady=10)
|
|
|
|
|
+
|
|
|
|
|
+holst = tk.Canvas(root, bg='#1e1e1e', width=600, height=600, highlightthickness=0)
|
|
|
|
|
+holst.pack(side='left', fill='both', expand=True)
|
|
|
|
|
+
|
|
|
|
|
+holst.bind("<Button-1>", nachalo_peremesh)
|
|
|
|
|
+holst.bind("<B1-Motion>", peremeshchenie)
|
|
|
|
|
+holst.bind("<ButtonRelease-1>", konets_peremesh)
|
|
|
|
|
+
|
|
|
|
|
+def zagruzit(put_k_file):
|
|
|
|
|
+ global tekush_izobr, poz_izobr, tekush_pryam, pil_izobr_obj
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ pil_izobr_obj = Image.open(put_k_file)
|
|
|
|
|
+
|
|
|
|
|
+ holst.delete("all")
|
|
|
|
|
+ tekush_pryam = None
|
|
|
|
|
+
|
|
|
|
|
+ shir_holst = holst.winfo_width()
|
|
|
|
|
+ vys_holst = holst.winfo_height()
|
|
|
|
|
+
|
|
|
|
|
+ img_width = pil_izobr_obj.width
|
|
|
|
|
+ img_height = pil_izobr_obj.height
|
|
|
|
|
+
|
|
|
|
|
+ scale_w = shir_holst / img_width
|
|
|
|
|
+ scale_h = vys_holst / img_height
|
|
|
|
|
+ scale = min(scale_w, scale_h, 1.0)
|
|
|
|
|
+
|
|
|
|
|
+ new_width = int(img_width * scale)
|
|
|
|
|
+ new_height = int(img_height * scale)
|
|
|
|
|
+
|
|
|
|
|
+ pil_izobr_obj = pil_izobr_obj.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
|
|
|
|
+ tekush_izobr = ImageTk.PhotoImage(pil_izobr_obj)
|
|
|
|
|
+
|
|
|
|
|
+ x_centr = (shir_holst - new_width) // 2
|
|
|
|
|
+ y_centr = (vys_holst - new_height) // 2
|
|
|
|
|
+
|
|
|
|
|
+ id_izobr = holst.create_image(x_centr, y_centr, anchor='nw', image=tekush_izobr)
|
|
|
|
|
+ koord_izobr = holst.bbox(id_izobr)
|
|
|
|
|
+
|
|
|
|
|
+ poz_izobr = (koord_izobr[0], koord_izobr[1])
|
|
|
|
|
+
|
|
|
|
|
+ levo_metka.config(text=f"Left: {koord_izobr[0]}")
|
|
|
|
|
+ niz_metka.config(text=f"Bottom: {koord_izobr[1]}")
|
|
|
|
|
+ verh_metka.config(text=f"Top: {koord_izobr[2]}")
|
|
|
|
|
+ pravo_metka.config(text=f"Right: {koord_izobr[3]}")
|
|
|
|
|
+
|
|
|
|
|
+ for i, metka in enumerate(metki_koord):
|
|
|
|
|
+ vvod_koord[metka.replace(":", "")].delete(0, tk.END)
|
|
|
|
|
+ vvod_koord[metka.replace(":", "")].insert(0, znach_po_umolch[i])
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ holst.delete("all")
|
|
|
|
|
+ holst.create_text(
|
|
|
|
|
+ 300, 300, text="Ошибка загрузки изображения",
|
|
|
|
|
+ fill=text_color, font=("Arial", 12), anchor='center'
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+def obnovit(event=None):
|
|
|
|
|
+ if tekush_izobr and pil_izobr_obj:
|
|
|
|
|
+ holst.delete("all")
|
|
|
|
|
+ tekush_pryam = None
|
|
|
|
|
+
|
|
|
|
|
+ shir_holst = holst.winfo_width()
|
|
|
|
|
+ vys_holst = holst.winfo_height()
|
|
|
|
|
+
|
|
|
|
|
+ if shir_holst <= 1 or vys_holst <= 1:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ img_width = pil_izobr_obj.width
|
|
|
|
|
+ img_height = pil_izobr_obj.height
|
|
|
|
|
+
|
|
|
|
|
+ scale_w = shir_holst / img_width
|
|
|
|
|
+ scale_h = vys_holst / img_height
|
|
|
|
|
+ scale = min(scale_w, scale_h, 1.0)
|
|
|
|
|
+
|
|
|
|
|
+ new_width = int(img_width * scale)
|
|
|
|
|
+ new_height = int(img_height * scale)
|
|
|
|
|
+
|
|
|
|
|
+ resized_img = pil_izobr_obj.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
|
|
|
|
+ tekush_izobr = ImageTk.PhotoImage(resized_img)
|
|
|
|
|
+
|
|
|
|
|
+ x_centr = (shir_holst - new_width) // 2
|
|
|
|
|
+ y_centr = (vys_holst - new_height) // 2
|
|
|
|
|
+
|
|
|
|
|
+ id_izobr = holst.create_image(x_centr, y_centr, anchor='nw', image=tekush_izobr)
|
|
|
|
|
+ koord_izobr = holst.bbox(id_izobr)
|
|
|
|
|
+
|
|
|
|
|
+ poz_izobr = (koord_izobr[0], koord_izobr[1])
|
|
|
|
|
+
|
|
|
|
|
+ levo_metka.config(text=f"Left: {koord_izobr[0]}")
|
|
|
|
|
+ niz_metka.config(text=f"Bottom: {koord_izobr[1]}")
|
|
|
|
|
+ verh_metka.config(text=f"Top: {koord_izobr[2]}")
|
|
|
|
|
+ pravo_metka.config(text=f"Right: {koord_izobr[3]}")
|
|
|
|
|
+
|
|
|
|
|
+holst.bind("<Configure>", obnovit)
|
|
|
|
|
+
|
|
|
|
|
+holst.create_text(
|
|
|
|
|
+ 300, 300,
|
|
|
|
|
+ text="Нажмите 'Выбрать изображение'\nчтобы загрузить картинку",
|
|
|
|
|
+ fill=text_color, font=("Arial", 12), anchor='center', justify='center'
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+def zakryt_bazu():
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ root.destroy()
|
|
|
|
|
+
|
|
|
|
|
+root.protocol("WM_DELETE_WINDOW", zakryt_bazu)
|
|
|
|
|
+
|
|
|
|
|
+root.mainloop()
|