Aus Schleife ausbrechen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Popkultur
User
Beiträge: 30
Registriert: Donnerstag 20. Oktober 2016, 16:46

Hallo!

Ich weiß nicht, ob der folgende Code ausreicht, aber er schmeisst eine nichtssagende Fehlermeldung im Programm, Frage: Kann man das formal nicht so schreiben mit dem return und break am Ende innerhalb einer Klasse? Der Code liest einen Stream und gibt ein Bild zurück.

Code: Alles auswählen

    def get_frame(self):
        response = urllib.request.urlopen(self.req)
        while True:
            self.bytes+=response.read(1024)
            x = self.bytes.find(b'\xff\xd8')
            y = self.bytes.find(b'\xff\xd9')
            if x!=-1 and y!=-1:
                jpg = self.bytes[x:y+2]
                self.bytes = self.bytes[x+y:]
                i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8),1)
                return i
                break   
Zuletzt geändert von Anonymous am Donnerstag 20. Oktober 2016, 19:33, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das `break` ist überflüssig. Die Schleife wird bereits durch das `return` verlassen.
BlackJack

@Popkultur: Was ist denn die nichtssagende Fehlermeldung? Und wo genau tritt die auf? Also am besten den kompletten Traceback 1:1 zeigen.

Ich denke es ist auch fehlerhaft, weil da beim erneuten Aufruf noch Informationen vom letzten Aufruf vorhanden sein können, die zu kaputten JPGs führen können.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Popkultur: willst Du von dem Stream immer nur das erste Bild? Das mit den self.bytes ist auf jeden Fall falsch.

Wenn man von einer URL einfach ein JPG lesen will, dann ist die Klasse überflüssig:

Code: Alles auswählen

def get_frame(url):
    response = urllib.request.urlopen(url)
    bytes = b""
    while True:
        data = response.read(1024)
        if not data:
            raise IOError("end of stream")
        bytes += data
        x = bytes.find(b'\xff\xd8')
        y = bytes.find(b'\xff\xd9')
        if x!=-1 and y!=-1:
            break
    return cv2.imdecode(np.fromstring(bytes[x:y+2], dtype=np.uint8), 1)
Will man von einer URL viele Frames lesen, dann ist die Klasse ebenso überflüssig und man würde eher einen Generator schreiben:

Code: Alles auswählen

def iter_frame(url):
    response = urllib.request.urlopen(url)
    bytes = b""
    while True:
        data = response.read(1024)
        if not data:
            break
        bytes += data
        x = bytes.find(b'\xff\xd8')
        y = bytes.find(b'\xff\xd9')
        if x!=-1 and y!=-1:
            yield cv2.imdecode(np.fromstring(bytes[x:y+2], dtype=np.uint8), 1)
            bytes = bytes[y+2:]
Aus der opencv-Dokumentation bin ich nicht schlau geworden, ob das Umwandeln in ein numpy-Array überhaupt notwendig ist, oder imdecode direkt einen Bytestring schluckt. Irgendwie scheinen alle sonstigen Beispiele nur voneinander zu kopieren.
Popkultur
User
Beiträge: 30
Registriert: Donnerstag 20. Oktober 2016, 16:46

Vielen Dank! Ich hatte wohl python 2 Code mit python 3 versucht zu vermischen, weshalb es nicht gleich funktioniert hat. Sirius3 hat mit seinem letzten Codeschnippsel den Nagel auf den Kopf getroffen, auch bei mir funktioniert es mittlerweile.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Falls das Bild nicht komplett in einen Chunk passt:

Code: Alles auswählen

def get_image(stream, chunksize=1024):
    start = stop = -1
    while True:
        data = stream.read(chunksize)
        if not data:
            raise IOError('end of stream')
        if start < 0:
            start = data.find(b'\xff\xd8')
        if start >= 0:
            stop = data.find(b'\xff\xd9')
        if stop >= 0:
            buf = np.fromstring(data[start:stop + 2], dtype=np.uint8)
            return cv2.imdecode(buf, 1)
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mein vorheriger Code hat den Aspekt, dass man Inhalt, der über mehrere Chunks geht, natürlich speichern sollte, gar nicht enthalten. Somit wäre der für diesen Fall auch nicht sinnvoll einsetzbar.

Hier die verbesserte Version:

Code: Alles auswählen

def get_image(stream, chunksize=1024):
    chunks = []
    start = stop = -1
    while stop < 0:
        chunk = stream.read(chunksize)
        if not chunk:
            raise IOError('end of stream')
        start = chunk.find(b'\xff\xd8')
        if start < 0:
            if not buf:
                # Try next chunk
                continue
            start = 0
        stop = chunk.find(b'\xff\xd9')
        if stop < 0:
            stop = len(chunk)
        chunks.append(chunk[start:stop + 2])
    buf = np.fromstring(''.join(chunks), dtype=np.uint8)
    return cv2.imdecode(buf, 1)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@snafu: das wird leider nicht besser. buf ist in Zeile 10 nicht definiert. Schwieriger zu entdecken ist der Bug, wenn im ersten Chunk das letzte Byte '\xff' ist, und erst im nächsten Chunk '\xd8' oder '\xd9' gelesen wird.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Jetzt ist der `NameError` weg. Außerdem habe ich die Schleifenbedingung korrigiert.

Code: Alles auswählen

def get_image(stream, chunksize=1024):
    chunks = []
    stop_seen = False
    while not stop_seen:
        chunk = stream.read(chunksize)
        if not chunk:
            raise IOError('end of stream')
        start = chunk.find(b'\xff\xd8')
        if start < 0:
            if not chunks:
                # Try next chunk
                continue
            start = 0
        stop = chunk.find(b'\xff\xd9')
        if stop < 0:
            stop = len(chunk)
        else:
            stop_seen = True            
        chunks.append(chunk[start:stop + 2])
    buf = np.fromstring(''.join(chunks), dtype=np.uint8)
    return cv2.imdecode(buf, 1)
BlackJack

@snafu: Du hast immer noch das Problem das die Markierungen über `chunk`-Grenzen hinweg gehen können.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dann hier nochmal abgeändert:

Code: Alles auswählen

REMAINING = 'remaining'
PROCESSED = 'processed'

def get_chars(stream, needle, buffer=None, chunksize=1024, mode=REMAINING):
    """
    Read chunks from stream and stop when needle was found.
    
    Return needle and all characters after it if mode is 'remaining'.
    
    Return all characters before needle and needle if mode is 'processed'.
    
    Note that no more chunks are read when needle was found. Therefore,
    characters might remain untouched at the stream. These characters are
    not included in the result.
    """
    if mode not in (REMAINING, PROCESSED):
        raise ValueError('unknown mode')
    empty = type(needle)()
    if not buffer:
        buffer = empty
    processed = []
    chunks = iter(lambda: stream.read(chunksize), empty)
    for chunk in chunks:
        haystack = buffer + chunk
        index = haystack.find(needle)
        if index >= 0:
            if mode == PROCESSED:
                buffered = haystack[:index + len(needle)]
                return empty.join(processed) + buffered
            return haystack[index:]
        buffer = haystack[-len(needle):]
        if mode == PROCESSED:
            data = haystack[:-len(needle)]
            processed.append(data)
    return empty

def get_image_data(stream, start_mark=b'\xff\xd8', stop_mark=b'\xff\xd9'):
    data = get_chars(stream, start_mark, mode=REMAINING)
    if not data:
        raise IOError('missing start mark')
    buf = data[len(start_mark):]
    data = get_chars(stream, stop_mark, buffer=buf, mode=PROCESSED)
    if not data:
        raise IOError('missing stop mark')
    return data
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier nochmal etwas robuster.

Die `get_chars()`-Funktion mit ihren verschiedenen Modi ist recht komplex geworden. Außerdem ist sie nicht bis zum Ende gedacht, da sie im Modus 'remaining' Zeichen verliert, die im zuletzt gelesenen Chunk hinter der Markierung stehen, da ich ja beim Auftreten der Markierung stoppe. Da könnte man die Definition verändern, hätte dann aber das Problem, dass der Aufrufer beim erhaltenen String nochmal prüfen müsste, ob da am Ende Zeichen übrig sind, die er nicht gebrauchen kann. Ein dritter Modus wäre hier denkbar, aber das hab ich einfach mal außen vor gelassen und nicht mehr eingebaut. Möglicherweise (oder: hoffentlich) kann man das alles sowieso mit weniger Code programmieren...
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@snafu: Deine Funktion hat jetzt noch Probleme, falls die Endmarkierung im selben Chunk wie die Startmarkierung steht, und das der letzte Chunk war. Außerdem ist die Funktion sehr speziell, mehrere Blöcke mit Endmarkierung kann man nicht lesen.

Eigentlich ist das Problem das selbe wie bei jeder readline-Funktion, nur dass das Zeileendezeichen variabel ist. Dazu braucht man eine Klasse, um die zu viel gelesenen Bytes irgendwo zu speichern:

Code: Alles auswählen

from functools import partial
import numpy
import cv2

class BlockReader(object):
    def __init__(self, stream, chunksize=1024):
        self.stream = stream
        self.chunksize = chunksize
        self.buffer = b""
        
    def read_until(self, end_of_block, include_marker=True, discard=False):
        result = []
        buffer = self.buffer
        read = partial(self.stream.read, self.chunksize)
        while True:
            idx = buffer.find(end_of_block)
            if idx >= 0:
                break
            data = read()
            if not data:
                raise IOError("%s not found" % end_of_block)
            if not discard:
                result.append(buffer[:-len(end_of_block)+1])
            buffer = buffer[-len(end_of_block) + 1:] + data
        if include_marker:
            idx += len(end_of_block)
        self.buffer = buffer[idx:]
        if discard:
            return None
        result.append(buffer[:idx])
        return b"".join(result)

def read_image(stream):
    reader = BlockReader(stream)
    reader.read_until(b'\xff\xd8', False, True)
    data = reader.read_until(b'\xff\xd9')
    buf = np.fromstring(data, dtype=np.uint8)
    return cv2.imdecode(buf, 1)
Antworten