Programowanie Fizyka Medyczna Wykład V 3 kwietnia 2012 Przypomnienie i rozszerzenie wiadomości z poprzednich wykładów
Operacje na typach złożonych,
Standardowe moduły: sys, os
Podstawy definicji klasy, Dziedziczenie:
Dziedziczenie wielokrotne,
super
MRO Method Resolution Order,
Kiedy __init__(), kiedy __new__()? Moduł PyDicom:
Podstawowe operacje: wczytywanie i zapisywanie plików,
Elementy Dicom Tag,
Anonimizacja,
Zmiana, usuwanie i wyszukiwanie elementów, Typy złożone mutable - ściąga Dodawanie elementu Usuwanie elementu Czyszczenie Lista L.append(elem) del L[indeks] del L[:] (list) L.insert(indeks,elem) L.pop(indeks) L.extend(iterable) L.remove(wart) Zbiór S.update(iterable) S.discard(wart) S.clear() (set) S.add(hashable) S.remove(wart) S.pop() Słownik D.update() D.pop(key,default) D.clear() (dictionary) D[klucz] = wart D.popitem(key,default) Ciąg bajtów B.append(liczba_calk) del B[indeks] del B[:] (bytearray) B.insert(indeks,licz_cal) B.pop(indeks) B.extend(iterable_int) B.remove(wart) del Z usuwa dowolną zmienną Z Programowanie FizMed, wykład 5 2 Moduł sys Moduł ten daje nam dostęp do podstawowych informacji o systemie operacyjnym i interpreterze python import sys Niektóre częściej używane funkcje: sys.hexversion Wersja interpretera Python zapisana jako liczba szestnastkowa sys.platform Windows czy Linux, 64bit czy 32bit sys.executable scieżka do interpretera Python sys.argv argumenty wywołania skryptu sys.float_info słowniki przechowujące informacje o szczegółach sys.int_info implementacji typów int i float sys.path lista zawierająca katalogi przeszukiwane w poszukiwaniu plików. sys.modules lista załadowanych modułów Programowanie FizMed, wykład 5 3 Moduł os Moduł do podstawowych operacji w systemie import os os.linesep znak końca linii ('\r' lub '\n') w *nix lub MacOS, '\r\n' w Windows os.path.abspath(s) absolutna ścieżka dla nazwy względnej s os.path.split(s) dzieli ścieżkę s na listę nazw [dysk, katalogi, plik] os.path.join(s,p) łączy dwie części ścieżki w jedną używając poprawnego łącznika os.path.basename(s) zwraca nazwę pliku lub ostatniego katalogu os.path.dirname(s) zwraca wszystko przed basename os.path.exists(s) sprawdza czy dana ścieżka/plik istnieje os.stat(s) informacja o statusie obiektu na dysku (poniżej konkretne pola) os.path.getatime(s) czas ostatniego dostępu do obiektu na dysku getctime(s) utworzenia getmtime(s) modyfikacji os.path.getsize(s) rozmiar pliku os.path.isdir(s) czy jest katalogiem os.path.isfile(s) czy jest plikiem Programowanie FizMed, wykład 5 4 Obiekt jako przedstawiciel klasy class mojaklasa(object): def __init__(self, *args, **kwargs): print("Tu jest inicjalizacja obiektu") def __repr__(self): return "Reprezentacja tekstowa obiektu" def __str__(self): return "Konwersja na string" Mając zdefiniowaną klasę, możemy już utworzyć jej przedstawiciela, czyli obiekt. a = mojaklasa() a jest obiektem klasy mojaklasa. Klasa ta może mieć kilku przedstawicieli. b = mojaklasa() b też jest obiektem klasy mojaklasa. Mówiliśmy już też o metodachnp. __call__() i __getitem__(), __add__() i __bool__(). Programowanie FizMed, wykład 5 5 Dziedziczenie Dziedziczenie (inheritance) jest mechanizmem współdzielenia funkcjonalności między klasami. W Pythonie klasa może dziedziczyć po jednej lub kilku klasach, co oznacza, że oprócz swoich własnych atrybutów oraz zachowań, uzyskuje także te pochodzące z klasy dziedziczonej. Klasa dziedzicząca jest nazywana klasą pochodną lub potomną (w j. angielskim: subclass lub derived class). Klasa, z której następuje dziedziczenie klasą bazową (w ang. superclass). Z jednej klasy bazowej można uzyskać dowolną liczbę klas pochodnych. Klasy pochodne posiadają obok swoich własnych metod i pól, również kompletny interfejs (metody i pola) klasy bazowej. Wszystkie klasy w Python 3 dziedziczą po pierwotnej klasie o nazwie object, nawet jeżeli nie jest to jawnie napisane w definicji klasy. Programowanie FizMed, wykład 5 6 http://pl.wikipedia.org/wiki/Dziedziczenie_(programowanie) Dziedziczenie i hermetyzacja Dziedziczenie pozwala na budowanie skomplikowanych funkcjonalnie klas z prostszych elementów. Klasy bazowe mogą stanowić niezależną, zamkniętą całość, która istnieje bez klasy pochodnej. Klasy pochodne mają dostęp tylko do określonych elementów klas bazowych. Takie podejście nazywamy hermetyzacją lub enkapsulacją. class Pierwsza(): class Druga(object): pass pass Obie klasy Pierwsza i Druga dziedziczą po object, nawet jeżeli w pierwszym przypadku nie jest to jawnie napisane. Programowanie FizMed, wykład 5 7 http://pl.wikipedia.org/wiki/Hermetyzacja_(informatyka) Drzewo klas class Druga(object): class Pierwsza(): def __init__(self): def __init__(self): self.wart = 2 self.wart = 1 pass pass Klasa bazowa object Pierwsza Druga wart = 1 wart = 2 Klasy pochodne Programowanie FizMed, wykład 5 8 Drzewo klas dziedziczenie po klasach pochodnych Klasa bazowa object Pierwsza Druga wart = 1 wart = 2 Klasy pochodne do object class Trzecia(Pierwsza): def __init__(self): Trzecia pass Klasa pochodna do Pierwsza Programowanie FizMed, wykład 5 9 Propagacja inicjalizacji object Pierwsza Druga wart = 1 wart = 2 p = Pierwsza() print(p.wart) # OK wart jest 1 t = Trzecia() print(t.wart) # << błąd !!! Trzecia Czy klasy bazowe są inicjalizowane? Programowanie FizMed, wykład 5 10 "__init__" wywoływany jest tylko raz! class Pierwsza(): p = Pierwsza() def __init__(self): print(p.wart) # OK wart jest 1 self.wart = 1 # bo jest ustawiona w __init__ pass t = Trzecia() class Trzecia(Pierwsza): print(t.wart) # << błąd !!! def __init__(self): pass W klasie głównej znaleziona jest metoda __init__ i to ona jest wykonana p = Pierwsza() class Pierwsza(): print(p.wart) # OK wart jest 1 def __init__(self): # bo jest ustawiona w __init__ self.wart = 1 pass t = Trzecia() class Trzecia(Pierwsza): print(t.wart) # OK jest wart 1 pass # bo __init__ jest z klasy Pierwsza Jeżeli nie ma __init__ w klasie głownej to wykonana jest pierwsza znaleziona. Programowanie FizMed, wykład 5 11 Co zrobić, gdy __init__ musimy mieć również w klasie pochodnej? "Klasa" super class Pierwsza(): def __init__(self): object self.wart = 1 pass super class Trzecia(Pierwsza): def __init__(self): # inicjalizacja klasy Trzecia Pierwsza super(Trzecia, self).__init__() wart = 1 p = Pierwsza() super print(p.wart) # OK wart jest 1 # bo jest ustawiona w __init__ Trzecia t = Trzecia() print(t.wart) # OK Klasa super jest uniwersalnym mechanizmem pozwalającym na inicjalizację klas bazowych z klasy pochodnej. Programowanie FizMed, wykład 5 12 "Diamond inheritance" czyli kłopoty object Pierwsza Druga wart = 1 wart = 2 Czwarta class Czwarta(Pierwsza, Druga): def __init__(self): # inicjalizacja klasy Czwarta # inicjalizacja klas bazowych super(Czwarta,self).__init__() "diamond" # ale w jakiej kolejności??? Programowanie FizMed, wykład 5 13 http://pl.wikipedia.org/wiki/Hermetyzacja_(informatyka) MRO = Method Resultion Order object Pierwsza Druga wart = 1 wart = 2 Czwarta Każda klasa przechowuje kolejność klas używająca mechanizmu super(klasa,self).metoda() Jest to krotka Klasa.__mro__ klas informującą w jakiej kolejności będzie przebiegać wywołanie poprzez super. Programowanie FizMed, wykład 5 14 Aańcuch MRO object Pierwsza Druga wart = 1 wart = 2 Czwarta Pierwsza.__mro__ (, ) Czwarta.__mro__ (, , '__main__.Druga'>, ) Programowanie FizMed, wykład 5 15 Ciągłość łancucha Czwarta.__mro__ (, , '__main__.Druga'>, ) class Pierwsza(): class Druga(object): def __init__(self): def __init__(self): print("In Pier") print("In Dru") self.wart = 1 self.wart = 2 class Czwarta(Pierwsza, Druga): def __init__(self): print("In 4") # inicjalizacja klasy Czwarta # inicjalizacja klas bazowych super(Czwarta,self).__init__() c = Czwarta() In 4 In Pier #A co z resztą MRO? Nie jest ona automatycznie przekazywana dalej Programowanie FizMed, wykład 5 16 Poprawna inicjalizacja wymaga kontynuacji wywołania super Czwarta.__mro__ (, , '__main__.Druga'>, ) class Pierwsza(): class Druga(object): def __init__(self): def __init__(self): print("In Pier") print("In Dru") self.wart = 1 self.wart = 2 super(Pierwsza,self).__init__() super(Druga,self).__init__() class Czwarta(Pierwsza, Druga): def __init__(self): print("In 4") # inicjalizacja klasy Czwarta # inicjalizacja klas bazowych super(Czwarta,self).__init__() c = Czwarta() In 4 In Pier In Dru c.wart Programowanie FizMed, wykład 5 17 2 Podsumownie MRO object Pierwsza Druga wart = 1 wart = 2 Czwarta 1. Prędzej czy pozniej "diamond inheritance" pojawi się w programie. 2. Jeżeli nie zrobimy tego my, może to zrobić nieświadomie ktoś, kto korzysta z naszej klasy. 3. Mechanizm MRO zapewni jednoznaczną kolejność klas pochodnych. 4. Do nas należy poprawne przekazanie wywołań super(Klasa, self).metoda() w górę łańcucha. Programowanie FizMed, wykład 5 18 Przykładowe i bezpieczne definicje klas Dodatkowym mechanizmem zabezpieczającym przed problemami z dziedziczeniem jest obsługa parametrów inicjalizacji. Proszę unikać parametrów pozycyjnych, a zmienne przekazywać poprzez named arguments. class Pierwsza(): def __init__(self, *args, **kwargs ): print("In Pier") self.wart = 1 super(Pierwsza,self).__init__(*args, **kwargs ) class Druga(object): def __init__(self, *args, **kwargs): print("In Dru") self.wart = 2 super(Druga,self).__init__(*args, **kwargs ) class Czwarta(Pierwsza, Druga): def __init__(self, *args, **kwargs): print("In 4") # inicjalizacja klasy Czwarta # inicjalizacja klas bazowych super(Czwarta,self).__init__(*args, **kwargs ) Programowanie FizMed, wykład 5 19 #Przykład z http://fuhm.net/super-harmful/ class A(object): def __init__(self, *args, **kwargs): print ("A" ) super(A, self).__init__(*args, **kwargs) class B(object): object def __init__(self, *args, **kwargs): print ("B" ) super(B, self).__init__(*args, **kwargs) class C(A): def __init__(self, arg, *args, **kwargs): print ("C","arg=",arg) A B super(C, self).__init__(arg, *args, **kwargs) class D(B): def __init__(self, arg, *args, **kwargs ): print ("D", "arg=",arg) super(D, self).__init__(arg, *args, **kwargs) C D class E(C,D): def __init__(self, arg, *args, **kwargs): print( "E", "arg=",arg) super(E, self).__init__(arg, *args, **kwargs) E print (E.__mro__) print( "MRO:", [x.__name__ for x in E.__mro__]) E(10) #E() """MRO: ['E', 'C', 'A', 'D', 'B', 'object'] Programowanie FizMed, wykład 5 20 Metoda __new__ Metoda __init__() nadaje się dobrze do inicjalizacji klas opartych na typach mutable, ponieważ można je zmieniać. Problem pojawia się, gdy nasza klasa ma być oparta na typie immutable na przykład str lub bytes. Jeżeli zmienna takiego typu jest już utworzona, nie można jej zmienić w metodzie __init__ bez zniszczenia całej oryginalnej zawartości. W Pythonie w pojawia się metoda __new__(), która jest odpowiednikiem konstruktora z innych języków obiektowych. Jest ona wywoływana przed metodą __init__(). Podlega takim samym regułom MRO jako __init__(). class Baza1(object): def __new__(klasa, wart): print("In Baza1 __new__,klasa = ", klasa) return super(Baza1,klasa).__new__(klasa, wart) def __init__(self,value): print("In baza 1 __init__") Przykładem są klasy PersonName i PersonNameUnicode z Pydicom Programowanie FizMed, wykład 5 21 Wracając do PyDicom Programowanie FizMed, wykład 5 22 Przypomnienie podstawowych informacji o PyDicom import dicom Podstawowym obiektem jest dataset, który można uważać za specjalny słownik. dicom.read_file(s) wczytuje plik o ścieżce s i zwraca dataset ds = dicom.read_file("MR_small.dcm") Wyświetlenie elementów słownika możliwe jest przez wpisanie jego nazwy w sesji interaktywnej lub polecenie dir(ds) które zwraca listę kluczy. Obraz jest dostępny poprzez pole pixel_array, w postaci macierzy numpy.array, którą można wyświetlić używając biblioteli matplotlib. Pakiet PyDICOM automatycznie wykonuje konwersję. import pylab pylab.imshow(ds.pixel_array, cmap=pylab.cm.bone) pylab.show() Programowanie FizMed, wykład 5 23 Przypomnienie podstawowych informacji o PyDicom cd. Informacje wpliku dicom zorganizowane są w postaci tablicy. Indeks tej tablicy to para liczb szestnastkowych tzw DICOM Tag, która określa charakter pola. (0008, 0008) Image Type CS: [b'DERIVED', b'SECONDARY', b'OTHER'] (0008, 0012) Instance Creation Date DA: b'20040826' (0008, 0013) Instance Creation Time TM: b'185434' Lista znaczników (tagów) rozpoznawanych przez pakiet PyDicom znajduje się w pliku _dicom_dict.py 0x00080008: ('CS', '2-n', "Image Type", '', 'ImageType'), DICOM Tag Description opis typu pola wynikająca z jego znacznika/tagu. DICOM Tag Name opis typu pola wynikająca z jego znacznika/tagu. VR Value Record jakiego typu jest wartość pola (CS-string, DA - Date) 2-n oznacza, ze wartość ma długość od 2 do n znaków, czyli więcej niż jeden. Programowanie FizMed, wykład 5 24 PyDicom cd. Pola, które nie zostaną znalezione w ogólnym słowniku traktowane są jako prywatne sekcje: Private creator (0009, 0000) Private Creator UL: 160 (0009, 0010) Private tag data LO: b'GEMS_IDEN_01' (0021, 0000) Private Creator UL: 298 (0021, 0010) Private tag data LO: 'GEMS_RELA_01' Na podstawie pola Private tag data można zidentyfikować wartości specyficzne dla producenta urządzenia. Lista znaczników zarezerwowanych dla producentów znajduje się w pliku: _private_dict.py Programowanie FizMed, wykład 5 25 Anonimizacja Anonimizacja pliku dicom polegała będzie na usunięciu z niego danych, które nie są potrzebne. Np. w celach badawczych nie są potrzebne dane personalne pajenta, czy też nazwisko lekarza lub nazwa przychodni. Aby to zrobić musimy: 1) Znalezć konkretne pole. 2) Usunąć go 3) Zapisać tak powstały plik. Obiekt dataset posiada wbudowane 2 metody ułatwiające to zadanie. ds.remove_private_tags() - usuwa sekcje Private Creator i ich zawartość. ds.walk(callback_func) callback_func jest nazwą funkcji, którą musimy sami napisać. Będzie ona wywołana dla każdego znacznika (tagu) obecnego w pliku dicom Programowanie FizMed, wykład 5 26 Anonimizacja Jeżeli znamy "tag" znacznika, możemy bezpośrednio zmienić jesgo wartość. Na przykład z dataset o nazwie ds wyciągamy znacznik (data element) o tagu (0028,0102). de = ds[(0x0028,0x0102)] de.name # nazwa znacznika 'High Bit' de.value # jego wartość 15 de.VR # jego typ 'US' de.value = 0 # zmieni wartość w obiekcie de a ponieważ jest on referencją do oryginalnego pola, zostanie zmieniona wartość w datasecie. Programowanie FizMed, wykład 5 27 Anonimizacja c.d. Pole "Patients Name" Z dataset o nazwie ds wyciągamy znacznik o tagu (0010,0010). de = ds[(0x0010,0x0010)] de.value = "" # Kasuje nazwisko pacjenta Tak zmieniony plik możemy zapisać pod nową nazwą: ds.save_as(output_filename) Programowanie FizMed, wykład 5 28 Koniec na dzisiaj! Programowanie FizMed, wykład 5 29