2011-09-09 12 views
5

Ho immagini PGM a 16 bit che sto cercando di leggere in Python. Sembra (?) Come PIL non supporta questo formato?PGM a 16 bit PGM

import Image 
im = Image.open('test.pgm') 
im.show() 

Mostra approssimativamente l'immagine, ma non è corretta. Ci sono bande scure in tutto e img è segnalato per avere mode=L. Penso che questo sia legato a una prima domanda che ho avuto su 16-bit TIFF files. 16-bit è così raro che PIL non lo supporta? Qualche consiglio su come posso leggere i file PGM a 16-bit in Python, usando il PIL o un'altra libreria standard o un codice sviluppato in proprio?

risposta

1

Di seguito dipende solo numpy per caricare l'immagine, che può essere a 8 bit o PGM/PPM raw a 16 bit. Ho anche mostrato un paio di modi diversi per visualizzare l'immagine. Quella che usa PIL (import Image) richiede che i dati vengano prima convertiti in 8 bit.

#!/usr/bin/python2 -u 

from __future__ import print_function 
import sys, numpy 

def read_pnm_from_stream(fd): 
    pnm = type('pnm',(object,),{}) ## create an empty container 
    pnm.header = fd.readline() 
    pnm.magic = pnm.header.split()[0] 
    pnm.maxsample = 1 if (pnm.magic == 'P4') else 0 
    while (len(pnm.header.split()) < 3+(1,0)[pnm.maxsample]): s = fd.readline() ; pnm.header += s if (len(s) and s[0] != '#') else '' 
    pnm.width, pnm.height = [int(item) for item in pnm.header.split()[1:3]] 
    pnm.samples = 3 if (pnm.magic == 'P6') else 1 
    if (pnm.maxsample == 0): pnm.maxsample = int(pnm.header.split()[3]) 
    pnm.pixels = numpy.fromfile(fd, count=pnm.width*pnm.height*pnm.samples, dtype='u1' if pnm.maxsample < 256 else '>u2') 
    pnm.pixels = pnm.pixels.reshape(pnm.height,pnm.width) if pnm.samples==1 else pnm.pixels.reshape(pnm.height,pnm.width,pnm.samples) 
    return pnm 

if __name__ == '__main__': 

## read image 
# src = read_pnm_from_stream(open(filename)) 
    src = read_pnm_from_stream(sys.stdin) 
# print("src.header="+src.header.strip(), file=sys.stderr) 
# print("src.pixels="+repr(src.pixels), file=sys.stderr) 

## write image 
    dst=src 
    dst.pixels = numpy.array([ dst.maxsample-i for i in src.pixels ],dtype=dst.pixels.dtype) ## example image processing 
# print("dst shape: "+str(dst.pixels.shape), file=sys.stderr) 
    sys.stdout.write(("P5" if dst.samples==1 else "P6")+"\n"+str(dst.width)+" "+str(dst.height)+"\n"+str(dst.maxsample)+"\n"); 
    dst.pixels.tofile(sys.stdout) ## seems to work, I'm not sure how it decides about endianness 

## view using Image 
    import Image 
    viewable = dst.pixels if dst.pixels.dtype == numpy.dtype('u1') else numpy.array([ x>>8 for x in dst.pixels],dtype='u1') 
    Image.fromarray(viewable).show() 

## view using scipy 
    import scipy.misc 
    scipy.misc.toimage(dst.pixels).show() 

Note di utilizzo

  • alla fine ho capito "come si decide su endianness" - in realtà è memorizzare l'immagine nella memoria come big-endian (piuttosto che nativo). Questo schema potrebbe rallentare qualsiasi elaborazione di immagini non banale, sebbene altri problemi di prestazioni con Python possano relegare questo problema a una banalità (vedi sotto).

  • Ho fatto una domanda relativa al problema di endianità here. Ho anche avuto alcune interessanti confusioni relative a endianness con questo perché stavo testando preelaborando l'immagine con pnmdepth 65535 che non è buona (di per sé) per testare l'endianness dato che i byte basso e alto potrebbero finire per essere uguali (non l'ho fatto avviso subito perché le uscite print(array) decimale). Avrei dovuto applicare anche pnmgamma per risparmiarmi un po 'di confusione.

  • Poiché Python è così lento, numpy cerca di essere subdolo intelligente su come si applica alcune operazioni (vedi broadcasting). La prima regola empirica per l'efficienza con numpy è let numpy gestisce l'iterazione per voi (o in un altro modo don't write your own for loops). La cosa divertente nel codice sopra è che segue solo parzialmente questa regola quando si esegue "l'elaborazione dell'immagine di esempio", e quindi le prestazioni di quella linea hanno un'estrema dipendenza dai parametri che sono stati dati a reshape.

  • Il prossimo grande numpy endianness mistero: Perché newbyteorder() sembrano return an array, quando è documented per restituire un dtype. Questo è rilevante se si desidera convertire in nativo endian con dst.pixels=dst.pixels.byteswap(True).newbyteorder().

  • Suggerimenti per il porting di Python 3: binary input with an ASCII text header, read from stdin

+0

Perché il tentativo di scrivere programmi Python apparentemente banali sembra sempre sfociare in un'odissea attraverso Stack Overflow? – nobar

+0

Una delle cose che mi fa impazzire di Python sono le copie superficiali, come 'dst = src' sopra. A volte penso che Python sia troppo difficile da capire per un programmatore C++. – nobar

+0

... Ho trovato alcune delle risposte più votate [qui] (http://stackoverflow.com/questions/9541025/how-to-copy-a-python-class) per essere il più utile. In particolare, sembra che posso risolvere il mio problema sopra facendo 'dst = src()'. – nobar

4

È necessaria una modalità di "L;16"; tuttavia sembra che PIL abbia una modalità di "L" codificata in File.c durante il caricamento di un PGM. Dovresti essere write your own decoder se vuoi essere in grado di leggere un PGM a 16 bit.

Tuttavia, il supporto di immagini a 16 bit sembra ancora inaffidabili:

>>> im = Image.fromstring('I;16', (16, 16), '\xCA\xFE' * 256, 'raw', 'I;16') 
>>> im.getcolors() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "/usr/lib/python2.6/dist-packages/PIL/Image.py", line 866, in getcolors 
    return self.im.getcolors(maxcolors) 
ValueError: image has wrong mode 

ritengo PIL è in grado di leggere immagini con 16 bit, ma in realtà la memorizzazione e la manipolazione di loro è ancora in fase sperimentale.

>>> im = Image.fromstring('L', (16, 16), '\xCA\xFE' * 256, 'raw', 'L;16') 
>>> im 
<Image.Image image mode=L size=16x16 at 0x27B4440> 
>>> im.getcolors() 
[(256, 254)] 

See, semplicemente interpretato come il valore 0xCAFE0xFE, che non è esattamente corretto.

+0

sono felice di leggerli solo. Se ho bisogno di scrivere userò PNG. Sto anche bene manipolandoli come dati in numpy piuttosto che come immagine in PIL. Il tuo post è stato utile, ma puoi approfondire come posso leggere correttamente i dati? – mankoff

+0

Intendi scrivere un decoder per PIL, o come interpretare il PGM? –

+0

Il tuo '' 'lettura' '' in corsivo mi ha fatto pensare che ci sia qualche trucco per farlo funzionare così com'è? Sto cercando di adattare il work-around qui (http://stackoverflow.com/questions/7247371/python-and-16-bit-tiff) ma senza perdere i bit. Se è necessario un decodificatore personalizzato, lo scriverò in base al tutorial PIL. Il formato PGM sembra piuttosto semplice, quindi forse dovrei leggerlo direttamente in numpy ... – mankoff

1

Ecco un generico lettore PNM/PAM sulla base di NumPy e una funzione non documentata in PyPNG.

def read_pnm(filename, endian='>'): 
    fd = open(filename,'rb') 
    format, width, height, samples, maxval = png.read_pnm_header(fd) 
    pixels = numpy.fromfile(fd, dtype='u1' if maxval < 256 else endian+'u2') 
    return pixels.reshape(height,width,samples) 

Naturalmente scrittura questo formato di immagine in genere non richiede l'assistenza di una biblioteca ...

+0

Ho preso in prestito alcune idee da [questa domanda correlata] (http://stackoverflow.com/questions/7368739/numpy-and-16-bit-pgm). – nobar

+0

Per quanto riguarda il supporto 'PAM', la funzione 'read_pnm_header()' qui usata non restituisce 'TUPLTYPE', ma restituisce il valore corretto per' DEPTH' (che ho chiamato 'samples'). – nobar

+0

Vedere [questa domanda] (http://stackoverflow.com/questions/2850893/reading-binary-data-from-stdin) per le note importanti sull'utilizzo di stdio anziché di un file. – nobar

Problemi correlati