Seite 1 von 1

Scripte mit Freunden Teilen?

Verfasst: Montag 20. Juli 2020, 10:11
von Potatütata
Liebe Community,
ich bin komplett neu in diesem Forum und habe wie vielleicht schon erwartet auch noch nicht viel Erfahrung mit Python. Ich kann schon ein paar eigene Scripts schreiben, allerdings habe ich vor kurzem versucht diese mit einem Freund zu teilen.
Dabei bin ich schnell auf das erwartete Problem gestoßen. Er kann die ganzen Librarys nicht importieren. Ich habe dieses Script mit Pycharm programmiert und dabei wird immer ein Projekt Ordner erstellt in dem auch die ganzen Librarys irgendwo gespeichert sind. Ich habe ihm den ganzen Projekt Ordner geschickt, also sollte er ja die Librarys auch haben, oder? Ich habe bereits probiert mit

Code: Alles auswählen

sys.path.append(os.path.join(sys.path[0], 'venv', 'Lib', 'site-packages'))
den richtigen Ordner an zu geben, allerdings funktioniert der import von opencv und pynput immer noch nicht. Das sind auch die einzigen Librarys die nicht von python selbst sind.

Ich habe den ganzen Ordner auf mediafire hochgeladen, falls sich jemand diesen anschauen möchte.

http://www.mediafire.com/file/wn6hbeyl5 ... g.zip/file

PS: Ich hätte mir die Regeln zum posten von Themen gerne durchgelesen, allerdings funktioniert der Link nicht. Vielleicht war ich auch einfach nur zu blöd :)

Re: Scripte mit Freunden Teilen?

Verfasst: Montag 20. Juli 2020, 10:38
von __blackjack__
@Potatütata: Virtualenvs sind nicht zum weitergeben geeignet. Die kann man nicht einmal auf dem eigenen Rechner einfach woanders hin kopieren ohne das sie kaputt gehen. Ich würde auch eher keine IDE-spezifischen Sachen weitergeben. Nur zum Ausführen braucht man die nicht, und falls derjenige doch etwas ändern möchte, muss er ja nicht unbedingt die gleiche IDE verwenden wollen wie Du.

Entweder gibst Du eine Textdatei mit, in der steht wie die Abhängigkeiten installiert werden müssen und/oder schreibst die in eine `requirements.txt` die von ``pip`` dann ausgewertet werden kann, oder Du schreibst ein ``setup.py`` wie in der Python-Dokumentation beschrieben, oder Du verwendest eines der Programme um Dein Programm in einen Installer zu verpacken, wenn es okay ist, dass das dann von der Zielplattform abhängig ist.

Re: Scripte mit Freunden Teilen?

Verfasst: Montag 20. Juli 2020, 12:55
von __blackjack__
@Potatütata: Zum Quelltext selbst: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Das "Script" im Modulnamen ist nicht wirklich sinnvoll.

Den Pfad zu den `venv`-Verzeichnissen steckt man nicht selbst *im* Programm in den Suchpfad für Module sondern man aktiviert vor dem Start des Programms das Virtualenv über die vorgesehenen Skripte. Das Programm selbst sollte nichts darüber wissen müssen ob es in einem venv läuft oder nicht.

``try``/``except`` um Importe zu legen nur um den Benutzer mit `print()` zu Informieren das ein Modul nicht importiert werden konnte und dann das Programm abzubrechen macht keinen Sinn. Genau das passiert ja auch wenn man diesen Code weg lässt. Zumal mir nicht so ganz klar ist wonach hier entschieden wurde ob normal importiert wird, oder mit dieser Sonderbehandlung, denn `numpy` könnte ja auch *nicht* installiert sein, das wird aber ohne irgendwelchen zusätzlichen Code importiert. `os` und `sys` kann man sich dann auch sparen wenn man diesen überflüssigen Code entfernt.

Das ohnehin schon abgekürzte `cv2` beim importieren auf `cv` zu verkürzen ist nicht wirklich sinnvoll.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Daraus folgt, dass es keine Modulglobalen Variablen gibt, also Funktionen und Methoden alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen.

Namen sollten keine kryptischen Abkürzungen enthalten und schon gar nicht nur eine kryptische Abkürzung sein. Wenn man `top_left_coordinates` meint, sollte man nicht nur `tl` schreiben.

Werte in Klammern beim Prompt von Eingaben sind in der Regel Defaultwerte die verwendet werden wenn man da *nichts* eingibt. Dass das hier nur Beispiele sind, ist sehr verwirrend für den Benutzer.

Wenn der Benutzer Zahlen eingeben soll, dann konvertiert man die Zeichenketteneingaben direkt nach der Eingabe in Zahlen und nicht jedes mal wenn man tatsächlich die Zahl braucht.

Der Dateipfad der Bilddatei sollte im Hauptprogramm vom Benutzer abgefragt werden. Sonst lässt sich die Funktion die das Bild lädt und in eine Bitmap wandelt schlecht testen und/oder wiederverwenden.

Daten wie Dateinamen wiederholt man nicht mehrfach über den gesamten Quelltext verteilt. Da definiert man am Anfang eine Konstante. Allerdings sehe ich den Sinn vom speichern und laden von "BWImg.png" über Funktionen hinweg nicht wirklich. Man kann das Bild auch einfach als Rückgabewert/Argument im Programm herum reichen. Da muss man nicht den Umweg über das Dateisystem gehen.

Warum wird nach der Anzeige des Bildes 10 Sekunden gewartet in denen nichts passiert? Würde mich als Benutzer ziemlich nerven.

`screensize` wird nirgends verwendet. Damit ist dann auch `ctypes` hinfällig und das Programm ist nicht mehr nur unter Windows lauffähig. `img_w` und `img_h` in `draw_image()` werden definiert aber nirgends verwendet.

Dann werden da mit dem Bild komische Dinge gemacht. Das ist bereits ein Numpy-Array, das heisst `np.array()` darauf aufzurufen bringt genau gar nichts. Der Name `n` ist auch sehr schlecht für ein Array das ein Bild enthält.

Wenn man zusätzlich zu den Werten eines iterierbaren Objekts noch einelaufende ganze Zahl benötigt, verwaltet man die nicht manuell, sondern verwendet `enumerate()`. Ich würde das auch eher nicht so frickelig kleinteilig lösen, sondern `itertools.groupby()` verwenden.

Das da in `coordinates` an jedem zweiten Index der Start- beziehungsweise Endwert einer Linie steht ist ungünstig und macht das Programm nur unnötig kompliziert. Das sollte eine Liste sein die als Elemente Paare von Start- und Endkoordinaten enthält.

Die `draw_image()`-Funktion sollte selbst dafür sorgen, dass am Ende die Maustaste nicht mehr gedrückt ist. Das sollte bei einem etwas übersichtlicheren Kontrollfluss auch leicht machbar sein.

Die Funktion macht zu viel. Das erstellen der Liniendaten sollte man dort heraus ziehen.

Für Zeitmessungen ist `time.monotonic()` besser geeignet als `time.time()`.

Das letzte `input()` ist überflüssig und nervig.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time
from itertools import count, groupby

import cv2
import numpy as np
from more_itertools import last
from pynput.mouse import Button, Controller


def make_bitmap_image(
    image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
):
    image = cv2.imread(str(image_file_path))
    width, height = image.shape[:2]
    factor = min(
        (bottom_right_x - top_left_x) / width,
        (bottom_right_y - top_left_y) / height,
    )
    _, bitmap_image = cv2.threshold(
        cv2.cvtColor(
            cv2.resize(image, (int(factor * width), int(factor * height))),
            cv2.COLOR_BGR2GRAY,
        ),
        127,
        255,
        cv2.THRESH_BINARY,
    )
    return bitmap_image


def iter_lines(image):
    #
    # FIXME This contains an error from the original implementation.  The
    # grouping just considers the X coordinates and completely ignores the Y
    # coordinates.  So the following image will give *one* line ((2, 0), (7,
    # 1)), instead of two lines — one for each pixel row.
    #
    #  ..###.....
    #  .....###..
    #
    y_coordinates, x_coordinates = np.where(
        (image[:, :, 0:3] == [0, 0, 0]).all(2)
    )
    for _, group in groupby(
        zip(count(), x_coordinates, y_coordinates),
        key=lambda item: item[1] - item[0],
    ):
        _, start_x, start_y = next(group)
        _, end_x, end_y = last(group, (None, start_x, start_y))
        yield ((start_x, start_y), (end_x, end_y))


def draw_image(lines, x_offset, y_offset):
    mouse = Controller()
    for (start_x, start_y), (end_x, _) in lines:
        mouse.position = (start_x + x_offset, start_y + y_offset)
        mouse.press(Button.left)
        time.sleep(0.03)
        for _ in range(start_x, end_x):
            mouse.move(1, 0)
            time.sleep(0.0002)
        time.sleep(0.02)
        mouse.release(Button.left)
        time.sleep(0.04)


def main():
    top_left_x, top_left_y = map(
        int, input("Input top left Corner (x,y): ").split(",")
    )
    bottom_right_x, bottom_right_y = map(
        int, input("Input bottom right Corner (x,y): ").split(",")
    )
    image_file_path = input(
        "Input the path to the image (C:/Users/.../image_filename.ext): "
    )

    image = make_bitmap_image(
        image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
    )
    cv2.imshow("Press any key to accept the chosen image!", image)
    cv2.waitKey()

    start = time.monotonic()
    draw_image(iter_lines(image), top_left_x, top_left_y)
    print(f"Time for the drawing: {(time.monotonic() - start) / 60:.3f} min")


if __name__ == "__main__":
    main()

Re: Scripte mit Freunden Teilen?

Verfasst: Sonntag 23. August 2020, 22:05
von Potatütata
__blackjack__ hat geschrieben: Montag 20. Juli 2020, 12:55 @Potatütata: Zum Quelltext selbst: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Das "Script" im Modulnamen ist nicht wirklich sinnvoll.

Den Pfad zu den `venv`-Verzeichnissen steckt man nicht selbst *im* Programm in den Suchpfad für Module sondern man aktiviert vor dem Start des Programms das Virtualenv über die vorgesehenen Skripte. Das Programm selbst sollte nichts darüber wissen müssen ob es in einem venv läuft oder nicht.

``try``/``except`` um Importe zu legen nur um den Benutzer mit `print()` zu Informieren das ein Modul nicht importiert werden konnte und dann das Programm abzubrechen macht keinen Sinn. Genau das passiert ja auch wenn man diesen Code weg lässt. Zumal mir nicht so ganz klar ist wonach hier entschieden wurde ob normal importiert wird, oder mit dieser Sonderbehandlung, denn `numpy` könnte ja auch *nicht* installiert sein, das wird aber ohne irgendwelchen zusätzlichen Code importiert. `os` und `sys` kann man sich dann auch sparen wenn man diesen überflüssigen Code entfernt.

Das ohnehin schon abgekürzte `cv2` beim importieren auf `cv` zu verkürzen ist nicht wirklich sinnvoll.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Daraus folgt, dass es keine Modulglobalen Variablen gibt, also Funktionen und Methoden alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen.

Namen sollten keine kryptischen Abkürzungen enthalten und schon gar nicht nur eine kryptische Abkürzung sein. Wenn man `top_left_coordinates` meint, sollte man nicht nur `tl` schreiben.

Werte in Klammern beim Prompt von Eingaben sind in der Regel Defaultwerte die verwendet werden wenn man da *nichts* eingibt. Dass das hier nur Beispiele sind, ist sehr verwirrend für den Benutzer.

Wenn der Benutzer Zahlen eingeben soll, dann konvertiert man die Zeichenketteneingaben direkt nach der Eingabe in Zahlen und nicht jedes mal wenn man tatsächlich die Zahl braucht.

Der Dateipfad der Bilddatei sollte im Hauptprogramm vom Benutzer abgefragt werden. Sonst lässt sich die Funktion die das Bild lädt und in eine Bitmap wandelt schlecht testen und/oder wiederverwenden.

Daten wie Dateinamen wiederholt man nicht mehrfach über den gesamten Quelltext verteilt. Da definiert man am Anfang eine Konstante. Allerdings sehe ich den Sinn vom speichern und laden von "BWImg.png" über Funktionen hinweg nicht wirklich. Man kann das Bild auch einfach als Rückgabewert/Argument im Programm herum reichen. Da muss man nicht den Umweg über das Dateisystem gehen.

Warum wird nach der Anzeige des Bildes 10 Sekunden gewartet in denen nichts passiert? Würde mich als Benutzer ziemlich nerven.

`screensize` wird nirgends verwendet. Damit ist dann auch `ctypes` hinfällig und das Programm ist nicht mehr nur unter Windows lauffähig. `img_w` und `img_h` in `draw_image()` werden definiert aber nirgends verwendet.

Dann werden da mit dem Bild komische Dinge gemacht. Das ist bereits ein Numpy-Array, das heisst `np.array()` darauf aufzurufen bringt genau gar nichts. Der Name `n` ist auch sehr schlecht für ein Array das ein Bild enthält.

Wenn man zusätzlich zu den Werten eines iterierbaren Objekts noch einelaufende ganze Zahl benötigt, verwaltet man die nicht manuell, sondern verwendet `enumerate()`. Ich würde das auch eher nicht so frickelig kleinteilig lösen, sondern `itertools.groupby()` verwenden.

Das da in `coordinates` an jedem zweiten Index der Start- beziehungsweise Endwert einer Linie steht ist ungünstig und macht das Programm nur unnötig kompliziert. Das sollte eine Liste sein die als Elemente Paare von Start- und Endkoordinaten enthält.

Die `draw_image()`-Funktion sollte selbst dafür sorgen, dass am Ende die Maustaste nicht mehr gedrückt ist. Das sollte bei einem etwas übersichtlicheren Kontrollfluss auch leicht machbar sein.

Die Funktion macht zu viel. Das erstellen der Liniendaten sollte man dort heraus ziehen.

Für Zeitmessungen ist `time.monotonic()` besser geeignet als `time.time()`.

Das letzte `input()` ist überflüssig und nervig.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time
from itertools import count, groupby

import cv2
import numpy as np
from more_itertools import last
from pynput.mouse import Button, Controller


def make_bitmap_image(
    image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
):
    image = cv2.imread(str(image_file_path))
    width, height = image.shape[:2]
    factor = min(
        (bottom_right_x - top_left_x) / width,
        (bottom_right_y - top_left_y) / height,
    )
    _, bitmap_image = cv2.threshold(
        cv2.cvtColor(
            cv2.resize(image, (int(factor * width), int(factor * height))),
            cv2.COLOR_BGR2GRAY,
        ),
        127,
        255,
        cv2.THRESH_BINARY,
    )
    return bitmap_image


def iter_lines(image):
    #
    # FIXME This contains an error from the original implementation.  The
    # grouping just considers the X coordinates and completely ignores the Y
    # coordinates.  So the following image will give *one* line ((2, 0), (7,
    # 1)), instead of two lines — one for each pixel row.
    #
    #  ..###.....
    #  .....###..
    #
    y_coordinates, x_coordinates = np.where(
        (image[:, :, 0:3] == [0, 0, 0]).all(2)
    )
    for _, group in groupby(
        zip(count(), x_coordinates, y_coordinates),
        key=lambda item: item[1] - item[0],
    ):
        _, start_x, start_y = next(group)
        _, end_x, end_y = last(group, (None, start_x, start_y))
        yield ((start_x, start_y), (end_x, end_y))


def draw_image(lines, x_offset, y_offset):
    mouse = Controller()
    for (start_x, start_y), (end_x, _) in lines:
        mouse.position = (start_x + x_offset, start_y + y_offset)
        mouse.press(Button.left)
        time.sleep(0.03)
        for _ in range(start_x, end_x):
            mouse.move(1, 0)
            time.sleep(0.0002)
        time.sleep(0.02)
        mouse.release(Button.left)
        time.sleep(0.04)


def main():
    top_left_x, top_left_y = map(
        int, input("Input top left Corner (x,y): ").split(",")
    )
    bottom_right_x, bottom_right_y = map(
        int, input("Input bottom right Corner (x,y): ").split(",")
    )
    image_file_path = input(
        "Input the path to the image (C:/Users/.../image_filename.ext): "
    )

    image = make_bitmap_image(
        image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
    )
    cv2.imshow("Press any key to accept the chosen image!", image)
    cv2.waitKey()

    start = time.monotonic()
    draw_image(iter_lines(image), top_left_x, top_left_y)
    print(f"Time for the drawing: {(time.monotonic() - start) / 60:.3f} min")


if __name__ == "__main__":
    main()


Allein an diesem Kommentar habe ich gemerkt wie viel ich noch zu lernen habe. Danke für die ganzen Tipps :)

Code: Alles auswählen

def make_bitmap_image(
    image_file_path, top_left_x, top_left_y, bottom_right_x, bottom_right_y
):
    image = cv2.imread(str(image_file_path))
    width, height = image.shape[:2]
    factor = min(
        (bottom_right_x - top_left_x) / width,
        (bottom_right_y - top_left_y) / height,
    )
    -
    -
    ------------------------- Ab hier verstehe ich nicht mehr alles -------------------------------
    -
    -
    _, bitmap_image = cv2.threshold(
        cv2.cvtColor(
            cv2.resize(image, (int(factor * width), int(factor * height))),
            cv2.COLOR_BGR2GRAY,
        ),
        127,
        255,
        cv2.THRESH_BINARY,
    )
    return bitmap_image


def iter_lines(image):
    #
    # FIXME This contains an error from the original implementation.  The
    # grouping just considers the X coordinates and completely ignores the Y
    # coordinates.  So the following image will give *one* line ((2, 0), (7,
    # 1)), instead of two lines — one for each pixel row.
    #
    #  ..###.....
    #  .....###..
    #
    y_coordinates, x_coordinates = np.where(
        (image[:, :, 0:3] == [0, 0, 0]).all(2)
    )
    for _, group in groupby(
        zip(count(), x_coordinates, y_coordinates),
        key=lambda item: item[1] - item[0],
    ):
        _, start_x, start_y = next(group)
        _, end_x, end_y = last(group, (None, start_x, start_y))
        yield ((start_x, start_y), (end_x, end_y))

Werde mich mal über die Zeilen die ich nicht verstehe informieren, dann kann ich daraus lernen.