2013-01-22 12 views
6

Ich habe ein Bild, gespeichert in einem numply Array von uint8 s, von Form (planes, rows, cols). Ich muss es mit den Werten vergleichen, die in einer Maske gespeichert sind, auch von uint8 s, von Form (mask_rows, mask_cols). Während das Bild sehr groß sein kann, ist die Maske normalerweise klein, normalerweise (256, 256) und ist über zu kacheln. Um den Code zu vereinfachen, lassen Sie uns so vorgehen, dass rows = 100 * mask_rows und cols = 100 * mask_cols.Alternative zu `numpy.tile` für periodische Maske

So wie ich im Moment bin Schwellwertbildung Handhabung ist so etwas wie diese:

out = image >= np.tile(mask, (image.shape[0], 100, 100)) 

Die größte Array ich auf diese Weise verarbeiten kann, bevor mit einem MemoryError ins Gesicht geschlagen zu werden als (3, 11100, 11100) etwas größer ist. So wie ich es mir vorgestellt habe, habe ich auf diese Weise bis zu drei ginormous Arrays, die nebeneinander im Speicher vorhanden sind: image, das gekachelte mask und meine Rückkehr out. Aber die gekachelte Maske ist dasselbe kleine Array, das über 10.000 Mal kopiert wurde. Also, wenn ich diesen Speicher sparen könnte, würde ich nur 2/3 des Speichers verwenden, und sollte in der Lage sein, Bilder 3/2 größer zu bearbeiten, also der Größe um (3, 13600, 13600). Dies ist, nebenbei bemerkt, im Einklang mit dem, was ich, wenn ich die Schwellwertbildung an Ort und Stelle mit

np.greater_equal(image, (image.shape[0], 100, 100), out=image) 

My (nicht) Versuch machen bei der periodischen Natur der mask Nutzung größere Arrays zu verarbeiten mask mit periodisch Index wurde linearer Arrays:

mask = mask[None, ...] 
rows = np.tile(np.arange(mask.shape[1], (100,))).reshape(1, -1, 1) 
cols = np.tile(np.arange(mask.shape[2], (100,))).reshape(1, 1, -1) 
out = image >= mask[:, rows, cols] 

für kleine Arrays tut es das gleiche Ergebnis wie die andere produzieren, wenn auch mit etwas von einer 20x Verlangsamung (!!!), aber es schrecklich für die größeren Größen nicht erfüllt. Anstelle eines MemoryError stürzt es schließlich Python, sogar für Werte, die die andere Methode ohne Probleme behandelt.

Was ich denke, passiert ist, dass numpy eigentlich nur die (planes, rows, cols) Array Index baut mask, so nicht gibt es keine Speichereinsparung, aber da es eine Reihe von int32 s ist, es nimmt tatsächlich viermal mehr Platz speichern ...

Irgendwelche Ideen, wie man das macht? Um Ihnen die Mühe zu ersparen, unter einem Sandbox-Code finden zu spielen, um mit:

import numpy as np 

def halftone_1(image, mask) : 
    return np.greater_equal(image, np.tile(mask, (image.shape[0], 100, 100))) 

def halftone_2(image, mask) : 
    mask = mask[None, ...] 
    rows = np.tile(np.arange(mask.shape[1]), 
        (100,)).reshape(1, -1, 1) 
    cols = np.tile(np.arange(mask.shape[2]), 
        (100,)).reshape(1, 1, -1) 
    return np.greater_equal(image, mask[:, rows, cols]) 

rows, cols, planes = 6000, 6000, 3 
image = np.random.randint(-2**31, 2**31 - 1, size=(planes * rows * cols // 4)) 
image = image.view(dtype='uint8').reshape(planes, rows, cols) 
mask = np.random.randint(256, 
         size=(1, rows // 100, cols // 100)).astype('uint8') 

#np.all(halftone_1(image, mask) == halftone_2(image, mask)) 
#halftone_1(image, mask) 
#halftone_2(image, mask) 

import timeit 
print timeit.timeit('halftone_1(image, mask)', 
        'from __main__ import halftone_1, image, mask', 
        number=1) 
print timeit.timeit('halftone_2(image, mask)', 
        'from __main__ import halftone_2, image, mask', 
        number=1) 

Antwort

6

würde ich fast haben Sie eine rolling window Art von Trick spitz, aber für diese einfache nicht-überlappende Sache, normal reshape tut es einfach auch. (Die Reshapes hier sind sicher, numpy wird nie eine Kopie für sie)

def halftone_reshape(image, mask): 
    # you can make up a nicer reshape code maybe, it is a bit ugly. The 
    # rolling window code can do this too (but much more general then reshape). 
    new_shape = np.array(zip(image.shape, mask.shape)) 
    new_shape[:,0] /= new_shape[:,1] 
    reshaped_image = image.reshape(new_shape.ravel()) 

    reshaped_mask = mask[None,:,None,:,None,:] 

    # and now they just broadcast: 
    result_funny_shaped = reshaped_image >= reshaped_mask 

    # And you can just reshape it back: 
    return result_funny_shaped.reshape(image.shape) 

Und da Timings sind alles (nicht wirklich, aber ...):

In [172]: %timeit halftone_reshape(image, mask) 
1 loops, best of 3: 280 ms per loop 

In [173]: %timeit halftone_1(image, mask) 
1 loops, best of 3: 354 ms per loop 

In [174]: %timeit halftone_2(image, mask) 
1 loops, best of 3: 3.1 s per loop 
+0

Schöne! Ich schlug mir eine ähnliche Lösung vor, hatte aber Mühe, die Reihenfolge der zusätzlichen Dimensionen richtig zu bestimmen. Wenn ich könnte, würde ich dir einen zusätzlichen +1 für den Link zum Rolling-Window-Trick geben. Und nur um dies zu bestätigen: Dies erfordert, dass die Maske das Bild genau teilt, wenn nicht, nehme ich an, dass das Bild auffüllen, bis es geht, und dass es an Ort und Stelle mit "Bild" gemacht werden kann. Größe ändern, oder? – Jaime

+0

@Jaime, stimmt, aber wenn Sie padieren müssen, müssen Sie kopieren (es * * * möglich sein, innerhalb des gleichen Datensegments zu kopieren, aber wirklich realistisch bleiben ...). Das einzige, was zu vermeiden wäre, wäre etwas "scipy" zu benutzen.ndimage' tool oder handle explizit mit den Grenzen. Und die ndimage Dinge werden wahrscheinlich alle die Maske überlappen. – seberg

+0

@Jaime OK, nvm ... es sieht so aus, als würde die Größenanpassung tatsächlich dazu führen, dass diese Erinnerung sich bewegt (wenn du Glück hast) – seberg

Verwandte Themen