reicht Python für meinen Zweck?

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
taugenix
User
Beiträge: 4
Registriert: Montag 13. Mai 2013, 16:24

Hi,

bin absoluter Python-Noob, deswegen hoffe ich,dass ihr mir meine Naivität verzeiht. Über ein Skript aus dem Internet bin ich zu python gekommen und dachte mir: bei der vielzahl an scripten (vor allem im wissenschaftlichen bereich), muss doch was dran sein an python.
Konkret geht es mir darum eine Datei (*.xyz-datei für z.b. jmol) in eine andere (*.vtk) zu konvertieren.

Folgendes skript erledigt den job relativ gut:

Code: Alles auswählen

#!/usr/bin/env python

"""
Converts xyz point cloud into something renderable
Original code by Rod Holland & Prabhu Ramachandran
Handy to visualise raw ascii column file

questions: a.steuwer@ill.fr (2003)

feel free to simplify the textfile interactions. 
it doesn't really require scientific/numeric to 
be installed to do what it does.
"""

#~ from Scientific.IO.TextFile import TextFile
#~ from Numeric import *
import sys
import string
from sys import argv


if len(argv)>1: 
        filename = argv[1]
        output = str(filename[:-3])+"vtk"
        print "output to:", output
        
else :
        print """Usage: 'python xyz2vtk.py pointclouddatafile'
Converts xyz file into vtk UNSTRUCTURED GRID format
for point cloud rendering."""
        sys.exit(0)

                # change i for headers in ascii file
i=0
x=[]
y=[]
z=[]
f = open(filename,'r')
for line in f.readlines():
        words = string.split(line)
        i=i+1
        if i>0:
            if len(words)> 0:
                #for j in range(len(words)):
                    x.append(words[0])
                    y.append(words[1])
                    z.append(words[2])
            
n=len(x)
print "n:",n
                    # write data to vtk polydata file
                    # write header
out = open(output, 'w')
h1 = """# vtk DataFile Version 2.0
loop
ASCII
DATASET UNSTRUCTURED_GRID
POINTS """ + str(n) + """ float
"""
out.write(h1)
                    # write xyz data
for i in range(n):
        #s = '%15.2f %15.2f %15.2f' % (x[i], y[i], z[i])
        out.write(str(x[i])+" "+str(y[i])+" "+str(z[i])+'\n')
        
                    # write cell data
out.write("CELLS "+ str(n)+ " "+ str(2*n)+'\n')
for i in range(n):
        #s = '1 %d \n' % (i)
        out.write("1 "+str(i)+"\n")
        
                    # write cell types
out.write("CELL_TYPES " + str(n)+'\n')
for i in range(n): out.write("1 \n")

                    # write z scalar values
h2 = '\n' + """POINT_DATA """ + str(n) + "\n" 
h3 = """SCALARS Z_Value float 1
LOOKUP_TABLE default"""
out.write(h2+ h3+'\n')
for i in range(n):
        sc=(z[i])
        out.write(str(sc)+ "\n")

                   
out.write('\n')
out.close()
Größere Dateien (~100 MB) überfordern das skript allerdings,was es für mich unbrauchbar macht.
Deswegen meine Frage:ist es irgendwie möglich (mit möglichst wenig aufwand) das skript derart anzupassen, dass die Variablen nicht in einem statischen Array zwischengespeichert werden (das passiert hier doch,oder), sondern direkt aus der Quelldatei gestreamt, benutzt und wieder ausgeschrieben werden?

Ich Frage desshalb, weil bevor ich mich näher damit befasse, ich auch auf nummer sicher gehen will,dass das auch funktioniert. Ansonsten benutze ich c++,weil es damit 100% funktioniert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Wo auch immer du den Code ausgegraben hast, der ist wirklich grausam und hat, abgesehen von der Syntax, nicht viel mit Python zu tun. Im Prinzip ein gutes Beispiel dafür, was man alles nicht tun sollte ...

In der Tat werden alle Daten in einer Liste gespeichert, was bei 100-MB-Dateien allerdings nicht besonders tragisch ist, denn so viel ist das an sich auch nicht. Die Daten sofort zu schreiben ist prinzipiell, auch in C++, gar nicht möglich, da offensichtlich ein Header die Anzahl der Punkte benötigt. Egal in welcher Sprache, musst du die Daten speichern oder vorher einen Durchlauf zum zählen machen. Was besser ist, hängt natürlich von den Daten ab.

Zumindest gibt es in dem Script keine Stellen, welche darauf hindeuten, dass die Performance nun besonders einbrechen müsste. Von der Komplexität bekommst du es mit C++ auch nicht besser hin. Wahrscheinlich bremst hier irgendwo die Hardware (Festplatte) oder das Buffering der vielen kleinen Schreibvorgänge geht schief. Da müsstest du aber mal durchmessen.
Das Leben ist wie ein Tennisball.
BlackJack

@taugenix: Was heisst überfordern denn genau? Läuft es zu lange oder wird zu viel Speicher verbraucht? Wie EyDu schon sagte sollte der Speicher eigentlich ausreichen, allerdings gibt es ein paar einfache Möglichkeiten den etwas zu drücken. Das Programm verwendet zum Beispiel `range()` mit einem potentiell grossen Wert als Argument, was völlig unnötigerweise eine Liste mit vielen Zahlen im Speicher erstellt. `xrange()` dagegen hat nur einen kleinen Speicherverbrauch, der unabhängig vom Argument ist.

Letztlich ist dieser Index aber sowieso „unpythonisch”, weil man direkt über die Elemente von Sequenzen iterieren kann, ohne den Umweg über einen Index. Dazu sollte man dann auch zusammengehörige Werte nicht in *verschiedene* Listen stecken. Das könnte dann eventuell auch noch einmal ein bisschen Speicher einsparen.

Es sind übrigens Listen und keine statischen Arrays.

Das Skript ist anscheinend schon etwas älter wenn man sich mal die auskommentierten Importe anschaut. `Numeric` ist tot, der Nachfolger heisst `numpy` und das wäre auch ein Weg den Code effizienter zu machen. Mindestens was den Speicherverbrauch angeht, und wahrscheinlich auch die Laufzeit.

Die Funktionen aus dem `string`-Modul, die es auch als Methoden auf Zeichenketten gibt, sind „deprecated” und deshalb sollte man die schon seit Ewigkeiten nicht mehr verwenden.

Die Konvention für die Einrücktiefe ist vier Leerzeichen pro Ebene. Wenn man davon abweicht wird es schwierig mit anderen zusammen zu arbeiten, weil die Einrückung in Python eine Bedeutung für den Compiler hat. Zusammen arbeiten schliesst auch Quelltext hier zeigen mit ein, denn nicht jeder hat Lust Code auszuprobieren und Fehler zu suchen wenn er erst seinen Editor umkonfigurieren oder den Quelltext anpassen muss.

Bei sauberem Code solle man sich auf Modulebene auf die Definition von Konstanten, Funktionen und Klassen beschränken und den direkt ausgeführten Code dort minimieren. Das hat in CPython auch den netten Nebeneffekt, dass die Namensauflösung schneller wird, denn lokale Namen können schneller nachgeschlagen werden als Modulglobale.

Einige der `str()`-Aufrufe sind unnötig weil sie auf Werte angewendet werden, die bereits Zeichenketten *sind*.

Die Operation mit dem Dateinamen ist nicht robust. Die geht davon aus, dass genau die letzten drei Zeichen des angegebenen Dateinamens die Dateinamensendung sind. Die kann aber auch kürzer oder länger sein, sogar gar nicht existieren. Das `os.path`-Modul bietet da ein paar nütliche Funktionen für Dateinamen und -pfade.

`i` beim Einlesen hat keinen Effekt ausser Laufzeit zu verbrauchen.

Ein- oder zweibuchstabige Namen reichen meistens nicht aus um einen Wert vernünftig zu beschreiben.

Die Eingabedatei wird nicht wieder geschlossen. Am besten verwendet man die ``with``-Anweisung um das sicher zu stellen. Ausserdem wird die Eingabedatei mit `readlines()` komplett in eine Liste mit Zeilen in den Arbeitsspeicher gelesen, statt die Zeilen einzeln zu lesen und zu verarbeiten.

Überarbeitet (aber ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
"""Converts xyz point cloud into something renderable
Original code by Rod Holland & Prabhu Ramachandran
Handy to visualise raw ascii column file

questions: a.steuwer@ill.fr (2003)

feel free to simplify the textfile interactions. 
"""
import os
import sys


def main():
    if len(sys.argv) == 2:
        xyz_filename = sys.argv[1]
        vtk_filename = '{0}{1}{2}'.format(
            os.path.splitext(xyz_filename)[0], os.extsep, '.vtk'
        )
        print 'output to:', vtk_filename
    else:
        print '''Usage: 'python xyz2vtk.py pointclouddatafile'
    Converts xyz file into vtk UNSTRUCTURED GRID format
    for point cloud rendering.'''
        sys.exit(0)

    coordinates = list()
    with open(xyz_filename) as xyz_file:
        for line in xyz_file:
            words = line.split()
            if words:
                coordinates.append(tuple(words[:3]))

    coordinate_count = len(coordinates)
    print '# of coordinates:', coordinate_count
    with open(vtk_filename, 'w') as vtk_file:
        vtk_file.write(
            '# vtk DataFile Version 2.0\n'
            'loop\n'
            'ASCII\n'
            'DATASET UNSTRUCTURED_GRID\n'
            'POINTS {0} float\n'.format(coordinate_count)
        )
        vtk_file.writelines(' '.join(xyz) + '\n' for xyz in coordinates)
        vtk_file.write(
            'CELLS {0} {1}\n'.format(coordinate_count, coordinate_count * 2)
        )
        vtk_file.writelines(
            '1 {0}\n'.format(i) for i in xrange(coordinate_count)
        )
        vtk_file.write('CELL_TYPES {0}\n'.format(coordinate_count))
        vtk_file.write('1\n' * coordinate_count)
        vtk_file.write(
            '\nPOINT_DATA {0}\n'
            'SCALARS Z_Value float 1\n'
            'LOOKUP_TABLE default\n'.format(coordinate_count)
        )
        vtk_file.writelines('{0}\n'.format(xyz[2]) for xyz in coordinates)
        vtk_file.write('\n')


if __name__ == '__main__':
    main()
taugenix
User
Beiträge: 4
Registriert: Montag 13. Mai 2013, 16:24

hi,

danke erstmal für die beiträge. Das Problem bei dem script ist, dass ich bei großen dateien folgenden Fehler erhalte:

Code: Alles auswählen

output to: 81.vtk
Traceback (most recent call last):
  File "../scripts/xyz2vtk.py", line 46, in <module>
    y.append(words[1])
IndexError: list index out of range
Die überarbeitet Version sieht auf den ersten Blick deutlich professioneller aus (ist es bestimmt auch,vielen dank dafür), aber ich erhalte wieder einen "out of range" fehler:

Code: Alles auswählen

output to: test..vtk
# of coordinates: 1478604
Traceback (most recent call last):
  File "../scripts/xyz2vtk_2.py", line 64, in <module>
    main()
  File "../scripts/xyz2vtk_2.py", line 59, in main
    vtk_file.writelines('{0}\n'.format(xyz[2]) for xyz in coordinates)
  File "../scripts/xyz2vtk_2.py", line 59, in <genexpr>
    vtk_file.writelines('{0}\n'.format(xyz[2]) for xyz in coordinates)
IndexError: tuple index out of range


Woran kann das liegen?
BlackJack

@taugenix: Es gibt anscheinend in der Eingabedatei Zeilen mit weniger als 3 „Wörtern”. Das hat aber überhaupt nichts mit der Grösse der Eingabedatei zu tun. Also müsste man beim Einlesen aus dem ``if words:`` ein ``if len(words) >= 3:`` machen. Und hoffen das all diese Zeilen am Anfang drei Zahlen enthalten, die das passende enthalten.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@taugenix, BlackJack: Deutlich besser wäre es, Zeilen, die nicht exakt drei Zahlen enthalten mit einer Warnung auszugeben, um zu sehen, ob die Datei korrupt ist, oder sonstwie das Dateiformat nicht den Konventionen entspricht.

Code: Alles auswählen

    coordinates = list()
    with open(xyz_filename) as xyz_file:
        for line in xyz_file:
            words = line.split()
            if len(words)!=3:
                print "Warning: Line has the wrong format:", repr(line)
            else:
                coordinates.append(tuple(words[:3]))
taugenix
User
Beiträge: 4
Registriert: Montag 13. Mai 2013, 16:24

Stimmt,ihr habt recht. Habe tatsächlich eine Zeile mit nur 1 Spalte gefunden. Jetzt klappts wunderbar. Danke nochmal
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Fazit: Python reicht für deinen Zweck :twisted:
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
taugenix
User
Beiträge: 4
Registriert: Montag 13. Mai 2013, 16:24

Leonidas hat geschrieben:Fazit: Python reicht für deinen Zweck :twisted:
Durchaus. Nur finde ich die syntax noch immer sehr befremdlich.
Habe da noch ein Frage.

ich möchte den Code nun etwas anpassen: Meine xyz-Datei hat nun 9 Spalten.
die x,y,z-Koordinaten sind Spalte 5,Spalte 7 und Spalte 7.
Zusätzlich brauche ich Spalte 9 (Temperatur)

versuch habe ich folgendes:

Code: Alles auswählen

...
coordinates.append(tuble(words[:9]))  #die ersten 9 Spalten interessieren mich
...
vtk_file.writelines(' '.join(xyz) + '\n' for xyz in coordinates)  <-- Hier habe ich keinen Schimmer wie ichs hinbekommen, dass nur die Spalten 5,6 und 7 geschrieben werden
...
vtk_file.write( '\nPOINT DATA {0}\n'
                    'SCALARS Temp float 1\n'
                    'LOOKUP_TABLE default\n'.format(coordinate_count)  
)
vtk_file.writelines('{0}\n'.format(xyzT[4]) for xyzT in coordinates) #das ist quatsch, wie wird nur die 9.Spalte geschrieben??
vtk_file.write('\n')
denke mal, das ist nicht besonders schwer, wenn man sich erstmal durch das Dickicht aus Objekten und Methoden durchgekämpft hat (die Befehle sind doch methoden,oder?)
BlackJack

@taugenix: Du kannst das doch einfach mal live ausprobieren.

Code: Alles auswählen

In [6]: a
Out[6]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [7]: a[4:7]
Out[7]: [5, 6, 7]

In [8]: a[8]
Out[8]: 9
Wobei ich in `coordinates` nur die vier Werte in den Elementen speichern würde, die nachherauch wirklich benötigt werden. also nur die Spalten 5, 6, 7, und 9 in diesem Fall.

„Befehle” sind eigentlich eher so etwas wie die ``print``-Anweisung oder ``import``. Also Sachen die der Sprache/Laufzeitumgebung etwas „befehlen” was man nicht selber Programmieren könnte ohne die Sprache selbst zu verändern. Funktionen und Methoden dagegen kann man selber in Python programmieren. Letztlich ist das bei C++ doch vom Konzept her gar nicht so unterschiedlich mit den Methoden.
Antworten