1
0
u23-27eroshko 1 неделя назад
Родитель
Сommit
5bef0aab08

BIN
ОАиП/2025-26/36гр/1 сем/Ерошко/2222.png


+ 642 - 0
ОАиП/2025-26/36гр/1 сем/Ерошко/Image Annotation Tool.py

@@ -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()

BIN
ОАиП/2025-26/36гр/1 сем/Ерошко/pngtree-picture-of-a-blue-bird-on-a-black-background-image_2937385.jpg


+ 108 - 0
ОАиП/2025-26/36гр/1 сем/Ерошко/readme.md

@@ -0,0 +1,108 @@
+# Image Annotation Tool
+
+## Описание
+
+Image Annotation Tool — это приложение для аннотирования изображений прямоугольными ограничивающими рамками с сохранением аннотаций в базу данных SQLite.
+
+## Возможности
+
+- **Загрузка изображений**: Поддержка форматов PNG, JPG, JPEG, GIF, BMP
+- **Аннотирование**: Рисование и редактирование прямоугольных ограничивающих рамок
+- **Интерактивное редактирование**: Изменение размеров рамки перетаскиванием угловых маркеров
+- **Сохранение в БД**: Автоматическое сохранение аннотаций с метаданными в SQLite
+- **Просмотр истории**: Табличный просмотр всех сохраненных аннотаций
+- **Адаптивный интерфейс**: Темная тема, поддержка изменения размеров холста
+
+## Установка
+
+### Предварительные требования
+
+- Python 3.6 или выше
+- Менеджер пакетов pip
+
+### Установка зависимостей
+
+```bash
+pip install pillow
+```
+
+**Примечание**: Библиотека tkinter входит в стандартную библиотеку Python на большинстве систем.
+
+## Использование
+### Рабочий процесс
+
+1. **Запустите приложение**
+2. **Нажмите "Выбрать изображение"** для загрузки изображения
+3. **Введите координаты** или используйте значения по умолчанию для прямоугольника
+4. **Нажмите "Нарисовать"** для создания рамки на изображении
+5. **Редактируйте рамку**, перетаскивая угловые маркеры
+6. **Нажмите "Сохранить в базу данных"** для сохранения аннотации
+
+### Интерфейс
+
+#### Левая панель (холст)
+- Отображение загруженного изображения
+- Интерактивное рисование и редактирование прямоугольников
+- Возможность перетаскивания углов для изменения размера
+
+#### Правая панель (управление)
+- **Координаты изображения**: Положение изображения на холсте (Left, Bottom, Top, Right)
+- **Координаты прямоугольника**: Поля ввода для точного задания координат (X1, Y1, X2, Y2)
+- **Кнопки управления**:
+  - "Нарисовать" — создать прямоугольник по введенным координатам
+  - "Очистить" — удалить текущий прямоугольник
+  - "Выбрать изображение" — загрузить новое изображение
+  - "Сохранить в базу данных" — сохранить текущую аннотацию
+  - "Показать историю" — просмотр всех сохраненных аннотаций
+
+### Функции базы данных
+
+При сохранении аннотации в базу данных записывается следующая информация:
+- Имя файла изображения
+- Размеры изображения
+- Координаты прямоугольника (относительно изображения)
+- Размеры прямоугольника
+- Координаты изображения на холсте
+- Дата и время создания записи
+
+## Структура базы данных
+
+Таблица `annotations` содержит следующие поля:
+- `id` — уникальный идентификатор
+- `filename` — имя файла изображения
+- `image_width`, `image_height` — размеры изображения
+- `x1`, `y1`, `x2`, `y2` — координаты прямоугольника
+- `rectangle_width`, `rectangle_height` — размеры прямоугольника
+- `image_left`, `image_bottom`, `image_top`, `image_right` — положение изображения
+- `created_at` — временная метка создания
+
+## Горячие клавиши и управление
+
+- **ЛКМ на угловом маркере** + **перетаскивание** — изменение размера прямоугольника
+- **Изменение размеров окна** — автоматическое масштабирование изображения
+- **Поля ввода координат** — поддержка ручного ввода значений
+
+## Особенности
+
+- Автоматическое центрирование изображения на холсте
+- Ограничение минимального размера прямоугольника (10x10 пикселей)
+- Проверка вводимых данных
+- Защита от потери данных при закрытии приложения
+- Читаемый табличный вывод истории аннотаций
+
+
+## Пример использования
+
+1. Запустите приложение
+2. Нажмите "Выбрать изображение" и выберите файл
+3. В полях X1, Y1, X2, Y2 введите координаты или оставьте значения по умолчанию
+4. Нажмите "Нарисовать" для отображения прямоугольника
+5. При необходимости отрегулируйте размер перетаскиванием углов
+6. Нажмите "Сохранить в базу данных"
+7. Для просмотра всех сохраненных записей нажмите "Показать историю"
+
+
+
+
+
+**Примечание**: При первом запуске приложения автоматически создается файл базы данных `sqlite.db` в текущей директории.

BIN
ОАиП/2025-26/36гр/1 сем/Ерошко/sqlite.db