Mikroskop mit Tkinter-GUi

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Samstag 31. März 2018, 06:27

Hallo Sirius3,

Zeile 64-68: size geht - bei zoom_size gibt es eine Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "microscope.py", line 322, in <module>
    main()
  File "microscope.py", line 311, in main
    microscope_ui.run()
  File "microscope.py", line 220, in run
    tk_image = self.microscope.get_image()
  File "microscope.py", line 79, in get_image
    self.interpolation_method])
TypeError: integer argument expected, got float
>Exit code: 1
mit print gibt es diese Ausgabe --> (640.0, 480.0

Zeile 85/90: habe jetz noch cycle eingebastelt und verhindere so eine Stopiteration. Macht es bestimmt nicht besser, doch warum sollte man es nicht so machen ?

Zeile 107:Mit deinem Datumsformat hat es nicht so geklappt, doch mir waren eigntlich nur die Eindeutigkeit wichtig (Ms). Die Datumsangab/Zeit ist eher zweitranging.

Zeile 118: Die Funktion macht schon was, doch soll sie erst wieder eine Funktion haben, wenn der vorhergehende Start abgelaufen ist. Ich könnte auch den Button blockieren - ist aber mehr Aufwand.

Zeile 137: Das tut schon was es soll und es ist auch ok die Anzahl 10 zu unterschreiten, doch < 1 ist dumm. Hier verhindere ich auch das Starten vor dem Ablauf des vorgehenden Starten.

Zeile 121: In meinem Fall ist es doch so gewollt und ich wollte nicht zwei Buttonlisten ?!

Zeile 219-249: Wie soll ich sonst den Text der Buttons ändern ?

Zeile 258: Dies sollen eigentlich nur die Bedienung vereinfachen:

Code: Alles auswählen

        if self.text_on_off == False:
            self.text_on_off_off()
Die Funktionen sind schon eigenständig, doch bei Veränderungen soll der Displaytext angeschaltet werden.

Das Konstrukt mit if state und Fehlermeldungen stammt von einer anderen Version, die auf eine Fehlfunktion von OpenCV reagiert. State gibt eigentlich True/False zurück, doch beim Abziehen des Steckers (im Betrieb) gibt es kein False zurück. Sollte der Fehler behoben werden, dann würde es funktionieren. Ist noch von einem Beitrag mit BlackJack - kann es gerne raussuchen. ich kann natürlich auch völlig falsch liegen, denn ein Hilfsprogrammierer ist da nicht so in der Materie drin.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Samstag 31. März 2018, 07:21

Hallo,

hier ist die nächste Version:

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = -1


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                             "LINEAR" : cv2.INTER_LINEAR,
                             "AREA" : cv2.INTER_AREA,
                             "CUBIC" : cv2.INTER_CUBIC,
                             "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods = self.INTERPOLATION_METHODS
        self.interpolation_methods_keys = cycle(
            self.interpolation_methods.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = self.cam.get(self.PROPID_WIDTH)
        self.cam_height = self.zoom_height = self.cam.get(self.PROPID_HEIGHT)
        

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return int(self.zoom_width), int(self.zoom_height)
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()

        if state:        
            inter_frame = cv2.resize(frame, self.zoom_size, 
            interpolation = self.interpolation_methods[
            self.interpolation_method])
            
            image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
                "raw", "BGR")
            if self.take_series:
                if self.series_time_counter.next() == \
                    self.series_time_interval:
                    counter = self.series_counter.next()
                    if counter != self.number_of_imgs:
                        img_name = "{0}{1}".format(counter +1, self.img_format)
                        self.get_image().save(os.path.join(self.series_img_path, 
                            self.series_dir, img_name))
                    else:
                        self.take_series = False
            if self.recording:
                self.video_writer.write(inter_frame)
                    
            return image
        else:
            return state

    def recording_start_stop(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(
                datetime.datetime.utcnow())
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name), cv2.cv.CV_FOURCC(* VIDEO_CODEC),
                24, self.zoom_size)
                
    def take_series_picture(self):
        if self.take_series == False:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = cycle(x for x in xrange(
                self.series_time_interval +1))
            self.series_counter = cycle(x for x in xrange(
                self.number_of_imgs +1))
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        self.get_image().save(os.path.join(self.image_path, name))
        
    def series_up_down(self, step):
        if self.take_series == False: 
            if self.number_of_imgs > 1 or step > 0:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.take_series == False:
            if self.series_time_interval > 1 or step > 0:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.zoom_width / self.zoom_height)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 20
    REC_ON = 3
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on_off = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.start_stop, None),
             ("Z+", 2, self.microscope.zoom_image, zoom_step),
             ("Z-", 2, self.microscope.zoom_image, -zoom_step),
             ("Z+/-", 2, self.microscope.reset_zoom, None),
             ("[1]", 2, self.take_picture, None),
             ("REC", 2, self.recording_film, None),
             ("INTPOL", 5, self.next_interpolation_method, None),
             ("[S]]]", 2, self.take_series_pictures, None),
             ("S+", 2, self.series_up_down, 1),
             ("S-", 2, self.series_up_down, -1),
             ("T+", 2, self.time_interval_up_down, 1),
             ("T-", 2, self.time_interval_up_down, -1),
             ("ON", 2, self.text_on_off_off, None),
             ("C", 2, self.change_text_colour, None))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold")
            button.grid(column=column, row=0)
            self.buttons.append(button)
            if var:
                 button.config(command=partial(command, var))
            else:
                 button.config(command=command)

    def start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        tk_image = self.microscope.get_image()
        if tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == 0:
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on_off:
                self.buttons[12].config(text = "OFF")
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, 
                    tag="txt")
                self.canvas.create_text(30, 10, font="Arial 11 bold", 
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs,                             
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size), anchor="nw", 
                    fill = self.text_colour, tag = "txt")
            else: 
                self.buttons[12].config(text = "ON")
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        if self.text_on_off == False:
            self.text_on_off_off()
        self.text_colour = self.text_colour_index.next()
        
    def text_on_off_off(self):
        if self.text_on_off:
            self.text_on_off = False
        else:
            self.text_on_off = True
                
    def raise_cam_id_error(self):
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        if self.text_on_off == False:
            self.text_on_off_off()
        self.microscope.next_interpolation_method()
        
    def recording_film(self):
        if self.recording:
            self.microscope.recording_start_stop()
            self.recording = False
        else:
            self.microscope.recording_start_stop()
            self.recording = True
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(DEFAULT_CAM_ID) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß
Sirius3
User
Beiträge: 9387
Registriert: Sonntag 21. Oktober 2012, 17:20

Samstag 31. März 2018, 10:04

kaytec hat geschrieben:Zeile 64-68: size geht - bei zoom_size gibt es eine Fehlermeldung
Dann kommt zoom_width/zoom_height noch von woanders her, denn in der Methode `zoom_image` wird explizit nach `int` konvertiert.
kaytec hat geschrieben:Zeile 85/90: habe jetz noch cycle eingebastelt und verhindere so eine Stopiteration. Macht es bestimmt nicht besser, doch warum sollte man es nicht so machen ?
Weil es viel zu umständlich ist, und keiner es so erwarten würde. Ich habe selbst erstmal 5 Minuten gebraucht, um zu verstehen, was da gemacht wird.
Du schreibst etwas, das dem entspricht:

Code: Alles auswählen

number_of_imgs = 5
series_counter = iter(range(number_of_imgs+1))

while True:
    counter = next(series_counter)
    if counter == number_of_imgs:
        break
    print("Bild", counter)
Mach einen Iterator und prüfe ob der Iterator einen bestimmten Wert erreicht hat. Dazu mißbrauche ich aber einen Iterator, denn der weiß selbst, wann er fertig ist. Besser ist also:

Code: Alles auswählen

number_of_imgs = 5
series_counter = iter(range(number_of_imgs))

while True:
    try:
        counter = next(series_counter)
    except StopIteration:
        break
    print("Bild", counter)
Normalerweise benutzt man einfach einen Zähler:

Code: Alles auswählen

number_of_imgs = 5
series_counter = 0

while True:
    if series_counter >= number_of_imgs:
        break
    series_counter += 1
    print("Bild", series_counter)
kaytec hat geschrieben:Zeile 107:Mit deinem Datumsformat hat es nicht so geklappt,
Was soll denn an einem Datumsformat nicht klappen, wenn es Dir doch sowieso nicht auf die genaue Form ankommt?

Code: Alles auswählen

name = "{0:%Y%m%d_%H%M%S.%f}.avi".format(datetime.datetime.utcnow())
In Zeile 118 geht es mir nur darum, dass eine Funktion, die nichts macht den Nutzer verwirrt. Der Vorschlag mit dem Button blockieren ist gut, da hat der Nutzer wenigstens ein Visuelles Feedback.

Und Zeile 137 (jetzt 132) ist immer noch falsch. Wenn self.number_of_imgs 4 ist und step -5 dann ist das Ergebnis -1 ohne dass es Deine if-Abfrage verhindert hätte.

Hier das Beispiel mit dem `var`:

Code: Alles auswählen

        self.buttons = list()
        for column, (text, width, command, var) in enumerate(
            [("||", 2, self.start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ())),
             ("[1]", 2, self.take_picture, ()),
             ("REC", 2, self.recording_film, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("[S]]]", 2, self.take_series_pictures, ()),
             ("S+", 2, self.series_up_down, (1,)),
             ("S-", 2, self.series_up_down, (-1,)),
             ("T+", 2, self.time_interval_up_down, (1,)),
             ("T-", 2, self.time_interval_up_down, (-1,)),
             ("ON", 2, self.text_on_off_off, ()),
             ("C", 2, self.change_text_colour, ())]):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)
Zeile 219/249: es geht nicht um das wie, sondern wo im Code. An der Stelle gehört das einfach nicht hin. Und wenn man vier mal die selben zwei Zeilen schreibt, ist das ein Grund, das in eine Funktion auszulagern, oder `text_on_off` einen weiteren Parameter zu spendieren:

Code: Alles auswählen

    def set_text_on_off(self, force_on=False):
        self.text_on_off = force_on or not self.text_on_off
        self.buttons[12].config(text="ON" if self.text_on_off else "OFF")

    def change_text_colour(self):      
        self.set_text_on_off(True)
        self.text_colour = self.text_colour_index.next()
Dass die Fehlerbehandlung in `get_image` schwieriger ist, als man sich wünscht, macht doch nur deutlicher, dass man eine ordentliche Fehlerbehandlung braucht, und die besteht eben darin, dass `get_image` nicht einmal ein Image und ein andermal False liefert, sondern immer ein Image und im Fehlerfall eine Exception wirft. So macht man Fehlerbehandlung, und nicht anders.
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Sonntag 1. April 2018, 06:54

Hallo Sirius3,

danke für deine ausführliche Kritik.

Die Kamera liefert die Werte und so geht es jetzt:

Code: Alles auswählen

self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
Ich habe versucht mit -->YYYY... - Diese Formatierung gibt es so nicht und ich wollte nicht rumprobieren, da es ja für mich keiner Veränderung bedarf. Du wolltest die Reihenfolge aufzeigen und nicht die Formatierung für "datetime" zeigen.

Zeile 118: Die GUI sollte dem Nutzer das aktuelle Verhalten des Modules anzeigen ?!

137(132): Ich wollte ja nie größere Schritte machen und so fand ich es ok --> Da natürich auch größere Schritte möglich sind, sollte es berücksichtigt werden ?!

Das mit "var" sieht jetzt auch schön aus und ist so etwas wie "*kwargs u. *args" bei Parameterübergabe an Funktionen. Das hatte ich nie so richtig verstanden.

tex_on_off: Wiederholungen sollte man nicht machen und zuzammenfassen. Das bastel ich in das Sript rein.

state / get_image: Dies war halt drin, da der Fehler bei OpenCV evt. behoben wird. Beim Abziehen der Kamera wird kein Fehler geworfen,
sondern in der Ausgabe(IDE/Terminal) wird "VIDIOC_DQBUF: No such device" ausgegeben. Das Programm läuft weiter und man hat ein Standbild des letzten Bildes. Man könnte auf Veränderungen des Bildinhaltes mit "np.array_equal" reagieren und einen Fehler auslösen. Das hatte ich schon mal mit Hilfe des Forums gemacht. Hatte ich vergessen :-)

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Sonntag 1. April 2018, 09:27

Hallo,

Code: Alles auswählen

    def get_image(self):
        state, frame = self.cam.read()
        
        if np.array_equal(self.last_frame, frame):
            raise RuntimeError("cam disconnected")
            
        self.last_frame = frame
So wird ein "RuntimeError" ausgelöst, wenn die Kamera im Betrieb abgezogen wird.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Montag 2. April 2018, 00:26

Hallo,

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle
import numpy as np

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                             "LINEAR" : cv2.INTER_LINEAR,
                             "AREA" : cv2.INTER_AREA,
                             "CUBIC" : cv2.INTER_CUBIC,
                             "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.last_frame = None
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods = self.INTERPOLATION_METHODS
        self.interpolation_methods_keys = cycle(
            self.interpolation_methods.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
        self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return self.zoom_width, self.zoom_height
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
        
        if np.array_equal(self.last_frame, frame):
            self.release()
            state = False
            
        self.last_frame = frame
        
        if state:        
            inter_frame = cv2.resize(frame, self.zoom_size, 
            interpolation = self.interpolation_methods[
            self.interpolation_method])
            
            image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
                "raw", "BGR")
            if self.take_series:
                self.series_time_counter += 1
                if self.series_time_counter == self.series_time_interval:
                    self.series_counter += 1
                    self.series_time_counter = 0
                    if self.series_counter != self.number_of_imgs:
                        img_name = "{0}{1}".format(self.series_counter +1, 
                            self.img_format)
                        self.get_image().save(os.path.join(
                            self.series_img_path, self.series_dir, img_name))
                    else:
                        self.take_series = False
            if self.recording:
                self.video_writer.write(inter_frame)
                    
            return image
        else:
            return state

    def recording_start_stop(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(
                datetime.datetime.utcnow())
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name), cv2.cv.CV_FOURCC(* VIDEO_CODEC),
                24, self.zoom_size)
                
    def take_series_picture(self):
        if self.take_series == False:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = 0
            self.series_counter = 0
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        self.get_image().save(os.path.join(self.image_path, name))
        
    def series_up_down(self, step):
        if self.take_series == False: 
            if self.number_of_imgs > step *-1 or step > 0:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.take_series == False:
            if self.series_time_interval > step *-1 or step > 0:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        width = int(self.zoom_width + step * self.zoom_width / self.zoom_height)
        height = int(self.zoom_height + step)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 10
    REC_ON = 5
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.capture_start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ()),
             ("[1]", 2, self.take_picture, ()),
             ("REC", 2, self.recording_film, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("[S]]]", 2, self.take_series_pictures, ()),
             ("S+", 2, self.series_up_down, (5,)),
             ("S-", 2, self.series_up_down, (-5,)),
             ("T+", 2, self.time_interval_up_down, (5,)),
             ("T-", 2, self.time_interval_up_down, (-5,)),
             ("ON", 2, self.text_on_off, ()),
             ("C", 2, self.change_text_colour, ()))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)

    def capture_start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        tk_image = self.microscope.get_image()
        if tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == 0:
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on:
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, 
                    tag="txt")
                self.canvas.create_text(30, 10, font="Arial 11 bold", 
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs,                             
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size), anchor="nw", 
                    fill = self.text_colour, tag = "txt")
            
            for button in (1, 2, 3, 7, 8, 9, 10, 11):
                if self.microscope.take_series:
                    self.buttons[button].config(state = tk.DISABLED)
                else:
                    self.buttons[button].config(state = tk.NORMAL)
                
                    
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        self.text_on_off(True)
        self.text_colour = self.text_colour_index.next()
        
    def text_on_off(self, state = False):
        if state or not self.text_on:
            self.text_on = True
        else:
            self.text_on = state
            
        self.buttons[12].config(text="ON" if self.text_on else "OFF")
        
    def raise_cam_id_error(self):
        self.canvas.delete("img", "rec", "txt")
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        self.text_on_off(True)
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        self.text_on_off(True)
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        self.text_on_off(True)
        self.microscope.next_interpolation_method()
        
    def recording_film(self):
        if self.recording:
            self.microscope.recording_start_stop()
            self.recording = False
        else:
            self.microscope.recording_start_stop()
            self.recording = True
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(series_time_interval=5) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()

Gruß Frank
Benutzeravatar
snafu
User
Beiträge: 5741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Montag 2. April 2018, 08:29

Willst du nicht langsam ein Repo anlegen? Das wäre übersichtlicher. Du könntest z.B. was auf GitHub Gist hochladen.
Sirius3
User
Beiträge: 9387
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 2. April 2018, 08:48

Zeile 49: warum `INTERPOLATION_METHODS` in `interpolation_methods` umbenennen?
Zeile 110: das setzen des Namens ist nur im else-Teil nötig.
Zeile 122: auf False prüft man nicht explizit sondern mit not
Zeile 138: »step * -1« ist das selbe wie »-step«, die zweite Bedingung ist in der ersten schon enthalten.
Zeile 147: durch mehrfaches Zoomen wird »width« durch Rundungsfehler immer ungenauer. Berechne »width« über die Aspect-Ratio immer aus »height«.
Zeile 246: solche bedingten Parameter macht man entweder direkt mit if-else oder mit Variablen:

Code: Alles auswählen

            state = tk.DISABLED if self.microscope.take_series else tk.NORMAL
            for button in (1, 2, 3, 7, 8, 9, 10, 11):
                self.buttons[button].config(state=state)
Zeile 267: das ist unschön, einmal den expliziten Wert und einmal eine Variable zu nehmen. Was gefällt dir an

Code: Alles auswählen

self.text_on = state or not self.text_on
nicht?
Zeile 291: zu viel Wiederholungen in den beiden Blöcken. Geht auch ohne if.

Und jetzt nochmal zum einzigen wirklichen Fehler: »get_image« sollte nur einen Typ an Rückgabewert haben, nicht ZWEI. Wenn ein Bild nicht genommen werden kann, sollte eine Exception geworfen werden.

Code: Alles auswählen

    def get_image(self):
        state, frame = self.cam.read()
        
        if state and np.array_equal(self.last_frame, frame):
            self.release()
            state = False
        if not state:
            raise RuntimeError("could not read image")

        self.last_frame = frame
        inter_frame = cv2.resize(frame, self.zoom_size,
        interpolation = self.interpolation_methods[
            self.interpolation_method])
        
        image = Image.frombytes("RGB",  self.zoom_size, inter_frame,
            "raw", "BGR")
        if self.take_series:
            self.series_time_counter += 1
            if self.series_time_counter > self.series_time_interval:
                self.series_counter += 1
                self.series_time_counter = 0
                if self.series_counter <= self.number_of_imgs:
                    img_name = "{0}{1}".format(self.series_counter,
                        self.img_format)
                    self.get_image().save(os.path.join(
                        self.series_img_path, self.series_dir, img_name))
                else:
                    self.take_series = False
        if self.recording:
            self.video_writer.write(inter_frame)
        return image
Ein paar Zählfehler waren auch noch drin.
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Dienstag 3. April 2018, 18:34

Hallo,

habe aus dem Modul noch Pil rausgenommen - macht das irgendwie Sinn ? Es gibt bei OpenCV irgendwo auch die Möglichkeit des Arrayvergleiches, doch kann ich irgendwie nicht finden ?

Das habe ich mal so gemacht - so ganz verstanden habe ich es nicht.

Code: Alles auswählen

    def recording_film(self):
        self.recording = False or not self.recording
        self.microscope.recording_start_stop()

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle
import numpy as np

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                             "LINEAR" : cv2.INTER_LINEAR,
                             "AREA" : cv2.INTER_AREA,
                             "CUBIC" : cv2.INTER_CUBIC,
                             "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.inter_frame = None
        self.last_frame = None
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods_keys = cycle(
            self.INTERPOLATION_METHODS.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
        self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return self.zoom_width, self.zoom_height
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
       
        if state and np.array_equal(self.last_frame, frame):
            self.release()
            state = False
        if not state:
            raise RuntimeError("could not read image")
 
        self.last_frame = frame
        self.inter_frame = cv2.resize(frame, self.zoom_size,
        interpolation = self.INTERPOLATION_METHODS[
            self.interpolation_method])
            
        if self.take_series:
            self.series_time_counter += 1
            if self.series_time_counter > self.series_time_interval:
                self.series_counter += 1
                self.series_time_counter = 0
                if self.series_counter <= self.number_of_imgs:
                    img_name = "{0}{1}".format(self.series_counter,
                        self.img_format)
                    cv2.imwrite(os.path.join(self.series_img_path, 
                        self.series_dir, img_name), self.inter_frame)
                else:
                    self.take_series = False
        if self.recording:
            self.video_writer.write(self.inter_frame)
        return self.inter_frame

    def recording_start_stop(self, name = None):
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name if name else 
                "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(datetime.datetime.utcnow()
                )), cv2.cv.CV_FOURCC(* VIDEO_CODEC), 24, self.zoom_size)
                
    def take_series_picture(self):
        if not self.take_series:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = 0
            self.series_counter = 0
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        cv2.imwrite(os.path.join(self.image_path, name), self.inter_frame)
        
    def series_up_down(self, step):
        if self.take_series == False: 
            if self.number_of_imgs > step *-1:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if self.take_series == False:
            if self.series_time_interval > step *-1:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        height = int(self.zoom_height + step)
        width = int(height * self.cam_width / self.cam_height)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 10
    REC_ON = 5
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.capture_start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ()),
             ("[1]", 2, self.take_picture, ()),
             ("REC", 2, self.recording_film, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("[S]]]", 2, self.take_series_pictures, ()),
             ("S+", 2, self.series_up_down, (1,)),
             ("S-", 2, self.series_up_down, (-1,)),
             ("T+", 2, self.time_interval_up_down, (1,)),
             ("T-", 2, self.time_interval_up_down, (-1,)),
             ("ON", 2, self.set_text_on_off, ()),
             ("C", 2, self.change_text_colour, ()))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)

    def capture_start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        try:
            tk_image = Image.frombytes("RGB",  self.microscope.zoom_size,
                self.microscope.get_image(), "raw", "BGR")
        except RuntimeError:
            tk_image = None
            
        if tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.recording:
                if self.rec_on.next() == 0:
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on:
                self.canvas.create_text(self.width / 2, self.height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, 
                    tag="txt")
                self.canvas.create_text(30, 10, font="Arial 11 bold", 
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}".format(
                    self.microscope.interpolation, 
                    self.microscope.number_of_imgs,                             
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size), anchor="nw", 
                    fill = self.text_colour, tag = "txt")

            state = tk.DISABLED if self.microscope.take_series else tk.NORMAL
            for button in (1, 2, 3, 7, 8, 9, 10, 11):
                self.buttons[button].config(state=state)
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        self.set_text_on_off(True)
        self.text_colour = self.text_colour_index.next()
        
    def set_text_on_off(self, force_on=False):
        self.text_on = force_on or not self.text_on
        self.buttons[12].config(text="OFF" if self.text_on else "ON")
        
    def raise_cam_id_error(self):
        self.canvas.delete("img", "rec", "txt")
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        self.set_text_on_off(True)
        self.microscope.next_interpolation_method()
        
    def recording_film(self):
        self.recording = False or not self.recording
        self.microscope.recording_start_stop()
            
    def take_series_pictures(self):
        self.microscope.take_series_picture()

    def take_picture(self):
        self.microscope.take_picture()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(series_time_interval=5) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß Frank
__deets__
User
Beiträge: 4832
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dienstag 3. April 2018, 21:45

self.recording = False or not self.recording

Kannst du zusammenkürzen zu

self.recording = not self.recording

Also einfach nur jeweils das Gegenteil von vorher. Das noch mit “or False” zu verknüpfen ist sinnfrei, das ändert nie das Ergebnis.
Sirius3
User
Beiträge: 9387
Registriert: Sonntag 21. Oktober 2012, 17:20

Donnerstag 5. April 2018, 07:14

Ob man jetzt die if-Abfrage in Zeile 110 wirklich in den Aufruf einbauen muß?
Zeile 130: nicht explizit auf False prüfen sondern not benutzen.
Zeile 131: step * -1 ist -step

Zeile 187ff: `take_picture`, `take_series_pictures` und `recording_film` können auch direkt von microscope genommen werden und brauchen keine eigenen Methoden in MicroscopeUI. Wenn man das text-on wegläßt auch noch einige andere.
Zeile 221: Exceptions sind dazu da, dass man direkt eine Fehlerbehandlung machen kann und nicht um ein Flag zu setzen, um dann wieder mit Flags zu arbeiten.
Zeile 228/282: Bei recording benutzt Du ein redundantes Flag in MicroscopeUI bei take_series das von Microscope.
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Donnerstag 5. April 2018, 21:51

Hallo __deets__ u. Siriuis3,

danke für eure Kritik und ich werde weitermachenl.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Donnerstag 5. April 2018, 23:40

Hallo,

Die Fehlerbehandlung habe ich so abgeschaltet, da die GUI es anzeigen soll. So können meine Nutzer (Kinder) darauf reagieren und es selbst neu starten . Die Kinder sollen mich eigentlich in Ruhe lassen, denn ich möchte meine Zeit mit Kaffee trinken verbringen und überall meine Erziehersstempel verteilen. Wie könnte ich es sonst lösen ?

Mit dem step*-1 habe ich irgendwann explizit geprüft das der Schritt positiv ist . Dies war bei dem Wert 1 nötig, denn da sollt er bei -step keinen Schritt mehr nach unten machen, sondern nur hoch laufen. Jetzt geht es auch so ? Seit Programmierer über diesen Code hergefallen sind, verstehe ich ihn selbst nicht mehr.

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle
import numpy as np

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                            "LINEAR" : cv2.INTER_LINEAR,
                            "AREA" : cv2.INTER_AREA,
                            "CUBIC" : cv2.INTER_CUBIC,
                            "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.inter_frame = None
        self.last_frame = None
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods_keys = cycle(
            self.INTERPOLATION_METHODS.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
        self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return self.zoom_width, self.zoom_height
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
       
        if state and np.array_equal(self.last_frame, frame):
            self.release()
            state = False
        if not state:
            raise RuntimeError("could not read image")
 
        self.last_frame = frame
        self.inter_frame = cv2.resize(frame, self.zoom_size,
        interpolation = self.INTERPOLATION_METHODS[
            self.interpolation_method])
            
        if self.take_series:
            self.series_time_counter += 1
            if self.series_time_counter > self.series_time_interval:
                self.series_counter += 1
                self.series_time_counter = 0
                if self.series_counter <= self.number_of_imgs:
                    img_name = "{0}{1}".format(self.series_counter,
                        self.img_format)
                    cv2.imwrite(os.path.join(self.series_img_path, 
                        self.series_dir, img_name), self.inter_frame)
                else:
                    self.take_series = False
        if self.recording:
            self.video_writer.write(self.inter_frame)
        return self.inter_frame

    def recording_start_stop(self, name = None):
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name if name else 
                "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(datetime.datetime.utcnow()
                )), cv2.cv.CV_FOURCC(* VIDEO_CODEC), 24, self.zoom_size)
                
    def take_series_picture(self):
        if not self.take_series:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = 0
            self.series_counter = 0
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        cv2.imwrite(os.path.join(self.image_path, name), self.inter_frame)
        
    def series_up_down(self, step):
        if not self.take_series: 
            if self.number_of_imgs > -step:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if not self.take_series:
            if self.series_time_interval > -step:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        height = int(self.zoom_height + step)
        width = int(height * self.cam_width / self.cam_height)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 10
    REC_ON = 5
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.capture_start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ()),
             ("[1]", 2, self.microscope.take_picture, ()),
             ("REC", 2, self.microscope.recording_start_stop, ()),
             ("[S]]]", 2, self.microscope.take_series_picture, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("S+", 2, self.series_up_down, (1,)),
             ("S-", 2, self.series_up_down, (-1,)),
             ("T+", 2, self.time_interval_up_down, (
              self.microscope.series_time_interval,)),
             ("T-", 2, self.time_interval_up_down, (
              -self.microscope.series_time_interval,)),
             ("ON", 2, self.set_text_on_off, ()),
             ("C", 2, self.change_text_colour, ()))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)

    def capture_start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self): 
        try:
            tk_image = Image.frombytes("RGB",  self.microscope.zoom_size,
                self.microscope.get_image(), "raw", "BGR")
        except RuntimeError:
            tk_image = None
            
        if tk_image:
            self.canvas.delete("img", "rec", "txt")
            self.tk_image = ImageTk.PhotoImage(tk_image)
            self.canvas.create_image((0,0), anchor=tk.NW,
                image=self.tk_image, tag="img")
            if self.microscope.recording:
                if not self.rec_on.next():
                    self.canvas.create_oval(10, 10, 25, 25, fill="red", 
                        tag="rec")
            if self.text_on:
                width, height = self.microscope.zoom_size
                self.canvas.create_text(width / 2, height / 2, 
                    text="+", font="Arial 30", fill=self.text_colour, 
                    tag="txt")
                self.canvas.create_text(width / 2, 
                    10 + height/2 - self.height / 2, 
                    font="Arial 11 bold", 
                    text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}"
                    .format(self.microscope.interpolation, 
                    self.microscope.number_of_imgs,                             
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size), 
                    anchor="center", 
                    fill = self.text_colour, 
                    tag = "txt")

            state = tk.DISABLED if self.microscope.take_series else tk.NORMAL
            for button in (1, 2, 3, 7, 8, 9, 10, 11):
                self.buttons[button].config(state=state)
                
            width, height = self.microscope.zoom_size
            self.canvas.config(scrollregion = (0, 0, width, height))
            self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
        else:
            self.raise_cam_id_error()
            
    def change_text_colour(self):       
        self.set_text_on_off(True)
        self.text_colour = self.text_colour_index.next()
        
    def set_text_on_off(self, force_on=False):
        self.text_on = force_on or not self.text_on
        self.buttons[12].config(text="OFF" if self.text_on else "ON")
        
    def raise_cam_id_error(self):
        self.canvas.delete("img", "rec", "txt")
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        self.set_text_on_off(True)
        self.microscope.next_interpolation_method()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(series_time_interval=1) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 9387
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 6. April 2018, 08:51

Ich verstehe nicht, was Du uns mit Deinen einleitenden Worten sagen willst. Ich habe versucht, den Code sauberer zu schreiben, dass sowohl Du ihn jetzt als auch in zwei Jahren noch verstehst.

Der Code sieht jetzt ganz in Ordnung aus, bis auf `run`:

Code: Alles auswählen

    def run(self):
        try:
            tk_image = Image.frombytes("RGB",  self.microscope.zoom_size,
                self.microscope.get_image(), "raw", "BGR")
        except RuntimeError:
            self.raise_cam_id_error()
            return

        self.canvas.delete("img", "rec", "txt")
        self.tk_image = ImageTk.PhotoImage(tk_image)
        self.canvas.create_image((0,0), anchor=tk.NW,
            image=self.tk_image, tag="img")
        if self.microscope.recording:
            if not self.rec_on.next():
                self.canvas.create_oval(10, 10, 25, 25, fill="red", tag="rec")
        if self.text_on:
            width, height = self.microscope.zoom_size
            self.canvas.create_text(width / 2, height / 2,
                text="+", font="Arial 30", fill=self.text_colour,
                tag="txt")
            self.canvas.create_text(width / 2,
                10 + height/2 - self.height / 2,
                font="Arial 11 bold",
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}"
                .format(self.microscope.interpolation,
                    self.microscope.number_of_imgs,                            
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size),
                anchor="center",
                fill = self.text_colour,
                tag = "txt")
 
        state = tk.DISABLED if self.microscope.take_series else tk.NORMAL
        for button in (1, 2, 3, 7, 8, 9, 10, 11):
            self.buttons[button].config(state=state)
               
        width, height = self.microscope.zoom_size
        self.canvas.config(scrollregion = (0, 0, width, height))
        self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
Benutzeravatar
kaytec
User
Beiträge: 575
Registriert: Dienstag 13. Februar 2007, 21:57

Freitag 6. April 2018, 10:49

Hallo Sirius3,
die Einleitung sollte nicht viel Tiefe haben, sondern eher meine Unfähigkeit zeigen. Eure Hilfe ist toll und der Lerneffekt ist für mich natürlich super, doch habe ich natürlich auch große Pause beim Programmieren und irgendwie alles Gelernte wieder vergessen. Da tauchen öfters die gleichen Fehler und Verständnisfragen auf. Ich benutze das Programm wirklich auf meiner Arbeit und versuche es anwenderfreundlicher zu machen, damit die Kinder selbstständig arbeiten können. Werde dafür einige Funktionen wieder abschalten und 2-3 Buttons behalten. Es wird solche Software zu kaufen geben und bestimmt auch in einer freien Version, doch so kann ich sie verändern und anpassen. Bei Vorschulkindern würde ein Button reichen.

Ist es eigentlich OK den veränderten Code komplett einzufügen ?

Code: Alles auswählen

#! /usr/bin/env python
# -*- coding: utf-8
from __future__ import division

import tkinter as tk
import os
import datetime
import cv2
from PIL import Image, ImageTk
from functools import partial
from itertools import cycle
import numpy as np

WIDTH = 640
HEIGHT = 480
VIDEO_CODEC = "XVID"
DEFAULT_CAM_ID = 0


class Microscope(object):

    PROPID_WIDTH = 3
    PROPID_HEIGHT = 4
    INTERPOLATION_METHODS= {"NEAREST" : cv2.INTER_NEAREST,
                            "LINEAR" : cv2.INTER_LINEAR,
                            "AREA" : cv2.INTER_AREA,
                            "CUBIC" : cv2.INTER_CUBIC,
                            "LANCZOS4" : cv2.INTER_LANCZOS4}

    def __init__(self, cam_id=DEFAULT_CAM_ID, number_of_imgs=10, 
        series_time_interval=1, image_path=".", video_path=".",
        series_img_path=".", img_format=".tiff"):
        self.cam = cv2.VideoCapture(cam_id)
        if not self.cam.isOpened():
            raise RuntimeError("can not open camera {0!r}".format(
                cam_id))
        self.img_format = img_format
        self.inter_frame = None
        self.last_frame = None
        self.take_series = False
        self.recording = False
        self.number_of_imgs = number_of_imgs
        self.series_counter = None
        self.series_time_counter = None
        self.series_time_interval = series_time_interval
        self.image_path = image_path
        self.video_path = video_path
        self.series_img_path = series_img_path
        self.series_dir = None
        self.interpolation_methods_keys = cycle(
            self.INTERPOLATION_METHODS.keys())
        self.interpolation_method = self.interpolation_methods_keys.next()
        self.cam_width = self.zoom_width = int(self.cam.get(self.PROPID_WIDTH))
        self.cam_height = self.zoom_height = int(self.cam.get(self.PROPID_HEIGHT))
        
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.release()

    @property
    def size(self):
        return self.cam_width, self.cam_height
        
    @property
    def zoom_size(self):
        return self.zoom_width, self.zoom_height
        
    @property
    def interpolation(self):
        return self.interpolation_method

    def get_image(self):
        state, frame = self.cam.read()
       
        if state and np.array_equal(self.last_frame, frame):
            self.release()
            state = False
        if not state:
            raise RuntimeError("could not read image")
 
        self.last_frame = frame
        self.inter_frame = cv2.resize(frame, self.zoom_size,
        interpolation = self.INTERPOLATION_METHODS[
            self.interpolation_method])
            
        if self.take_series:
            self.series_time_counter += 1
            if self.series_time_counter > self.series_time_interval:
                self.series_counter += 1
                self.series_time_counter = 0
                if self.series_counter <= self.number_of_imgs:
                    img_name = "{0}{1}".format(self.series_counter,
                        self.img_format)
                    cv2.imwrite(os.path.join(self.series_img_path, 
                        self.series_dir, img_name), self.inter_frame)
                else:
                    self.take_series = False
        if self.recording:
            self.video_writer.write(self.inter_frame)
        return self.inter_frame

    def recording_start_stop(self, name = None):
        if self.recording:
            self.recording = False
        else:
            self.recording = True
            self.video_writer = cv2.VideoWriter(os.path.join(
                self.video_path, name if name else 
                "{0:%d%b%Y_%H_%M_%S.%f}.avi".format(datetime.datetime.utcnow()
                )), cv2.cv.CV_FOURCC(* VIDEO_CODEC), 24, self.zoom_size)
                
    def take_series_picture(self):
        if not self.take_series:         
            self.series_dir = "{0:%d%b%Y_%H_%M_%S.%f}".format(
                datetime.datetime.utcnow())
            os.makedirs(self.series_dir)
            self.series_time_counter = 0
            self.series_counter = 0
            self.take_series = True

    def take_picture(self, name = None):
        if not name:
            name = "{0:%d%b%Y_%H_%M_%S.%f}{1}".format(
                datetime.datetime.utcnow(), self.img_format)
        cv2.imwrite(os.path.join(self.image_path, name), self.inter_frame)
        
    def series_up_down(self, step):
        if not self.take_series: 
            if self.number_of_imgs > -step:
                self.number_of_imgs += step
        
    def set_time_interval(self, step):
        if not self.take_series:
            if self.series_time_interval > -step:
                self.series_time_interval += step
        
    def zoom_image(self, step):
        height = int(self.zoom_height + step)
        width = int(height * self.cam_width / self.cam_height)
        if width > 0 and height > 0:
            self.zoom_width = width
            self.zoom_height = height

    def reset_zoom(self):
        self.zoom_width, self.zoom_height = self.size
        
    def next_interpolation_method(self):
        self.interpolation_method = self.interpolation_methods_keys.next()
        
    def release(self):
        self.cam.release()

class MicroscopeUI(tk.Frame):

    UPDATE_INTERVAL = 10
    REC_ON = 5
    
    def __init__(self, parent, microscope, width, height, zoom_step=10):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.width = width
        self.height = height
        self.tk_image = None
        self.rec_on = cycle(x for x in xrange(self.REC_ON))
        self.recording = False
        self.text_on = False
        self.microscope = microscope
        self.text_colour_index = cycle(["white", "green", "black", "red",
            "magenta", "green", "brown", "yellow", "blue", "orange", "gray"])
        self.text_colour = self.text_colour_index.next()
        self.canvas = tk.Canvas(self, width=width, height=height)
        self.canvas.grid(column=0, row=0)
        vscrollbar = tk.Scrollbar(self)
        vscrollbar.grid(column=1, row=0, sticky=tk.N+tk.S)
        self.canvas.config(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=self.canvas.yview)
        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        hscrollbar.grid(column=0, row=1, columnspan=5, sticky=tk.E+tk.W)
        self.canvas.config(xscrollcommand=hscrollbar.set)
        hscrollbar.config(command=self.canvas.xview)
        button_frame = tk.Frame(self)
        button_frame.grid(column=0, row=3, columnspan=2)
        self.buttons = list()
        for column, (text, width, command, var)in enumerate(
            (("||", 2, self.capture_start_stop, ()),
             ("Z+", 2, self.microscope.zoom_image, (zoom_step,)),
             ("Z-", 2, self.microscope.zoom_image, (-zoom_step,)),
             ("Z+/-", 2, self.microscope.reset_zoom, ()),
             ("[1]", 2, self.microscope.take_picture, ()),
             ("REC", 2, self.microscope.recording_start_stop, ()),
             ("[S]]]", 2, self.microscope.take_series_picture, ()),
             ("INTPOL", 5, self.next_interpolation_method, ()),
             ("S+", 2, self.series_up_down, (1,)),
             ("S-", 2, self.series_up_down, (-1,)),
             ("T+", 2, self.time_interval_up_down, (
              self.microscope.series_time_interval,)),
             ("T-", 2, self.time_interval_up_down, (
              -self.microscope.series_time_interval,)),
             ("ON", 2, self.set_text_on_off, ()),
             ("C", 2, self.change_text_colour, ()))):
            button = tk.Button(button_frame, text=text, width=width,
                relief="raised", font="Arial 10 bold",
                command=partial(command, *var))
            button.grid(column=column, row=0)
            self.buttons.append(button)

    def capture_start_stop(self):
        if self.after_id is None:
            self.buttons[0].config(text = "||")
            self.run()
        else:
            self.buttons[0].config(text = ">")
            self.after_cancel(self.after_id)
            self.after_id = None

    def run(self):
        try:
            tk_image = Image.frombytes("RGB",  self.microscope.zoom_size,
                self.microscope.get_image(), "raw", "BGR")
        except RuntimeError:
            self.raise_cam_id_error()
            return
 
        self.canvas.delete("img", "rec", "txt")
        self.tk_image = ImageTk.PhotoImage(tk_image)
        self.canvas.create_image((0,0), anchor=tk.NW,
            image=self.tk_image, tag="img")
        if self.microscope.recording:
            if not self.rec_on.next():
                self.canvas.create_oval(10, 10, 25, 25, fill="red", tag="rec")
        if self.text_on:
            width, height = self.microscope.zoom_size
            self.canvas.create_text(width / 2, height / 2,
                text="+", font="Arial 30", fill=self.text_colour,
                tag="txt")
            self.canvas.create_text(width / 2,
                10 + height/2 - self.height / 2,
                font="Arial 11 bold",
                text = "FILTER: {0}  SERIES: {1}  TIME: {2}  RES: {3}"
                .format(self.microscope.interpolation,
                    self.microscope.number_of_imgs,                            
                    self.microscope.series_time_interval,
                    self.microscope.zoom_size),
                anchor="center",
                fill = self.text_colour,
                tag = "txt")
 
        state = tk.DISABLED if self.microscope.take_series else tk.NORMAL
        for button in (1, 2, 3, 7, 8, 9, 10, 11):
            self.buttons[button].config(state=state)
               
        width, height = self.microscope.zoom_size
        self.canvas.config(scrollregion = (0, 0, width, height))
        self.after_id = self.after(self.UPDATE_INTERVAL, self.run)
            
    def change_text_colour(self):       
        self.set_text_on_off(True)
        self.text_colour = self.text_colour_index.next()
        
    def set_text_on_off(self, force_on=False):
        self.text_on = force_on or not self.text_on
        self.buttons[12].config(text="OFF" if self.text_on else "ON")
        
    def raise_cam_id_error(self):
        self.canvas.delete("img", "rec", "txt")
        self.canvas.create_text((self.width / 2, self.height / 2),
            text='NO CAM', font='Arial 40')
        for button in self.buttons:
            button.config(state="disabled")
            
    def series_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.series_up_down(step)
        
    def time_interval_up_down(self, step):
        self.set_text_on_off(True)
        self.microscope.set_time_interval(step)
        
    def next_interpolation_method(self):
        self.set_text_on_off(True)
        self.microscope.next_interpolation_method()

    def release(self):
        self.parent.destroy()

def main():
    root = tk.Tk()
    root.title('MICROSCOPE')
    
    try:
        with Microscope(series_time_interval=1) as microscope:
            def take_picture(e):
                microscope_ui.take_picture()
            microscope_ui = MicroscopeUI(
                root, microscope, WIDTH, HEIGHT)
            microscope_ui.pack(expand=tk.YES)
            microscope_ui.run()
            root.protocol("WM_DELETE_WINDOW", microscope_ui.release)
            #root.bind("<Key" + chr(10) + ">", take_picture)
            root.mainloop()
            
    except RuntimeError:
        tk.Label(root, text = 'can not open camera {0!r}'.format(
                DEFAULT_CAM_ID), font = "Arial 20", height = 10).pack()
        root.mainloop()
    
if __name__ == '__main__':
    main()
Gruß und Dank
Antworten