niedziela, 4 czerwca 2017

Podsumowanie konkursu.

Konkurs "Daj się poznać", edycja 2017 został oficjalnie zakończony. Pragnę pogratulować tym samym wszystkim uczestnikom, a tym którym udało się zakwalifikować dalej, życzyć powodzenia w fazie finałowej.

Jako, że spełniłem wszystkie wymagania, udało mi się dostać do grona finalistów. Oddałem również głosy na projekty, które uważałem za interesujące. Aczkolwiek, mój wybór chciałbym pozostawić w tajemnicy. Widziałem sporo interesujących projektów i blogów, więc ograniczenie wyboru do trzech pozycji nie było łatwym zadaniem. Śledząc inne blogi dowiedziałem się kilku interesujących rzeczy. Być może ktoś dowiedział się czegoś wartego poznania z mojego bloga.

Na koniec, małe podsumowanie:

  • Cały projekt rozwijałem przez 11 tygodni.
  • Podczas tego czasu zrobiłem 45 commit'ów.
  • Utworzyłem 8302 linie kodu.
  • Usunąłem 3223 linie kodu.

Powyższe statystyki nieco przekłamują wielkość aplikacji, ponieważ obejmują wszystkie utworzone linie kodu, które zostały commitowan'e oraz wszystkie które zostały usunięte. Aczkolwiek różnica w ilości linii dodanych i usuniętych wynosi 5079, co nie odzwierciedla prawidłowej ilości 4285 linii kodu (wraz z pustymi liniami, bez pustych linii - 3522). Szybkie poszukiwania z użyciem google nie dały mi odpowiedzi na to, w jaki sposób liczone są commit'owane linie na github'ie.

Statystyki dotyczące ilości linii kodu.

Name: statistics_gui.py              | Lines: 895
Name: markers_gui.py                 | Lines: 532
Name: run.py                         | Lines: 345
Name: main_gui.py                    | Lines: 317
Name: texts.py                       | Lines: 280
Name: options_gui.py                 | Lines: 213
Name: statistics.py                  | Lines: 135
Name: png_export_gui.py              | Lines: 92
Name: export_canvas_utilities.py     | Lines: 90
Name: jpg_export_gui.py              | Lines: 88
Name: tif_export_gui.py              | Lines: 81
Name: samples.py                     | Lines: 67
Name: widgets_geometries.py          | Lines: 61
Name: about_gui.py                   | Lines: 58
Name: shapes.py                      | Lines: 56
Name: settings_utilities.py          | Lines: 54
Name: export_statistics.py           | Lines: 32
Name: manage_settings.py             | Lines: 31
Name: screen_utilities.py            | Lines: 28
Name: statuses.py                    | Lines: 26
Name: marker.py                      | Lines: 22
Name: marker_utilities.py            | Lines: 19

TOTAL: 3522

Wszystkie pliki zostały automatycznie przeskanowane, a linie w plikach policzone z zastosowaniem prostego skryptu, z wyłączeniem, pustych linii, ale z uwzględnieniem docstrings oraz komentarzy.

niedziela, 28 maja 2017

Kolejny błąd odnaleziony.

Przeznaczyłem trochę czasu na testowanie BioCounter'a poprzez jego użytkowanie. To znaczy, wczytywałem zdjęcia, w różnym formacie i zaznaczałem na nich różne elementy. Przy czym moim celem nie było celowe zepsucie działania aplikacji, ale sprawdzenie, czy jakiś błąd nie wyjdzie podczas normalnego użytkowania.

Znalazłem w ten sposób jeden błąd. Okazało się, że aplikacja nie trzymała popranie informacji dotyczącej rozszerzenia wczytanego pliku. Po wczytaniu np. pliku w formacie "jpg", aplikacja trzymała informacje na temat tego rozszerzenia. Jeśli użytkownik oznaczył interesujące elementy na zdjęciu, a następnie zdecydował się na eksport takiego zdjęcia, wszystko przebiegało zgodnie z założeniami i program poprawnie sugerował rozszerzenie pliku jako "jpg". Problem pojawiał się w momencie kiedy po otwarciu pierwszego zdjęcia, wybierano otwieranie kolejnego, ale proces ten anulowano. W tym momencie program gubił informacje o rozszerzeniu obrabianego pliku, a podczas próby eksportu tego pliku, brakowało domyślnego rozszerzenia w oknie dialogowym.

Rozwiązanie tego problemu okazało się bardzo proste i polegało na zmodyfikowaniu zmiennej odpowiedzialnej, za przechowywanie informacji dotyczącej rozszerzenia wczytanego pliku graficznego.

czwartek, 25 maja 2017

Czyszczenie kodu.

Czystość pisanego kodu jest ważna, ponieważ poprawia czytelność i ułatwia pisanie samej aplikacji. Jednak w tym przypadku chodzi o usuwanie zbędnych elementów programu, które były potrzebne do debugowania jego kodu. Gdyż nie powinny być one widoczne dla użytkownika.

Oczywiście elementy te nie muszą być usunięte na stałe. Byłoby to nawet niewskazane, ponieważ za każdym razem kiedy zaistniała by potrzeba sprawdzenia działania jakiegoś stałego elementu aplikacji, kod odpowiedzialny za debugowanie trzeba byłoby na nowo dodawać. Wystarczy je sprytnie zdezaktywować dodając zmienną "debug" oraz stosując instrukcje warunkowe w odpowiednich, potrzebnych miejscach w programie. Jest to bardo wygodne, gdyż w łatwy sposób pozwala na włączanie i wyłączanie całych sekcji kodu.

Zdaję sobie sprawę, że do testowanie przeznaczone są testy jednostkowe. Jednak nie wiem, czy za pomocą takich testów można by przetestować wszystkie elementy implementowane z użyciem Tkinter. Dotychczas używałem testów jednostkowych do sprawdzania funkcjonowania klas, czy też modułów i do tego nadawały się wyśmienicie. Jednak nie wiem, czy można przeprowadzić takie testy w połączeniu z event'ami Tkinter.

Na moje potrzeby wystarczające okazało się wypisywania potrzebnych wartości w konsoli, ponieważ interesowało mnie, czy coś ma daną, spodziewaną wartość. Metoda ta, bardzo szybko osiąga swoje limity i nie nadaje się do testowania szerokiego zakresu wartości jednej zmiennej. Jednak dla moich potrzeb okazała się szybka i efektywna, co było wystarczającym powodem na jej zastosowanie.

niedziela, 21 maja 2017

Kontrola nad aplikacją.

Użytkownik powinien posiadać swobodę pracy z aplikacją, ale jaka kontrola zostanie mu dana, zależy jedynie od developera. Oczywiście wszystko powinno być wyważone i wskazane jest, aby zachować złoty środek. Aczkolwiek należy pamiętać, że obecność pewnych funkcji wiąże się z pewnymi oczekiwaniami dotyczącymi funkcji towarzyszących.

Dlatego kiedy dodałem do programu możliwość eksportowania obrazów z naniesionymi markerami, poszedłem o krok dalej i zaimplementowałem możliwość regulowania jakości eksportowanego obrazu. Dyskusyjne jest, czy było to potrzebne, ponieważ normą są dyski o kilkuset gigabajtowej pojemności, więc kilkaset kilobajtów wydaje się nie robić problemu. Jednak funkcja pozwalająca na zmianę jakości zapisywanego obrazu, jest nieodzownie związana z możliwością eksportowania samego obrazu. Te dwie funkcje w programach graficznych są połączone z sobą, a obecność jednej, bez dodania drugie powoduje, że produkt może sprawiać wrażenie niekompletnego.

Oczywiście na popularność programu, czyli to czy odniesie sukces, czy też nie, składa się wiele innych czynników. Jednak to jak dany program jest odbierany przez użytkownika stanowi jeden z wyznaczników, który jest podwaliną tego sukcesu. Obecność pewnych funkcji programu może mieć charakter minimalistyczny, ale ważnym czynnikiem jest to, jakie rozwiązania są swoiste dla implementacji danych rozwiązań. Dlatego też, dodając pewne funkcjonalności programu nie można ograniczać się do nich samych, ale łączyć je z funkcjonalnościami, których może oczekiwać sam użytkownik. Podnosi to znacznie jakość samego programu, ale sprawia również, że praca z nim staje się bardziej komfortowa.

czwartek, 18 maja 2017

Drobnostki poprawiające ogólne wrażenie.

W jednym ze wcześniejszych postów pisałem, że na to czy program jest odbierany jako dobry, czy kiepski mogą wpływać niewielkie niuanse. Oprócz tego w programie może znajdować się wiele elementów poprawiających ogólne wrażenie i dodających elementy nie związane z podstawową funkcjonalnością.

Jednym z takich elementów jest okno z informacjami o programie. Jest to w zasadzie szczegół, na który nie zwraca się prawie uwagi. Aczkolwiek sam pamiętam, że czasami sprawdzałem, co w takim oknie znajdę i byłem zaskoczony informacjami jakie tam odkryłem. W szczególności kiedy dowiadywałem się, że np. dany program stworzyła tylko jedna osoba. Oczywiście takie informacje jak autorstwo programu, można umieścić w samym kodzie aplikacji, ale utworzenie do tego celu specjalnego okna, pokazuje pewną dbałość o szczegóły.

Oprócz informacji o autorstwie w oknie takim mogą znajdować się informacje dotyczące wersji programu, łącznie z numerem buildu, odnośnik do strony internetowej autora, czy też informacje o użytych bibliotekach. Takie ono może być również dobrym miejscem na dodanie loga programu. Możliwości realizacji takiego pomysłu jest na prawdę wiele.

niedziela, 14 maja 2017

Dodatkowe okna, kolejnych słów kilka.

Postanowiłem zaimplementować możliwość zmiany jakości dla zapisywanego pliku "jpg". Wybór padła na zastosowanie klasy Toplevel() i zbudowanie odpowiedniego interfejsu graficznego w nowym oknie.

Konstruowanie okna przebiegło bez żadnych problemów. Jednak mimo przekazywania wszystkich koniecznych parametrów, do odpowiednich ustawień, eksportowane pliki zapisywały się z domyślną wartością kompresji, wynoszącą 95. Okazało się, że nowe okno jest tworzone, ale cały program nie czeka na wprowadzenie zmian, tylko kontynuuje pracę w tle. Dlatego też zmiana ustawień nie maiła, żadnego wpływu na jakość eksportowanego pliku. Wprowadzone zmiany byłyby zastosowane dopiero do kolejnego eksportowanego pliku.

W celu pauzowania pracy głównego programu, trzeba wywołać metodę wait_window() na odpowiednim elemencie, co jest zależne od przyjętej implementacji tej metody. Oprócz pauzowania dobrym rozwiązaniem jest wcześniejsze wykonanie metody transient() na instancji klasy Toplevel() i przekazanie do tej metody głównego elementu programu. Spowoduje to związanie nowego okna z głównym oknem programu, dzięki czemu nowe okno nie pokaże się jako dodatkowa ikona na pasku zadań w Windows.

wtorek, 9 maja 2017

Dodatkowe okna w Tkinter.

Cała aplikacja Tkinter działa w pętli określonej za pomocą metody mainloop(), a wszystkie procesy zachodzą na jednym wątku. Aczkolwiek nie znam Tkinter tak dobrze, aby móc stwierdzić, czy wykorzystywany jest Threading, czy też nie. W związku z tym, kiedy trzeba utworzyć dodatkowe okno programu, nie można po prostu utworzyć kolejnej instancji Tk() i wywołać na niej kolejnej metody mainloop(). Nie wiem, jak wyglądało by działanie Tkinter z utworzeniem dwóch osobnych okien, na dwóch osobnych procesach, ale sama koncepcja nie wydaję się być za dobra.

Problem wielu okien można w łatwy sposób obejść dodając do listy kolejne instancje widget'ów, z danego segmentu GUI i w razie potrzeby niszczyć te obiekty, poprzez wywołanie na nich metody destroy(). W miejsce usuniętych elementów można utworzyć nowe, wchodzące w skład wywołanej części interfejsu.

list_of_widgets = [self.a_button_1, self.a_button_2]
for element in list_of_widgets:
    element.destroy()

Rozwiązanie to działa bardzo dobrze i sam stosowałem je wielokrotnie, a jedynym minusem jest konieczność tworzenia list widget'ów. Szybkość tworzenia nowych elementów GUI również nie stanowi problemu i jest niezauważalna dla użytkownika.

Jeśli jednak zajdzie sytuacja, kiedy konieczne jest utworzenie nowego okna, to w tym celu należy użyć klasy Toplevel(). Oferuje ona szereg standardowych opcji konfiguracji, takich jak przy tworzeniu głównego okna. Największą różnicą jest to, że na instancji klasy Toplevel() nie wywołuje się metody mainloop(). Przekazywanie zmiennych między obydwoma oknami również zachodzi bezproblemowo.

Użycie jednej, albo drugiej metody zależy tylko i wyłącznie od przyjętego projektu interfejsu. Nic nie stoi na przeszkodzie, aby połączyć obydwie metody i stosować je zamiennie, w odpowiednich do tego miejscach.

niedziela, 7 maja 2017

Status bar.

Obecność status bar'a nie jest obowiązkowa, ale wydaje się, że pomaga w pewien sposób przekazywać użytkownikowi informacje, dotyczące działania aplikacji. Budując okno programu miałem w planach dodanie status bar'a, ale zakodowanie tej funkcjonalności odkładałem w czasie, gdyż nie była ona krytyczna dla funkcjonowania całego programu.

Wyświetlanie statusów następuje poprzez, zmianę wartości odpowiedniej instancji klasy StringVar() w widget'cie ttk.Label. Bardzo istotnym założeniem było wyświetlanie danej wiadomości tylko przez określony okres czasu, a następnie czyszczenie całego paska. Kolejkowanie zadań w Tkinter odbywa się poprzez zastosowanie metody "after" na jakimś widget'cie. Piszę jakimś, ponieważ implementując to rozwiązanie dostrzegłem, że instancja widget'a nie musi należeć do tej samej hierarchii. Jednak dla zachowania jednorodności, wszystkie wywołania metody "after" przypisywałem do tego samego obiektu. W metodzie tej przekazuje się czas (w milisekundach) po jakim ma zostać wywołana dana funkcja oraz przekazuje się samą funkcję.

self.canvas_frame.after(2000, self.clear_status)

Okazało się, że częściowo myliłem się odnośnie funkcjonowania tej metody. Nie wiedziałem, że kolejne jej wywołanie, nie anuluje automatycznie poprzedniego. Zamiast tego, kolejne wywołania są kolejkowane i wywoływane po odpowiednim upływie czasu. Jeśli zdefiniowane interwały są niewielkie nie sprawia to problemu, aczkolwiek przy zastosowaniu okresu 2000 ms, mogą dziać się ciekawe rzeczy. Użyłem tego rozwiązania do wyświetlania liczby odpowiadającej wielkości markera, po zmianie jego wielkości za pomocą skrótów klawiaturowych (oraz odpowiadających przycisków). Przy zmianie wielkości o kilka stopni, następowało kolejkowanie zadań i ich wywoływanie po odpowiednim czasie, co kilkukrotnie, w niewielkich odstępach czasu, czyściło status bar'a. Spowodowało to migotanie tekstu wyświetlanego na status bar'ze. Być może uszło by to uwadze większości użytkowników, jednak w pewien sposób wskazywało na kiepską jakość kodu w programie. Było też nie estetyczne.

Jak zwykle szukałem rozwiązań w internecie, głównie na "stacku". Najprostszym z nich było utworzenie atrybutu klasy, do którego przypisywane było aktualne zadanie, "self.job_queue". Jeśli atrybut ten posiadał wartość "None", przypisywano do niego pierwsze zadanie. Jeśli zadanie to zostało wykonane, wartość zostawała ponownie zmieniana na "None". Jeśli natomiast, przed wykonaniem zadania została ponownie wywołana metoda "after", to poprzednie zadanie było anulowane za pomocą metody "after_cancel", a do "self.job_que", przypisywano nowe zadanie. W ten sposób status bar zostawał czyszczony tylko raz, 2000 ms po wyświetleniu ostatniego tekstu, problem migotania został rozwiązany.

sobota, 6 maja 2017

Walka z zakładkami.

Funkcjonowanie Tkinter potrafi niejednokrotnie zaskoczyć, co może owocować niespodziewanym działaniem programu. Podczas przełączanie zakładek w widget'cie ttk.Notebook następuje zmiana elementu, który aktualnie posiada focus. Jest on przełączany na elementy umieszczone w zakładce, którą aktywowano. Działanie to może być niepożądane, jeśli w karcie znajduje się np. ttk.Entry, ponieważ spowoduje to zaznaczenie całego obecnego w elemencie tekstu.

Aby temu zapobiec można przekazać parametr "takefocus" o wartości "False", podczas tworzenia instancji "ttk.Entry". Ma to jednak poważne działanie uboczne, ponieważ wyłącza możliwość przełączania aktywnych elementów za pomocą przycisku Tab. Takie rozwiązanie odpada. Ponadto focus zostanie przeniesiony na kolejny obiekt znajdujący się na zakładce.

Inną możliwością jest bind'owanie do danej zakładki metody, w oparciu o "event"
"<Visibility>" i przypisanie jej odpowiedniego wywołania callback. "Event" ten zostaje aktywowany w momencie kiedy dany element staje się widoczny na ekranie.

self.tab_1.bind('<Visibility>', lambda event,
    tab=self.tab_1:callback_method(event, tab))

Następnie używając zdefiniowanej metody callback_method, można przenieść focus na inny element, w tym wypadku na samą zakładkę, która jest przekazywana do tej metody. To rozwiązanie działa, ale pozostawia po sobie pewne artefakty.

Tekst obecny w ttk.Entry nie jest zaznaczony, ale posiada pewne zauważalne pogrubienie. Jeśli możliwość wpisywania teksu do ttk.Entry została zablokowana poprzez ustawienie "state" na wartość "readonly", to tło teksu w takim polu zmienia kolor na biały. Jest to pewnie związane z tym, że tekst został zaznaczony podczas przełączania zakładki, po czym focus został przeniesiony na inny obiekt. W związku z tym, że są to obiekty stylizowane ttk, nie można tego szybko naprawić poprzez zmianę "hightlightthickness", czy "hightlightbackground", prawdopodobnie konieczne byłoby zdefiniowanie odpowiedniego stylu ttk.

Rozwiązanie okazało się proste i było raczej niespodziewane. Otóż podczas przełączanie zakładki focus zostaje przeniesiony zgodnie z obecną hierarchią powstałą między obiektami, a związaną z przypisywaniem danych elementów do siebie. W celu zdefiniowania wielkości samej zakładki konieczne jest utworzenie obiektu typu Frame lub ttk.Frame, która jest najwyżej w hierarchii. Widget'y te mają domyślnie ustawiony parametr "takefocus" na "False". Po zmianie wartości na "True", ramka będzie pierwszym obiektem w kolejce do otrzymania focus'a. Na ramce natomiast nie widać żadnego efektu zaznaczenia, problem został rozwiązany.

środa, 3 maja 2017

Testowania nic nie zastąpi.

Podczas pisania kodu starałem się sprawdzać funkcjonowanie programu, mimo tego nie uniknąłem błędów. Jest to związane z tym, że byłem skupiony na kontrolowaniu działania niewielkiej części programu w związku z czym, mojej uwadze mogły umknąć pewne rzeczy. Znalazłem i poprawiłem dwa następujące błędy.

Pierwszy odnaleziony błąd był dosyć stary. Podczas włączania programu wszystkie zmienne i wartości dotyczące markerów oraz ich ilości są wyzerowane. Po wczytaniu zdjęcia, a następnie pracy na nim, te wartości ulegają zmianie. Podczas wczytywania do programu kolejnego, nowego zdjęcia wartości te są zerowane, w celu umożliwienia zliczenia nowych elementów. Problem dotyczył tego, że wartości liczbowe przechowywane jako Tkinter StringVar() nie były zerowane i pokazywały wartości odnoszące się do starego zdjęcia.

Drugi błąd dotyczył zapisu zdjęcia z naniesionymi markerami. Do nazwy pliku automatycznie dodawane było jego rozszerzenie. Jeżeli podczas eksportu, w oknie dialogowym zaznaczono już istniejący plik, posiadający końcówkę określającą rozszerzenie, to mimo tego do nazwy pliku dodawany był kolejny, nowy fragment tekstu oznaczający rozszerzenie.

Powyższe przykłady pokazują, że nieoczekiwane błędy mogą się pojawić podczas zwykłej pracy z programem. Ponadto mogą znajdować się w kodzie, który napisany był dużo wcześniej, a podczas sprawdzania jego konkretnej funkcjonalności nie powodował żadnego problemu.


niedziela, 30 kwietnia 2017

Nawet przypisywanie przycisków może być podchwytliwe.

Okazało się, że skróty klawiszowe nie działały poprawnie. Trochę potrwało zanim znalazłem przyczynę tego zachowania. Po pierwsze dodając skrót do drop down menu, nie wystarczy skorzystać z argumentu "accelerate". Przekazany w ten sposób argument, jest tylko i wyłącznie tekstem wyświetlonym po prawej stronie etykiety, aby dany skrót działał, należy go również zbindować.

Odbywa się to przez metodę "bind_all". Strona będąca moim głównym źródłem informacji na temat Tkinter nie działała, wiec nie mogłem się dowiedzieć, jak to dokładnie działa. Według mojego rozumowania, co jest w zasadzie czystą spekulacją, metoda "bind" dotyczy, czy raczej jest wywoływana na obiekcie interfejsu, który jest aktualnie "sfokusowany". Wygląda na to, że przed wywołaniem callback'a tej metody, wyszukiwany jest widget posiadający focus, który mógł uzyskać np. poprzez kliknięcie lewym przyciskiem myszy. Jeżeli fokus widget'u zgadzał się z koordynatami event'u, to callback był wykonywany. Natomiast metoda "bind_all", bind'uje niejako daną komendę do wszystkich obecnych elementów. Chociaż zaznaczam, że są to tylko moje spekulacje.

W związku z powyższym, aby skróty z drop down menu działały, zostały zbindowane do obiektu, main_frame, który swoją wielkością obejmuje niemal całe okno. Był niejako krok zapobiegawczy, ponieważ obiekt main_frame, był kolejnym głównym obiektem, zaraz po root, gdyż nie wiem, czy bind_all nie jest związane w jakiś sposób z hierarchią widget'ów w oknie. Jednak okazało się, że przypisanie "bind_all", tylko do wyznaczonego obiektu, np przycisku, powoduje oczekiwane działanie i skrót można wywołać w każdym miejscu okna.

Inną rzeczą było zapisywanie pliku graficznego posiadającego naniesione markery. Szukałem jak najłatwiejszej metody na osiągnięcie tego celu, jednak okazało się, że nie można w prosty sposób dokonać zapisania canvas'u Tkinter za pomocą biblioteki Pillow. Konieczne jest utworzenie obiektu Image i przekazanie go do klasy ImageDraw, w której następuje dodawanie elementów, do instancji klasy Image. Metody wchodzące w skład klasy ImageDraw, są podobne do metod Tkinter canvas. Wystarczyło pobrać listę wszystkich markerów, skonwertować ich kolory do postaci "RGBA" i nanieść na obraz przeznaczony do zapisania do pliku. W związku z tym implementacja tej funkcjonalności nie była najprostsza, ale nadal okazała się być dość prostym zadaniem.

sobota, 29 kwietnia 2017

Trudne implementacje.

Powoli przygotowuję grunt pod umożliwienie zapisywania zmodyfikowanego zdjęcia, to znaczy zdjęcia z naniesionymi znacznikami. Optymalnie było by zapisywać zmodyfikowane zdjęcie do formatu pozwalającego na dodawanie warstw. W ten sposób każda grupa znaczników znajdowała by się na osobnej warstwie, co mocno ułatwiło by dalszą pracę z plikiem. Nie wiem czy biblioteka "pillow" pozwala na zapis do takiego formatu. Na razie zadowolę się zapisem do któregoś z najpopularniejszych formatów.

Dobrym wyborem wydaje się ".png", ze względu na dobrą jakość obrazu, aczkolwiek kosztem wielkości pliku. Dobrym pomysłem wydaje się również sprawdzanie automatyczne wczytanego pliku i zapis do tego samego formatu. Zwalnia to z ewentualnych problemów związanych z konwersją między formatami. Jednak poza ewentualną utratą jakości obrazu, taka konwersja nie powinna stanowić żadnego problemu.

Najlepszym rozwiązaniem wydaje się również zapis zdjęcia w jego oryginalnym rozmiarze. To znaczy bez skalowania, które odbyło się w oknie programu. Wyszedłem z założenia, że ta zaimplementowana funkcja powiększenia, jest tylko i wyłącznie na potrzeby podglądu.

środa, 26 kwietnia 2017

CSV format popularny chociaż nieznany.

Zaimplementowałem eksport statystyk do pliku w formacie ".csv" (coma separated values). Format ten nie wyróżnia się niczym szczególnym, a plik zapisany z takim rozszerzeniem można otworzyć najprostszym programem do edycji tekstu. Po wczytaniu pliku, pojawią się rzędy wartości, które w kolumnach oddzielone są od siebie średnikami, albo innymi znakami. Pierwszy rząd może stanowić nagłówek, pozwalający na zorientowanie się, czego dotyczą poszczególne kolumny danych.
Plik w tym formacie można bez problemu zaimportować do arkusza kalkulacyjnego, a wczytane dane poddać następnie statystycznej obróbce.

W zastosowanej implementacji nie eksportuję wartości procentowych do pliku. W założeniu wartości te miały pomóc ocenie ilościowej, w przypadku pracy tylko z samym BioCounter'em. Ponadto po importowaniu pliku do arkusza kalkulacyjnego dostępnych jest mnóstwo lepszych narzędzi, pozwalających na wykonanie rozmaitych analiz statystycznych.

Język Python posiada wbudowany moduł ułatwiający zapis pliku do formatu ".csv". Dzięki czemu implementacja eksportu do tego formatu staje się bardzo łatwa. Dodatkowo użyłem również modułu "codecs", pozwalającego na zapisywanie plików z czcionkami zawierającymi polskie znaki diakrytyczne.

niedziela, 23 kwietnia 2017

Małymi krokami, ale do przodu.

Wiele nie napisałem. Zastanawiałem się głównie nad tym, gdzie umieścić opcję eksportu statystyk. Na razie padło na panel "Statistics", ale rozważałem również nad dodaniem tej funkcji do drop down menu "File". Chodzi mi o zachowanie jak największej intuicyjności oraz funkcjonalności. Jednak najczęściej sporo rzeczy wychodzi podczas codziennego użytkowania, wtedy najłatwiej wprowadzać wszelkie udogodnienia.

Sprawdziłem również jak zmienia się rozmiar instancji klasy, w przypadku kiedy zawiera ona statyczną metodę, w porównaniu z instancją, która tej metody nie posiada. Okazało się, że statyczna metoda przypisana do klasy nie zmienia wielkości instancji. Być może robię coś, źle ponieważ po dodaniu metody, która dodawała do instancji klasy kolejny atrybut, wielkość instancji nadal nie uległa zmianie. Nawet po użyciu tej metody i przypisaniu nowego atrybutu do instancji.

Ilość miejsca w pamięci zajmowanej przez dany element programu można sprawdzić poprzez importowanie modułu "sys" i użycie komendy:

sys.getsizeof(element_to_check)

Jest to bardzo wygodne polecenie, do kontroli wielkość poszczególnych elementów w pamięci, w celu optymalizacji pracy programu. Moduł "sys" zawiera dużo więcej przydatnych metod, do sprawdzania działania programu i jego pracy w systemie.

sobota, 22 kwietnia 2017

Błędów nie sposób przewidzieć.

Miałem zamiar zaimplementować eksport statystyk do pliku. Jednak odkryłem kolejny błąd i zająłem się jego naprawą. W tym wypadku debugowanie zajęło mi bardzo dużo czasu. Wiedziałem co nie działa i jak powinno działać, ale nie mogłem odnaleźć miejsca odpowiedzialnego za błędy i go naprawić. Okazało się, że kod wymagał więcej niż korekcji tylko w jednym miejscu.

Przyczyną było skalowanie załadowanego obrazka. Wraz ze zdjęciem skalowaniu powinny ulegać również markery, to znaczy nie one same, ale ich pozycja powinna być odpowiednio przeliczana. Dodatkowo, ważne było, aby obiekt "marker" trzymał informacje dotyczące jego absolutnej pozycji.

To był właściwy problem. Nieodpowiednie przeliczanie pozycji. Podczas dodawania markera do "canvas" nie uwzględniałem skali, więc zarejestrowane pozycje markerów, były obarczone błędem związanym z ustawionym powiększeniem dla wczytanego obrazka. Innym problemem było uwzględnianie skali podczas "rysowania" samego markera. Działanie to jest konieczne, podczas przeliczania pozycji, po zmianie skalowania wczytanego zdjęcia, ale w programie zachodziło za każdym razem, kiedy program nanosił marker na "canvas". Implementacja usuwania markerów i nanoszenia nowych, po zmianie przybliżenia, spowodowała w tym wypadku wydłużenie czasu odnajdywania błędu. Jednak nie znalazłem lepszego sposobu na zaimplementowanie tej funkcjonalności.

Nanoszenie nowego markera jest uwzględniane i podczas tej czynności program nie uwzględnia skali załadowanego obrazka. Natomiast w celu wyliczania i uwzględniania absolutnych pozycji markerów utworzyłem dodatkową klasę posiadającą statyczną metodę. Nie wiem, czy w Pythonie instancja klasy zawiera również statyczne metody, więc wybrałem to rozwiązanie, aby niepotrzebnie nie zapychać pamięci komputera,ponieważ markerów naniesionym na "canvas" może być bardzo dużo.

poniedziałek, 17 kwietnia 2017

Nieprzewidziane funkcje, czyli naprawa błędów.

Nawet w nieskomplikowanym kodzie mogą znajdować się błędy. Problem ten dotyka każdego oprogramowania, nawet tego najprostszego. Przeczytałem kiedyś, że na 100 linijek kodu, w 1-3 znajduje się błąd. Wszakże tekst dotyczył programów napisanych w C++, aczkolwiek problem ten ma uniwersalny wymiar i dotyczy wszelkich języków programowania.

Pierwszy błąd, który znalazłem dotyczył "nieskończonych" wymiarów obiektu Tkinter "canvas". Okazało się, że po wczytaniu zdjęcia, ograniczałem rozmiar "canvas'u", do wymiarów zdjęcia, gdyż było to potrzebne do przypisania belek scroll'owania. Jednak sam "canvas" tworzony był dużo wcześniej i nie posiadał tych ograniczeń. Pomimo, że belki przewijania był niewidoczne, "canvas" można było przewijać w pionie, za pomocą rolki myszy oraz w poziomie trzymając wciśnięty klawisz shift i używając w tym czasie rolki myszy. Nie wiem jakie są realne ograniczenia w wymiarach "canvas", ale ja zaniechałem dalszego przewijania przy 35 tysiącach pikseli w poziomie. Nie wiem nawet, czy eksportując obraz w takiej rozdzielczości udało by się tego dokonać dla jakiegoś sensownego formatu np. jpg. Akurat nie pamiętam, czy te koordynaty były przechowywane jako float, czy integer. Jednak Python jest w stanie przechowywać dużo większe liczby całkowite, niż tradycyjne języki programowania. Udało mi się obliczyć 1000000! i zapisać tą wartość do pliku, bez stosowania żadnych dodatkowych bibliotek.

Drugi błąd dotyczył usuwania naniesionych markerów. Obiekty do usunięcia są rejestrowane, poprzez sprawdzenie, jakie obiekty pokrywają się z "testowym" wirtualnym obiektem, a badane jest to w miejscu naciśnięcia prawego przycisku myszy. Okazało się, że wczytany obraz, zawsze się pokrywał z tym obiektem, a co za tym idzie zawsze był usuwany. Naprawa tego błędu była prosta. Przed usunięciem pokrywających się obiektów, sprawdzane jest, czy dany obiekt przeznaczony do usunięcia posiada "tag" znajdujący się na liście "tag'ów" przypisywanych markerom. Jeśli posiada, to jest usuwany.

Mimo, że znalezione błędy były łatwe do uniknięcia, to mimo tego jakoś przesmyknęły się do programu. Pokazuje to jasno, że nawet w najprostszej funkcjonalności może kryć się nieprzewidziany błąd.

niedziela, 16 kwietnia 2017

Dokumentacja kodu.

Jako, że stworzyłem już sporą część programu, a sprawę tą dotychczas zaniedbywałem, postanowiłem dopisać trochę dokumentacji do kodu. Mimo, że staram się zawsze, aby nazwy zmiennych, funkcji, metod i klas, były jak najbardziej opisowe, to lubię dopisać te kilka dodatkowych linijek dokumentacji. Jest to o tyle dobre, że po dłuższej przerwie ułatwi w przypomnieniu struktury danego programu.

Tworząc ten program nie skupiam się tylko na jednym fragmencie, to znaczy nie tworzę go w całości od początku, do końca. Często zmieniam klasę lub element funkcjonalny programu, do którego dopisuję kod. Nie wiem, czy ta metodyka sprawdzała by się dla innych osób. Jednak mi pomaga zapamiętać strukturę całego programu i wydaje mi się, że jest to związane ze sposobem, w jaki funkcjonuje pamięć. Powtarzając i przypominając sobie cyklicznie o danych fragmentach programu tworzę ich mapę i utrwalam ją w pamięci. Oczywiście nie jest to z dokładnością do jednej zmiennej, czy metody. W budowanym od podstaw programie nie ms to znaczenia, ze względu na często dokonywane zmiany. Jednak pomaga mi w ogólnym rozeznaniu na temat klas i metod.

Inną sprawą jest zmiana aktualnie kodowanego segmentu. Pomaga mi to uniknąć znużenia, które mogło by być związane, z pracą tylko i wyłącznie nad jedną klasą, czy funkcją. Po powrocie do kodowania danego elementu, mam na niego niejako świrze spojrzenie, co pomaga wyłapywać pewne błędy i usprawniać kod. Oczywiście idealnie byłoby pisać kod nie wymagający poprawek i usprawnień, jednak na razie wydaje się to poza moim zasięgiem.

To świeże spojrzenie jest związane z tym jak funkcjonują neurony. Otóż pracując nad jednym elementem, tylko jedna grupa neuronów ulega pobudzaniu wraz z częścią neuronów sąsiadujących. Początkowo siła z jaką są one wzbudzone utrzymuje się na wysokim poziomie, a nawet rośnie. Jednak po pewnym czasie ich aktywność spada, co wiąże się z wyczerpaniem neuroprzekaźników uwalnianych do przestrzeni synapsy. Z takim miejscowym pobudzeniem wiąże się, to że myśli i pomysły są ukierunkowane w pewien sposób.

Prostym dowodem na to jest sytuacja, w której staramy się przypomnieć np. nazwisko aktora, który grał w danym filmie. Może się wydawać, że np. miał nazwisko zaczynające się na literę "B". Intensywnie myślimy wtedy o takich nazwiskach - Bronson, Bogart, itp. Im dłużej o tym myślimy, tym więcej nazwisk się przypomina. Jednak, często brakuje tego jednego, konkretnego nazwiska. Po pewnym czasie rezygnujemy, gdyż nie możemy sobie tego aktora przypomnieć. Odkładamy temat na bok, zmieniamy na inny. Wtedy, często się zdarza, że po dłuższej chwili wpadamy na to konkretne nazwisko, o które chodziło od samego początku.

Jest to właśnie związane wzbudzaniem określonej grupy neuronów, która ukierunkowuje w pewien sposób aktualne myślenie. W socjotechnice często wykorzystuje się podobne działanie neuronów.

sobota, 15 kwietnia 2017

Skalowanie obrazu.

Wydawało by się, że skalowanie obrazu jest czymś bardo prostym. Jednak to zagadnienie jest dużo bardziej skomplikowane, niż początkowo mogło by się wydawać. Przywykliśmy do tej opcji tak bardzo, że w programach graficznych nawet nie zwracamy na nią szczególnej uwagi.Niestety nie znam kodu źródłowego Gimp'a na wcale, więc nie mogę przedstawić jak to wygląda w rzeczywistości, mogę opisać jedynie na jakie trudności natrafiłem implementując to w kodzie
Bio Countera.

Okazało się, że musiałem sporo ograniczyć powiększanie obrazu, ponieważ już kilkukrotnie powiększony obraz zajmował bardzo dużą ilość ramu. Ponadto proces samego skalowania obrazu wydłużał się znacznie, wraz z powiększaniem skali. Nie jestem pewien do końca dlaczego, być może powodem jest to, że PIL przeliczając wielkość obrazu korzysta z CPU zamiast z GPU. Rozwiązaniem tego problemu byłoby skalowanie tylko niewielkiego wycinka obrazu, bezpośrednio widocznego dla użytkownika. Dzięki temu problem niewielkiej ilości dostępnego ramu staje się nieistotny, a czas potrzebny do przeskalowania tylko niewielkiego wycinka obrazu ulega istotnemu skróceniu. Innym rozwiązaniem jest skorzystanie z biblioteki do obróbki obrazu, która do transformacji obrazu będzie wykorzystywała GPU. Chociaż w tym wypadku, problem z rosnącą wielkością obrazu pozostanie.

Na razie nie mam czasu, na skupianie się nad implementacją takich rozwiązań. Pozostało jeszcze sporo do zrobienia i wciąż brakuje pewnych obowiązkowych funkcjonalności programu.

wtorek, 11 kwietnia 2017

Wygoda użytkowania, znaczenie pierwszorzędowe.

Nastąpił niewielki commit, ale znaczący. Wreszcie połączyłem przyciski powiększające i zmniejszające rozmiar markerów, z samymi markerami naniesionymi na Tkinter "canvas". Tym oto sposobem, wszystkie podstawowe funkcjonalności markerów zostały dodane.

Do finiszu, pozostało jeszcze daleko. W najbliższym czasie zamierzam dodać możliwość pomniejszania oraz powiększania wczytanego obrazu. Nie wiem, czy w związku z tym nie będzie konieczne dodanie możliwości automatycznego skalowania wielkości markerów. Bądź umożliwienie włączenia takiej funkcji, poprzez zaznaczenie odpowiedniego "checkbox'a" w panelu opcji.

Pod względem funkcjonalności i wygody użytkowania, przeciętny program od programu dobrego oddzielają właśnie takie drobne rzeczy. Może wydawać się to niuansem, nie wartym uwagi na tym etapie rozwoju programu, gdyż zawsze można wprowadzić zmiany, ale wraz z powiększaniem programu, coraz trudniej czegoś takiego dokonać. Kilkakrotnie spotykałem dobre oprogramowanie, które odpychało przeciętnie wykonanym interfejsem użytkownika. Chyba najlepszym tego przykładem był Blender w wersji do 2.49c (chodzi mi o tą wersję posiadającą stary wygląd). Sam program był bardzo dobry i po zapoznaniu się z niezbędnymi skrótami klawiaturowymi korzystało się z niego bardzo przyjemnie, ale ze względu na przestarzałe GUI, początki pracy z tym programem były dla mnie trudne. W przypadku Blendera na pewnym etapie rozrostu tego programu, postanowiono przepisać cały kod aplikacji i zapadła decyzja, że będzie to również związane z powstaniem nowego GUI.

Zagadnienia związane z neuroergonomią mogą pomóc w zaprojektowaniu wygodnego interfejsu. Jednak mimo tego, że pewne rozwiązania wyglądają doskonale w teorii, nie oznacza, że równie dobrze sprawdzą się w praktyce. Trzeba też brać pod uwagę, że ciężko przewidzieć jak dane rozwiązania sprawdzą się w przypadku, kiedy wraz z rozwojem programu, na danej zakładce przybędzie elementów dodających kolejne funkcjonalności.

Bio Counter w założeniu ma być prostym programem i chciałbym, aby korzystanie z niego było wygodne i przyjemne. Samo umieszczenie opcji pozwalającej na zmianę wielkości markera na odrębnej zakładce, było związane z przypisaniem odpowiednich funkcji, do odpowiednich kategorii GUI. Było to również podyktowane ograniczeniem minimalnej rozdzielczości do 800x600, którą to w późniejszym czasie ograniczyłem do 1024x768, ponieważ stwierdziłem, że ekrany posiadające rozdzielczość 800x600 nie są już stosowane. Dobrym rozwiązaniem wydaje się dodanie do opcji zmiany wielkości markera, jakiegoś łatwego do zapamiętania skrótu klawiaturowego np. alt + rolka myszy. Aczkolwiek z rolką myszy jest już związanych kilka funkcji i chciałbym uniknąć sytuacji, w której staną się one mylące.

niedziela, 9 kwietnia 2017

Usuwanie markerów, zaginiona funkcjonalność.

To było to, czego brakowało w tym programie. To znaczy, kiedy dodałem możliwość nanoszenia markerów na "canvas" nie dałem użytkownikowi możliwości usuwania markerów już istniejących. Konieczność odnotowywania błędów "na kartce", ze względu na brak możliwości korekcji pomyłek w programie, była by bardzo irytująca. Bez niej program, nie nadawałby się do użycia.

Ta funkcjonalność była zaplanowana. Jednak zanim ją zaimplementowałam, chciałem dodać klasę oraz elementy interfejsu odpowiedzialne, za zliczanie klikniętych elementów. Gdyż był to element, niejako będący w centrum, chciałem mieć pewność, że wszystko działa tak jak należy.

Okazało się, że sposób w jaki zaimplementowałem zmianę koloru markerów, spowodował niemałe problemy. Spędziłem sporo czasu, na dojście do tego, co działa nie tak jak powinno i dlaczego nie funkcjonuje zgodnie z moim zamysłem.

Podczas dodawania elementów do "canvas" w Tkinter, są one przechowywane w zmiennej typu "touple", a "nazwa" tych elementów jest zgodna z kolejnością ich dodania. Usuwanie markerów działało doskonale, do czasu aż zmieniałem barwę, którychś z istniejących markerów. Problemem była numeracja tych elementów. Program zmieniając kolory wybranych elementów, nie zmienia tak na prawdę ich barwy. Stare elementy są usuwane z "canvas", a na ich miejsce tworzone są nowe posiadające zaktualizowany kolor. Jako, że program przechowuje również informacje o wszystkich markerach w instancjach odpowiednich klas, zapisanych w liście, powstał problem z powiązaniem elementów między tymi dwoma zbiorami. Marker, który był nr 2 na liście markerów, po zmianie barwy stawał się ostatnim elementem w zbiorze ("touple") obiektów "canvas",

Jako, że ktoś mądry powiedział kiedyś, że o wiele łatwiej jest dodać coś do klasy, niż coś z niej usunąć. Postanowiłem dodać do klasy "Marker" zmienną przechowującą informację odnoszącą się do nazwy danego elementu na "canvas". W przypadku zmiany barwy elementu, podczas ponownego rysowania obiektów na "canvas" ich nazwy przechowywane przez instancję klasy "Marker" są aktualizowane. Rozwiązanie to wydaje się proste i jest również skuteczne.

sobota, 8 kwietnia 2017

Statystyki, nie działały.

Testowanie kodu nie jest moją mocną stroną. Wiem jak testy jednostkowe przeprowadzać, ale rzadko je stosuję. Zawsze wydaje mi się, że kod jest za prosty, za łatwy lub za oczywisty, aby wymagało to tworzenia specjalnych testów jednostkowych. Pisanie takich testów nie jest trudne, ale jest czasochłonne. Czasami testowanie prostego kodu zajmuje więcej niż wyłapywanie ewentualnych błędów. Dostałem gorzką nauczkę.

Wydawało mi się, że napisałem prosty kod zliczający kliknięcia i obliczający statystyki. Nie był on w żaden sposób skomplikowany, aczkolwiek struktura nie należała do najbardziej czytelnych. Commit poszedł kilka dni temu, a ja nie zastanawiałem się nad tym, czy to aby faktycznie działa.

Okazało się, że to nie działało od samego początku. Debugowanie kodu nie było zbyt proste, ze względu na brak czytelności. Nie pomagało nawet zastosowanie kilku linii z "print". Dopiero napisanie całego fragmentu kodu od nowa załatwiło sprawę. Nie było to najlepsze rozwiązanie i zostało zastąpione lepszym i bardziej czytelnym. Mimo, że nie przepadam za strukturą zagnieżdżonych słowników, zastosowanie jej do przechowywania ilości markerów i statystyk wydawało się rozwiązaniem najlepszym. Wyłapanie i naprawienie pomyłek zajęło mi sporo czasu.

Zastosowanie testów jednostkowych pozwoliło by na wyłapanie tych błędów na samym początku. Ponadto nie stracił bym niepotrzebnego czasu na debugging kodu, który i tak ostatecznie został usunięty. Okazuje się również, że czasami nawet prosty w założeniu kod może sprawiać problemy i zawierać błędy.

wtorek, 4 kwietnia 2017

Markery, mniej statyczne niż by się zdawało.

Marker miał być prostym elementem graficznym naniesionym na "canvas" w Tkinter, w celu odróżnienia elementów policzonych od niepoliczonych. Mimo, podstawowych założeń, element ten jest daleki od statycznego.

Na razie zaimplementowałem dodawanie pojedynczych markerów oraz usuwanie wszystkich już naniesionych. Jeżeli kolor dla markera określonej próbki zostanie zmieniony, to wszystkie naniesione markery danej próbki aktualizują swój kolor. To znaczy usuwane są wszystkie naniesione, których kolor uległ zmianie i nanoszone są na nowo posiadając aktualny kolor. Jest to możliwe dzięki nadawaniu określonych tagów elementom nanoszonym na "canvas".

W zasadzie nie jest to niczym skomplikowanym. Wszystkie utworzone elementy są przeszukiwane, a następnie sprawdzane są ich tagi. Jeśli dany tag należy do grupy, której zmieniono kolor, id danego elementu jest dodawane do listy. Następnie elementy obecne na liście są usuwane z "canvas'u". W kolejnym kroku jest przeszukiwana główna lista wszystkich naniesionych markerów i jeśli dany marker należy do grupy, której zmieniono barwę, to jest on nanoszony na "canvas" z nową barwą.

W założeniach jest to bardzo proste, ale miałem pewien problem z implementacją, gdyż okazało się, że tagi Tkinter "canvas" działają nieco inaczej, niż to zrozumiałem z dokumentacji. Warto również wspomnieć, że tagi podczas ich przypisywania do elementów, są automatycznie konwertowane do string'a  przechowywanego w touple.

Zastosowane rozwiązania mogą również pozwolić na wybiórcze usuwane danej grupy markerów, co może umożliwić na dodanie czegoś w rodzaju warstw. Przydatnym narzędziem do dodania wydaje się również, możliwość usuwania pojedynczych markerów, na wypadek gdyby, któryś element został oznaczonym przez pomyłkę.

sobota, 1 kwietnia 2017

Statystyki, zakładka której brakowało.

Długo zastanawiałem się w jaki sposób przedstawić tą zakładkę, aby wszystko było czytelne i logicznie rozmieszczone. Niestety Tkinter nie posiada żadnego wbudowanego widgetu, który dawałby funkcjonalność siatki podobnej do tej z arkusza kalkulacyjnego, pozwalającej na równe rozmieszczenie elementów w kolumnach i rzędach. Istnieje biblioteka, która dodaje tą funkcjonalność, ale jak już wcześniej zaznaczałem, chcę ograniczyć korzystanie z zewnętrznych bibliotek do absolutnego minimum.

Pozostaje ręczne budowanie interface'u. Wykorzystanie kopiuj/wklej przy budowie interface'ów w Tkinter to absolutna podstawa. Przydatna jest również opcja "zamień". Czasami jeśli wymaga tego sytuacja, pomagam sobie również, arkuszem kalkulacyjnym. Niewiele osób zdaje sobie sprawę, ale programu te posiadają bardzo przydatne funkcje pozwalające na obróbkę tekstu. Bardzo łatwo przygotować sobie dwie odrębne kolumny w programie tekstowym, a następnie scalić je w arkuszy kalkulacyjnym.

Użyłem tego sposobu do utworzenia słownika, który posiadał rozbudowany klucz będący ciągiem znaków. Jest to bardzo przydatne, ponieważ pozwala zaoszczędzić sporo czasu, na ręcznej zmianie poszczególnych znaków.

piątek, 31 marca 2017

Rollback'a nie było, wiedzy przybyło.

Okazało się, że moje założenia dotyczące konieczności przeprowadzenia rollback'a, były niepotrzebne. Testowałem różne możliwe rozwiązania i nie podobało mi się, że spora część mojej pracy zostanie zmarnowana, przez to, że nie uwzględniłem przekazywania atrybutów w Tkinter.

Wygrało najprostsze rozwiązanie - tworzenie referencji.

Zastosowałem również najprostszą opcję, czyli użycie listy. Nie sprawdziłem, czy dało by radę zrobić to ze słownikiem. Aczkolwiek ze względu na zastosowanie słowników w przekazywaniu wielu elementów jako key word arguments w "**kwargs" nie sądzę, aby to było możliwe. Nie natrafiłem również na żadną wzmiankę poruszającą ten temat.

Jedyny mankament związany z wykorzystaniem list w Tkinter jest bug związany z kopiowaniem listy. Trafiłem na niego pisząc prostą grę typu "snake" wykorzystując właśnie Tkinter. Uznałem to za pewnego rodzaju eksperyment i jednocześnie wyzwanie, gdyż gdzieś na "stacku" przeczytałem, że się nie da. Efektem ubocznym tego projektu, było znalezienie bug'a związanego z kopiowaniem listy.

Otóż, w Python jeśli kopiuje się listę nazwaną "a" do listy o nazwie "b" nie wystarczy postawić zwykłego znaku równości:

a = [1, 2, 3, 4, 5]
b = a

Kod ten nie spowoduje utworzenia kopii listy "a", zmienna "b" będzie jedynie wskazywała (będzie referencją) do miejsca w pamięci gdzie znajduje się lista "a".  Aby skopiować listę, trzeba użyć:

b = a[:]

Spowoduje to utworzenie w pamięci kopii listy "a" i przypisanie jej do zmiennej "b". Błąd, który znalazłem powodował, że ten mechanizm nie działał poprawnie. Do przeprowadzenia kopiowania listy było konieczne użycie modułu "deepcopy".

poniedziałek, 27 marca 2017

Commit przed rollback.

Dowiedziałem się dlaczego programy w Python korzystające z Tkinter pisane są w jednej głównej, dużej klasie. Otóż przekazywanie danych między wieloma klasami jest problematyczne. Metoda wywoływana w czasie naciśnięcia przycisku, obsługująca dany "event", nie jest w stanie zwrócić wartości z użyciem "return". To znaczy teoretycznie dana wartość zostanie zwrócona, ale nie do klasy znajdującej się wyżej w hierarchii, tylko do "mainloop" Tkinter. Przy czym nie można jej w żaden sposób przechwycić.

Istnieje kilka możliwości w jaki sposób można to naprawić. Najprostszym rozwiązaniem wydaje się przypisanie głównych instancji klas, których używam do przechowywania najważniejszych informacji, do global namespace. Jednak takie rozwiązanie jest odradzane, że względu na wolniejszy dostęp do tych danych oraz jest postrzegane jako coś co zrobiła by osoba początkująca. Utworzona lista zawierająca markery będzie przeszukiwana, dlatego z powodu wolniejszego dostępu do tych danych takie rozwiązanie odpada. Kolejną możliwością jest utworzenie serwera i wymiana danych za jego pomocą, jednak takiego rozwiązania nigdy nie stosowałem, nigdy również nie interesowałem się jak takie rozwiązanie zaimplementować w Python. Chciałbym uniknąć takiej implementacji na siłę, a drugą sprawą jest, że nie mam pojęcia jak wyglądała by stabilność takiego rozwiązania. Innym sposobem na obejście tego problemu jest zapisywanie i odczytywanie istotnych informacji z pliku. Jednak takie rozwiązanie również nie wygląda zbyt profesjonalnie.

Nie wiem natomiast czy możliwe jest przekazywanie referencji do instancji klasy jako argumentu w Python. Pobieżne przejrzenie informacji dostępnych na "stacku" pokazuje, że można to zrobić jedynie z zmiennymi (mutable) typami, jako przykład podana jest lista. Nie mam pojęcia czy przekazanie instancji w liście załatwi sprawę, ale takie rozwiązanie nie wydaje mi się zbyt eleganckim.

Stąd powrót do struktury programu opartej o jedną dużą główną klasę jest najprostszym podejściem, mimo że kod stanie się mniej czytelny. Kiedy podstawowe założenia projektu zostaną zrealizowane, będę miał czas na eksperymenty.

niedziela, 26 marca 2017

Wczytywanie zdjęć w Tkinter.

Założeniem, jakie przyjąłem przed rozpoczęciem tego projektu, było użycie jak najmniejszej ilości zewnętrznych pakietów (bibliotek) Pythona. Wiedziałem, że wykorzystanie biblioteki Pillow będzie w tym projekcie koniecznością. Tkinter ma jedną bardzo istotną wadę, pozwala jedynie na bezpośrednie wczytywanie obrazów w formatach GIF, PGM oraz PPM. Ciężko wymagać od użytkownika, aby za każdym razem konwertował zdjęcia do mało popularnych formatów, wyłącznie w celu pracy z jednym programem.

Dlatego z pomocą przychodzi Pillow. Biblioteka ta pozwala na wczytywanie ponad 30 różnych formatów graficznych plików. Posiada również wbudowaną klasę, pozwalającą na konwertowanie tych formatów, do postaci rozpoznawalnej przez Tkinter. Wystarczy do tego tylko jedna linia kodu.

loaded_image = ImageTk.PhotoImage(Image.open('full_path_to_the_image'))

Oprócz tej niezbędnej do tego projektu funkcji Pillow jest bardzo rozbudowaną biblioteką graficzną, posiadającą szereg bardzo różnych funkcji. Jest też bardzo popularny, jeśli więc dostarczona przez twórców dokumentacja okaże się niewystarczająca, z dużym prawdopodobieństwem rozwiązanie problemu odnajdzie się na "stacku".

Oczywiście istnieje szereg innych bibliotek graficznych, posiadających podobne funkcje oraz dużo więcej. Aczkolwiek moja decyzja padła na Pillow z tego względu, że już z tą biblioteką pracowałem, co bardzo ułatwia mi zadanie oraz wydaje mi się, że posiada ona wszystko to czego potrzebuję. Oczywiście jeśli chciałbym wprowadzić funkcjonalność typu: przechwytywanie obrazu z kamery mikroskopu. To biblioteka ta, będzie niewystarczająca. Planując taką funkcjonalność lepiej użyć np. openCV.

Wybierając bibliotekę graficzną, kierowałem się również łatwością jej instalacji, gdyż zdawałem sobie sprawę, że jeśli nie będę rozprowadzał programu w formie pakietu, to na użytkowniku będzie spoczywał wymóg zainstalowania odpowiednich bibliotek. Pillow można zainstalować przez pip, manager pakietów Pythona. Pobierze on automatycznie i zainstaluje odpowiednią wersję biblioteki, dla danej wersji Pythona, co jest bardzo wygodnym i odpornym na błędy rozwiązaniem.

sobota, 25 marca 2017

Kompartmentacja kodu.

Lubię czysty i przejrzysty kod. W takim kodzie można prościej wprowadzać zmiany oraz łatwiej rozeznać się przy powrocie do pracy, nad danym programem, po dłuższej przerwie.

Kod BioCounter'a stał się zbyt nieczytelny i nadeszła pora na poprawienie jego przejrzystości. Podzieliłem główną klasę na mniejsze fragmenty i porozmieszczałem je w odrębnych klasach i plikach. Podejście jedna klasa, w jednym pliku zostało doradzone mi przez bardziej doświadczonych programistów. Stosowanie takiego zabiegu sprzyja przejrzystości całego programu, a w momencie kiedy trzeba poszukać danej klasy, aby wprowadzić w niej zmiany, nie trzeba przeglądać całego pliku, czy też korzystać z opcji wyszukaj.

Zwrócono mi również uwagę na to, że główna klasa była zbyt długa. Podział głównej klasy na mniejsze klasy, spowodował jej spore skrócenie, część metod wywoływanych przez event'y na poszczególnych widget'ach, również znalazła się w odpowiednich dla danych elementów klasach.

Programując staram się również stosować do zasady SOLID, dla zwiększenie przejrzystości i czytelności kodu.

czwartek, 23 marca 2017

Ustawienia i zmienne.

Pracowałem nad możliwością zmiany klasyfikatorów w markerach i dostrzegłem kolejne ograniczenie Tkinter.

Wyświetlanie tekstu na przycisku jest bardzo proste. Wystarczy do klasy przekazać dany tekst, jako atrybut "text". Dany napis zostanie wtedy wyświetlony na przycisku, a wielkość (długość) przycisku zostanie dopasowana do długości przekazanego tekstu. W ten sposób nic nie zostanie ucięte. Schody zaczynają się, kiedy wielkość przycisku "width" zostanie odgórnie zdefiniowana.

Niestety Tkinter próbuje umieścić na przycisku cały przekazany tekst, a jeśli napis jest zbyt długi, wtedy widoczny jest tylko jego początek. Wygląda to brzydko, a dodatkowo cięcie napisu odbywa się nie pomiędzy danymi literami, tylko miejscu, w którym kończy się "pole" do wyświetlania tekstu, a granica ta może przebiegać przez środek litery. Czyli wygląda to jeszcze brzydziej. Prostym rozwiązaniem byłoby przycięcie ciągu znaków, poprzez zastosowanie "[:]", ale tekst na przyciskach nie jest typu string, tylko odrębną klasą Tkinter StringVar(), która takiej funkcji nie posiada.

Koniecznością okazało się utworzenie osobnego słownika, przechowującego zmienne odpowiedzialne, za przechowywanie tekstu, który znajdzie się na przyciskach. Dodatkowo tekst ten jest obrabiany do odpowiedniej długości. W ten sposób jeśli nowy klasyfikator przekroczy długość 3 znaków, zostanie wyświetlony na przycisku jako skrót opatrzony na końcu kropką, aczkolwiek podczas eksportu statystyk do plików zostanie wyeksportowana jego pełna nazwa. Dzięki temu prostemu zabiegowi GUI będzie wyglądało poprawnie i żadne informacje nie zostaną utracone.

niedziela, 19 marca 2017

Eventy i dezaktywacja przycisków.

Lubię pisać dobry kod, a przede wszystkim czytelny oraz oparty na jednorodnych rozwiązaniach. Nie mogłem zrozumieć dlaczego dezaktywację przycisków zaprojektowano w taki właśnie sposób i dlaczego zmiana atrybutu "state" na "disabled" nie powoduje zatrzymania wywoływania przypisywanej metody. Postanowiłem zbadać sprawę i znalazłem wytłumaczenie.

W Tkinter istnieją dwa sposoby na przypisanie metody do przycisku (a tak na prawdę do każdego widget'u). Można to zrobić za pomocą metody "bind" oraz za pomocą atrybutu "command" będącego w klasie samego widget'u. Czyli odpowiednio:

self.button = ttk.Button(root, text='Press to activate', 
    command=self.method_to_execute)
self.button.place(x=0, y=0)

lub

self.button = ttk.Button(root, text='Press to activate')
self.button.place(x=0, y=0)
self.button.bind('<Button-1>', self.method_to_execute)

Otóż zmiana atrybutu "state" na wartość "disabled" powoduje dezaktywację przycisku jeśli przypisano mu metodę bezpośrednio do atrybutu "command". Jeśli do przypisania metody użyje się ".bind" to mimo zmiany wyglądu przycisku na nieaktywny, po jego naciśnięciu przypisana metoda zostanie wykonana. Jedynym sposobem na zablokowanie wykonywania tej metody jest użycie metody "unbind" na instancji klasy. Co będzie wyglądało następująco:

self.button.unbind('<Button-1>')

Do metody "unbind" można jedynie przekazać event, który ma zostać odłączony, jeśli wraz z event'em zostanie przekazana metoda, kompilator wywali błąd.
Po zmianie statusu przycisku na aktywny, potrzebne jest ponowne użycie metody "bind", co wiąże się również z przekazywaniem metody, która ma zostać po naciśnięciu przycisku wykonana. Nie jest to zbyt dobre rozwiązanie, ponieważ ilość kodu rośnie, a wraz z nim możliwość powstania błędów. Jednak ma tą zaletę nad "command", że pozwala na przypisywanie różnych event'ów, "command" jest wykonywane po naciśnięciu lewego przycisku myszy. Chociaż nie mogę powiedzieć, że klikanie innym przyciskiem nie działa, ponieważ sam nigdy tego nie próbowałem :)

Niedogodności związane z koniecznością używania "bind" oraz "unbind" można w pewien sposób złagodzić, umieszczając instancje przycisków w słowniku. W ten sposób używając odpowiednich kluczy oraz instrukcji warunkowych można zaimplementować nie najgorsze rozwiązanie, jednak jeśli do przycisk korzysta z więcej niż jednego event'u, to rozwiązanie to zaczyna się komplikować. Dodatkowo jeśli wraz z event'em przekazujemy do metody pewne zmienne to złożoność dodatkowo rośnie. Przykładowa implementacja nie wygląda zbyt ładnie:

for k, v in self.buttons.items():
    if k == 'button_a':
        v.bind('<Button-1>', lambda event, var_1=(1, 2, 3, 5, 7),
            block=self.block_size:self.process_method(event, var_1, block))
        v.bind('<Button-2>', lambda event,
            text_index=1:self.show_tip(event, text_index))
        v.bind('<Button-3>', self.rest_process_settings)

Podany przykład dotyczy tylko jednego przycisku, a powstała konstrukcja już jest mało czytelna. Oczywiście można wypchnąć to do zewnętrznej klasy, w ten sposób nie będzie zmniejszało czytelności głównej klasy okna, ale nie zmienia to faktu, że ten mało czytelny kod trzeba będzie gdzieś umieścić.

Dość prostym rozwiązaniem jest użycie "command" oraz "bind" w jednej instancji przycisku, jednak nie jestem fanem takiego typu rozwiązań.

Jeśli chcesz dowiedzieć się więcej o eventach zajrzyj np. tu.

sobota, 18 marca 2017

Rozdzielczości i budowanie interfejsu.

Dynamiczne dostosowywanie rozdzielczości, w reakcji na przeciąganie za krawędź ramki okna, okazało się większym wyzwaniem niż przypuszczałem. Po kilku przeprowadzonych testach doszedłem do wniosku, że zastosowanie metody ".config" na poszczególnych elementach stanowczo odpada. Jest to oczywiście wykonalne, jednak wraz ze zrostem ilości elementów, ilość kodu wymaganego do przeprowadzania konfiguracji również rośnie i nie wygląda to ani ładnie, ani czysto. Natomiast testy z zastosowaniem relacji "parent-children" okazały się niepowodzeniem. Nie znalazłem podobnego rozwiązania w internecie, które mogło by mi pomóc w zrozumieniu na czym polega błąd mojej implementacji, mimo że znalazłem kilka zastosowań metody ".winfo_children".

Kolejną sprawą jest również to, że przeliczanie pozycji i ewentualnie wielkości elementów, musiało by się również odbywać dla nieutworzonych elementów okna. Nie byłoby to problemem, jeśli elementy te byłyby tworzone, ale nie umieszczane na formularzu. Jednak jakiekolwiek użycie metody ".destroy" mogłoby powodować błędy, jeśli przy kolejnym przeliczeniu wielkości i pozycji, program próbowałby odwołać się do nieistniejącego elementu. Przez to zastosowanie interfejsu posiadającego tylko jedno okno, byłoby bardzo trudne, o ile w ogóle niemożliwe do wykonania.

Zamiast tego rozwiązania, dałem użytkownikowi do wyboru kilka rozdzielczości, wielkości okna aplikacji, a dla łatwiejszej implementacji i zmniejszenia ilości zbędnego kodu, zmiany są wprowadzane po resecie programu. Przy czym wymaga to ingerencji użytkownika i musi być wykonane ręcznie. Wybrana rozdzielczość ekranu przechowywana jest w pliku "settings" wraz z innymi ustawieniami.

Zacząłem też budować elementy z zakładki "markers". Jak na razie przewiduję możliwość zliczana 8 próbek oraz możliwość nadania 4 odrębnych klasyfikatorów, ale będących wspólnymi dla wszystkich próbek. Wywodzi się to z prostego sposobu rozumowania. Zakładając przypadkowo, że jeśli ornitolog będzie chciał zliczyć ptaki z wczytanej fotografii, to określonymi próbkami będą dane gatunki ptaków, czyli przypuśćmy dla zobrazowania - "Sikora Bogatka", "Sikora Modra" i "Zięba". Natomiast obranymi przez owego ornitologa klasyfikatorami będą mogły być - "Samiec", "Samica", "Osobnik młodociany" i "Nie można określić", które są wspólne dla wszystkich próbek. Dzięki temu podziałowi przeliczanie statystyk oraz eksportowanie pliku z wynikami będzie proste.

Wyzwaniem będzie skonfigurowanie przycisku wyboru klasyfikatora, jako przełącznika, to znaczy tak aby po naciśnięciu pozostał on widoczny jako wciśnięty. Można tego dokonać poprzez zastosowanie metody ".state". Aczkolwiek zmiana ta tyczy się tylko wyglądu. Przycisk, któremu nadano status "disabled" pozostaje nadal aktywnym, a kliknięcie na niego spowoduje wywołanie przypisanej metody. W tym przypadku nie jest to problem, ponieważ ponowne naciśnięcie danego przycisku spowoduje jedynie ponowną aktywację wybranego już markera. Jednak jeżeli chciałbym zastosować tą metodę, do przycisku, który powiększa marker, a wielkość markera miałaby pewne ograniczenie w wielkości, to napotkam już pewien problem. Można to w prosty sposób obejść, ale owe obejście nie jest sposobem eleganckim, jest w założeniu proste dla jednego przycisku. Jednak w momencie wzrostu ilości takich przycisków i chęci sekwencyjnego ich aktywowania, kiedy np. Przycisk 2 staje się aktywny po naciśnięciu Przycisku 1, złożoność programu rośnie i trudniej określić, które przyciski w danym momencie mają pozostać aktywne. Być może uda mi się znaleźć rozwiązanie, które będzie bezpośrednio blokowało event przycisku, utworzenie nowej klasy z wykorzystaniem dziedziczenia i dodanie nowego atrybutu wydaje się być równie dobrym pomysłem.

wtorek, 14 marca 2017

Plik z ustawieniami i rozdzielczości ekranu.

Postanowiłem dodać możliwość zapisywania i odczytywania ustawień programu z pliku. Jak na razie znalazły się tam ustawienia związane z rozdzielczością ekranu i inne ustawienia związane z wyświetlaniem obrazu. Wszystkie wartości ustawień przechowywane są w pliku tekstowym, dzięki czemu użytkownik może je edytować ręcznie, ale również wczytywanie opcji i wartości do klasy nie sprawia żadnych problemów.

Dostrzegłem też spory błąd. Kodowałem rozmiary i położenie elementów formularza w sposób statyczny, to znaczy używałem zmiennych, ale były one wykorzystywane w nieodpowiedni sposób i nie dawały odpowiedniego efektu. Przerobię to w ten sposób, że rozmiary i położenie zagnieżdżonych elementów będzie obliczane na podstawie tychże obiektów nadrzędnych. W ten sposób wielokrotne zagnieżdżanie obiektów będzie miało większe znaczenie i będzie lepiej użyte, poprzez zastosowanie relacji "parent-child/children".

Testowałem również reakcję elementów na zmianę wielkości okna, poprzez przeciąganie jego krawędzi, znalazłem jedno rozwiązanie, ale nie było ono zbyt ładne. Wymagało każdorazowej konfiguracji elementów, poprzez zastosowanie metody "config". Mimo, że takie rozwiązanie jest proste i działa, to jego złożoność rośnie wraz z wzrastającą ilością elementów. Niestety w takim przypadku łatwo o błędy, a i sam kod nie będzie zbyt czytelny.

Utworzyłem również wirtualną maszynę na której zainstalowałem Linuxa. Nie wiem czy będę prowadził wsparcie dla tego systemu. Na razie służy to tylko i wyłącznie testom. Nigdy nie zagłębiałem się w różnicę między skryptami Pythona na Windowsa i Linuxa. Różnice są, a jedną z tych o której mi wiadomo jest obsługa rolki myszki. Ta funkcjonalność będzie ważna ponieważ przewiduję zaprogramować funkcję przybliżania i oddalania zdjęcia w oknie programu, z użyciem kombinacji klawisza "Ctrl" oraz kółka myszy. Takie rozwiązanie zastosowano w wielu programach graficznych i jeśli ktoś miał z nimi styczność, jest bardzo intuicyjne.

niedziela, 12 marca 2017

Okienka i ramki, dużo ramek.

Budowanie interface'u graficznego z użyciem Tkinter nie jest proste. Nie dostajemy żadnego wbudowanego narzędzia, za pomocą którego możemy tworzyć formularze za pomocą metody "drag and drop". Istnieją do tego zewnętrzne narzędzia, ale nie są one jakoś specjalnie rozwijane i w zasadzie nigdy z nich nie korzystałem.

Pisanie ręczne jest mozolne, a nanoszenie wszelkich poprawek dotyczących umiejscowienia elementów wymaga cierpliwości. Jednak jest to do zrobienia i robiłem to przy każdym interface'ie graficznym wykorzystującym metodę "place".

Dobrym nawykiem wydaje się grupowanie i zakotwiczanie elementów z zastosowaniem wielu ramek. Pozwala to na lepszą i wygodniejszą kontrolę nad obiektami, jeśli trzeba przesunąć większą ich ilość. Generalnie kompartmentacja tych elementów, podobnie jak kompartmentacja kodu jest dobrą praktyką. Interesującą sztuczką jest zastosowanie atrybutu "ttk.Frame" o nazwie "relief" i ustawieniu jego wartości na "GROOVE" (bez cudzysłowu). Granice ramki stają się widoczne, dzięki powstałemu na krawędziach zagłębieniu. Trzeba pamiętać również, że obiekty stylizowane "ttk" (prawie dosłowne tłumaczenie) mimo, że mogą należeć do tej samej rodziny co bazowe elementy, np. Button, Frame, czy Label, mogą posiadać inne opcje. To znaczy mogłoby wydawać się, że "ttk.Frame" utworzono dziedzicząc klasę "Frame", ale niestety nie działa to w ten sposób. Podany przykład jest celowy, ponieważ obiekt "Frame" generalnie służy tylko do zakotwiczania i grupowania elementów, a mimo tego istnieją różnice miedzy "Frame" i "ttk.Frame". W zasadzie nie wiem na jakich regułach oparto tworzenie obiektów "ttk", nigdy się w ten temat nie zagłębiałem. Dużą zaletą "ttk" jest możliwość tworzenia stylów, które możemy przypisywać do różnych elementów formularza, ponadto "ttk" rozpoznaje domyślny styl systemu i dostosowuje do niego wygląd prezentowanego okna programu.

Konsola języka Python "IDLE" została zbudowana z zastosowaniem Tkinter i obiektów stylizowanych "ttk".

sobota, 11 marca 2017

Nawet w krótkim kodzie mogą czyhać błędy.

Ledwie co zacząłem kodować, a już zdążyłem odnaleźć pierwsze problemy.

Jednak, aby sprawę wyjaśnić muszę naświetlić jak wygląda tworzenie okienek i ich elementów w Tkinter. Otóż istnieją trzy sposoby:
- pack - polegający na stosowaniu, w sumie sam nie wiem czego, gdyż ten sposób jest tak bardzo prymintywny, że nigdy go nie stosowałem,
- grid - polegający na układaniu elementów z zastosowaniem siatki oraz z pomocą kierunków (E, W, N, S) oraz przyciągania elementów do tychże kierunków,
- place - polegający na zastosowaniu współrzędnych "x" oraz "y" w pikselach i na podstawie tych współrzędnych umieszczanie elementów w okienku.

O ile przy zastosowaniu metody "grid" nie ma problemu z minimalnymi zmianami wielkości okna, gdyż wszystko się do siebie relatywnie przyciąga, oraz przyciąga do kierunków zakotwiczenia. To przy nieodpowiednim zakotwiczaniu obiektów, ich nieodpowiednie rozjeżdżanie się jest bardzo prawdopodobne. Czyli, najprościej to ujmując, zmiana wielkości okna nie lubi się z tą metodą i generalnie nie wygląda to ładnie. Zastosowanie metody "place" pozwala na ustawianie elementów okienek z dokładnością do jednego piksela, ale o ile koordynaty nie są opisane wzorami (określającymi relatywne położenie względem siebie), to zmiana wielkości okna wiąże się z tym, że wszystkie obiekty pozostają zakotwiczone w swoich stałych miejscach.  Optymalnym rozwiązaniem byłoby wymieszanie rozwiązań z metody "grid" oraz metody "place", ale niestety Tkinter nie pozwala na takie rozwiązania.

Problem polegał na tym, że program pobierał z systemu informację o ustawionej rozdzielczości ekranu i na jej podstawie określał wielkość okna (odejmując kilka pikseli na ramkę oraz pasek start). Sęk w tym, że na systemach posiadających wiele aktywnych monitorów, okno programu było rozciągnięte na wiele monitorów i nie można było jego rozmiaru zmienić, ponieważ taką możliwość zablokowałem poprzez użycie:

root = Tk()
root.resizable(width=0 height=0)


Dlatego też stworzyłem prostą klasę zawierającą statyczną metodę, która sprawdza rozdzielczość ekranu oraz jego proporcję i na tej podstawie dobiera rozdzielczość okienka programu. Mimo tego, że rozwiązanie jest prymitywne to powinno działać. Niestety w domu nie jestem w stanie tego sprawdzić, gdyż nie mam dodatkowego monitora do laptopa. W przypadku gdyby zastosowane rozwiązanie sprawiało problemy mogę użyć biblioteki "ctypes" pozwalającej na dostęp do zasobów i informacji z systemu operacyjnego, wadą tego rozwiązania jest to, że jest ono typowo windowsowe. Mimo, że na razie rozwijam ten program typowo na Windowsa, to w przyszłości chciałbym go również przetestować na linuxie (na wirtualnej maszynie z zainstalowanym linuxem), a zastosowanie typowej dla Windowsa biblioteki pozbawi mnie tej możliwości.

piątek, 10 marca 2017

Konkurs i wybór tematu.

Postanowiłem wystartować w konkursie (link), a w zasadzie zostałem do tego namówiony.

Na potrzeby tego konkursu postanowiłem stworzyć prostą aplikację do liczenia i oznaczania elementów na potrzeby nauk biologicznych. Założenie jest takie, że ładujemy zdjęcie do programu, zostaje ono wyświetlone w oknie jak w każdym programie graficznym i następnie po utworzeniu prostej kategorii możemy na to zdjęcie nanosić znaczniki graficzne, które są następnie automatycznie zliczane. Po zliczeniu wszystkich potrzebnych elementów można to zdjęcie wraz ze znacznikami zapisać, a otrzymane statystyki wyeksportować do pliku, w celu dalszego obrobienia danych w przeznaczonym do tego programie.

Jako język programowania wybrałem - Python. Zdaję sobie sprawę, że istnieją bardziej odpowiednie języki programowania do zbudowania offline'owej aplikacji na Windowsa, ale nie znaczy to, że nie można tego dokonać w Pythonie.  Zdecydowałem się również na użycie wbudowanego modułu Tkinter, gdyż posiada on całą podstawową funkcjonalność "okienek" i nie wymaga od niedoświadczonego użytkownika instalowania, żadnych zewnętrznych modułów. Jest często również postrzegany przez użytkowników jako przestarzały oraz bezużyteczny, aczkolwiek jest to dalekie od prawdy.

Zdaję sobie sprawę, że istnieje wiele bibliotek czy framework'ów, które są bardziej rozbudowane i być może nadawały by się lepiej: pyQt, wxPython, kivy oraz pygame, ale na potrzeby założonej funkcjonalności Tkinter wydaje się wystarczający.