Python (12.11.2019 - 14.11.2019 in Graz)

Eine Einführung in Python und NumPy, zur Beantwortung der Frage, “Ist die Herrschaft von MATLAB absolut?”. Gestellt von einer Firma aus dem Raum Graz, gehalten in den Schulungsräumen von tecTrain (Graz), über die der Kurs gebucht wurde. Platzhirsche sind schwer zu schlagen, haben wir herausgefunden, aber Python ist auf einem sehr guten Weg. Spass hatten wir allemal.

“Die Schulung war unglaublich lustig und hat mir einen sehr guten Überblick über Python vermittelt, ich habe gelernt wie ich es auf für mich relevante Probleme anwende und was es dabei zu beachten gilt.

Zudem war der Kursleiter sehr sympathisch und kompetent. Ich würde es also jederzeit weiterempfehlen bzw. wiederholen.”

Danke Christoph, sowas nettes hat noch nie jemand zu mir gesagt!

Standardthemen

Die Datentypen, und das “Normale” an Python waren schnell erklärt. Ich kanns nicht lassen, bevor die Anfängerthemen vorbei sind, auf dem Besten von Python herumzureiten: Iteration und Generatoren. Hier hatte ich eine kleine Demo gehackt - zum x-ten mal. Das ganze Fibonacci-Zeug gibts jetzt als Live-Hacking-Screenplay

Das Hauptthema: Numerik, NumPy

Siehe dazu auch ein Jupyter Notebook “BigPlan” (download).

Der Chef hat mir bei einem Vorgespräch ein Übungsbeispiel für die Teilnehmer mitgegeben: ausgehend von einem Spektralbild (sagt man so?), verwende den K-Means Clusteringalgorithmus, um die Bereiche auf dem Bild zu kategorisieren (die abgebildeten Stücke zu erkennen).

Ich hab mir erlaubt, für die Kursvorbereitung [1] so quasi als Appetizer das Problem etwas zu reduzieren: Farbreduktion eines Bildes (auf 8 Farben). Das Programm (siehe unten) verwendet

  • Pillow, um Bilddaten zu lesen und schreiben. Die Library interoperiert nahtlos mit NumPy, was sicher kein Zufall ist.

  • NumPy, um die Alpha-Plane des Ausgangsbildes abzuschneiden und zu restoren.

  • Den K-Means Algorithmus aus scikit-learn, um das Clustering jemand anders machen zu lassen.

../../../../_images/veggie.png

Ausgangsbild veggie.png: viele Farben

../../../../_images/veggie-reduced.png

Reduziertes Bild veggie-reduced.png: Acht Farben

Das Programm ist überschaubar - es verwendet nur Libraries und macht nichts selbst (das ist der Plan, immer, beim Programmieren). Die Ausdrucksstärke von Python macht sich hier bemerkbar durch z.B. die Slice-Syntax (wegschneiden der Alpha-Plane), oder beim Iterieren mittels enumerate().

#!/usr/bin/env python

import numpy
import PIL.Image
from sklearn.cluster import KMeans

import sys


img = PIL.Image.open('veggie.png')

imgarray = numpy.array(img)
nrows, ncols, nrgba = imgarray.shape

# disregard alpha plane for clustering
alpha = imgarray[:,:,3:]
rgb = imgarray[:,:,0:3]

# change view to a linear sequence of (r,g,b) points. (K-Means cannot
# take arbitrarily dimensioned spaces, he likes it linear.)
linear_rgb = rgb.reshape((nrows*ncols, 3))

km = KMeans(n_clusters=8)
km.fit(linear_rgb)

# reduce: let cluster centers be their members
for idx, label in enumerate(km.labels_):
    linear_rgb[idx] = km.cluster_centers_[label]

# stack saved alpha plane on top of it
imgarray = numpy.concatenate((rgb, alpha), axis=2)

reduced_img = PIL.Image.fromarray(imgarray, 'RGBA')
reduced_img.save('veggie-reduced.png')

Lesen von .mat Files

Das Spektralbild liegt im .mat Format vor - was immer das ist, hat wahrscheinlich mit MATLAB zu tun. Etwas Recherche hat ergeben, dass die Funktion scipy.io.loadmat() das kann. Hier ein kleines Testprogramm,

#!/usr/bin/python

from scipy.io import loadmat
import sys


mat = loadmat(sys.argv[1])

print(type(mat['imnData']))
print(mat['imnData'])
print(mat['imnData'].dtype)

# obviously "imnData" is what we want
data = mat['imnData']
# ...

Lösen einer Uni-Übung

Eine Teilzeitmitarbeiterin der Firma, sie studiert Physik neben der Arbeit, muss für eine Übung … was weiss ich … machen. Wie auch immer, der Input für ihre Arbeit liegt in folgendem bekackten Inputformat vor, das es zu parsen gilt. War eine nette Zwischendurch-Gruppenarbeit.

 ---------------------------- Atom information ----------------------------
     atom    #        X              Y              Z            mass
 --------------------------------------------------------------------------
    O        1  0.0000000E+00  0.0000000E+00 -1.2662399E-01  1.5994910E+01
    H        2  1.4391972E+00  0.0000000E+00  1.0048070E+00  1.0078250E+00
    H        3 -1.4391972E+00  0.0000000E+00  1.0048070E+00  1.0078250E+00
 --------------------------------------------------------------------------


          --------------------------------------------------------
          MASS-WEIGHTED PROJECTED HESSIAN (Hartree/Bohr/Bohr/Kamu)
          --------------------------------------------------------


               1            2            3            4            5            6            7            8            9
   ----- ----- ----- ----- -----
    1    4.05054E+01
    2   -1.61610E-24  0.00000E+00
    3    1.20781E-07  8.08051E-25  2.83024E+01
    4   -8.06829E+01  4.42629E-24 -4.65256E+01  3.52600E+02
    5   -7.69570E-24  0.00000E+00  2.91733E-24  2.04388E-23  0.00000E+00
    6   -6.34292E+01  8.04780E-25 -5.63758E+01  2.19019E+02  6.41217E-24  2.11622E+02
    7   -8.06829E+01  3.21912E-24  4.65256E+01 -3.11752E+01  1.04198E-23  3.36702E+01  3.52600E+02
    8   -8.14839E-24  0.00000E+00  2.71613E-24  1.96373E-23  8.40456E-19  7.21369E-24  1.24236E-23  0.00000E+00
    9    6.34292E+01 -1.60956E-24 -5.63758E+01 -3.36702E+01 -1.84350E-23  1.29686E+01 -2.19019E+02 -1.92365E-23  2.11622E+02
    

Der Code, mit dem wir nach einigen Runden Nachdenkens einigermaßen zufrieden waren, sieht so aus,

import numpy as np


def load_dat(filename):
    '''load a .dat file (whatever that is) into a numpy matrix and returns
    that matrix.
    '''

    matrix_lines = []

    with open(filename) as f:
        for line in f:
            if '----- ----- ----- ----- -----' in line:
                matrix_lines = f.readlines()
                break
        else:
            raise RuntimeError('file format vergeigt')

    # split matrix_lines into elements
    matrix_elements = []
    for l in matrix_lines:
        if len(l.strip()) == 0:
            continue
        elems = l.split()
        del elems[0] # line-number column, unnecessary
        matrix_elements.append(elems)

    # determine dimensions of (triangular?) matrix 
    x = len(matrix_elements)
    y = max((len(l) for l in matrix_elements))

    matrix = np.zeros((x,y))
    for row_no, row in enumerate(matrix_elements):
        matrix[row_no,0:len(row)] = row

    return matrix

if __name__ == '__main__':
    print(load_dat(sys.argv[1]))

Footnotes