Compare commits

..

57 Commits

Author SHA1 Message Date
s450026
356d5af77e Return dirty plate to the kitchen 2020-06-09 20:43:27 +02:00
s450026
5be76f9a4b Add good checking plates 2020-06-09 19:26:53 +02:00
s450026
575a2def45 add plate to table 2020-06-09 18:35:00 +02:00
s450026
2f76d70270 append the taskslist 2020-06-09 17:56:24 +02:00
s450026
d46137d634 clear main 2020-06-09 17:27:51 +02:00
s450026
6790341472 add walk to new guest 2020-06-09 16:21:26 +02:00
s450026
74af28c7c2 moving 2020-06-09 16:00:26 +02:00
s450026
a18927bfb4 fix simulation, fix tasklist 2020-06-09 15:32:39 +02:00
s450026
df0bf3207c Add randomly change the plate from full to empty 2020-06-09 15:08:10 +02:00
s450026
6d4744f553 Merge branch 'master' of https://git.wmi.amu.edu.pl/s444427/Sztuczna_Inteligencja_2020 2020-06-09 15:06:12 +02:00
s450026
14299fbe63 Add randomly change the plate from full to empty 2020-06-09 15:06:03 +02:00
b1c7b70ace Zaktualizuj 'main.py' 2020-06-09 12:26:23 +00:00
9acdfc0cc8 Zaktualizuj 'src/decisionTree.py' 2020-06-09 12:24:41 +00:00
s450026
35d84146d9 Add plate class, showin plate on table, predict by plate class 2020-06-09 14:13:21 +02:00
Marcin Dobrowolski
9f44a715cf gówno 2020-06-08 18:09:09 +02:00
Marcin Dobrowolski
510be39b90 Marcin Dobrowolski subproject v2 2020-06-08 18:05:05 +02:00
807eb522be Zaktualizuj 'main.py' 2020-06-05 12:18:45 +00:00
2abfdf6239 Zaktualizuj 'src/decisionTree.py' 2020-06-05 12:16:19 +00:00
Marcin Dobrowolski
55b9625189 Marcin Dobrowolski - podprojekt 2020-05-27 10:28:47 +02:00
Marcin Dobrowolski
d89e37e6eb Marcin Dobrowolski razport z podprojketu 2020-05-27 09:20:38 +02:00
Marcin Dobrowolski
68104e25c6 sugesstionDecisionTree implementation 2020-05-27 09:15:08 +02:00
Marcin Dobrowolski
a7006c1424 suggestionDecisionTree implementation 2020-05-27 09:13:23 +02:00
s450026
51ad494b5a abc 2020-05-26 23:49:00 +02:00
s450026
c462497905 fix 2020-05-26 23:23:49 +02:00
s450026
e5cb3a8722 fix report 2020-05-26 22:06:13 +02:00
s450026
bb5b59fe48 fix img 2020-05-26 21:32:22 +02:00
s450026
451b85bd17 fix img 2020-05-26 21:31:16 +02:00
s450026
41eac9a8aa Add report 2020-05-26 21:24:38 +02:00
s450026
16002209bf Add report 2020-05-26 21:23:50 +02:00
s450026
1202ad849d Fix clearing the predict 2020-05-26 18:36:02 +02:00
s450026
eb23dda5b7 Create more models and picked the best 2020-05-26 15:45:51 +02:00
s450026
2e114bc0ee Improving model, fix plate prediction 2020-05-26 15:02:34 +02:00
s450026
b8f9b15692 Improving model, fix plate prediction 2020-05-26 15:00:29 +02:00
s450026
1f9ee15a72 Add missing table 2020-05-25 15:01:56 +02:00
s450026
68a554e57e Add random picking tables with plate prediction 2020-05-25 14:59:50 +02:00
s450026
f7f3e5de84 integration plate recognition with the game, add popup 2020-05-24 23:29:17 +02:00
s450026
4dd12ebf10 Add more test photos 2020-05-24 21:15:56 +02:00
s450026
0b8838d7f1 Add relative path to graphics, matrix, plate recognition, create data, create first model and logs 2020-05-24 21:11:21 +02:00
s450026
525de869e7 Add CNN plate recognition with data 2020-05-24 19:34:19 +02:00
s450026
626c471e3e prepared environment for the subproject 2020-05-22 00:32:20 +02:00
51fa7c527d Usuń 'src/rabbit.py' 2020-05-21 16:57:08 +00:00
dd3c53c82e Zaktualizuj 'main.py' 2020-05-21 16:56:56 +00:00
43d8d957f0 Zaktualizuj 'src/waiter.py' 2020-05-19 16:32:02 +00:00
55d7b9b886 Zaktualizuj 'main.py' 2020-05-19 16:28:40 +00:00
fbf6bc089d Zaktualizuj 'main.py' 2020-05-19 16:19:34 +00:00
9a6768a89d Prześlij pliki do ''
Decision Tree
2020-05-19 16:18:03 +00:00
8396b810bb Prześlij pliki do 'src'
Decision Tree
2020-05-19 16:17:33 +00:00
dc8e83e6b2 Rabbit - first implement 2020-05-13 09:22:12 +02:00
s450026
8f80df7ce0 Merge AStar 2020-04-29 20:56:54 +02:00
6b6d7fc792 Prześlij pliki do '' 2020-04-29 09:22:50 +00:00
Marcin Dobrowolski
2de2791d79 AStar adding direction sensitive wiater model 2020-04-29 11:21:42 +02:00
Marcin Dobrowolski
fefb9419b5 5th part of AStar implementation 2020-04-29 11:12:18 +02:00
Marcin Dobrowolski
7a65079aa2 4th part of AStar implementation 2020-04-28 18:56:50 +02:00
Marcin Dobrowolski
0fa109ef35 3rd part AStar implementation 2020-04-27 21:56:17 +02:00
Marcin Dobrowolski
96b42fb6c0 2nd part AStar implementation 2020-04-26 14:14:35 +02:00
Marcin Dobrowolski
cdbc599d14 1st part AStar implementation 2020-04-24 16:14:58 +02:00
s450026
c227126bb2 Add raport and images 2020-04-07 23:20:53 +02:00
554 changed files with 2343 additions and 50 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
bin
venv
.venv
__pycache__

282
MarcinDobrowolski.md Normal file
View File

@ -0,0 +1,282 @@
# Podprojekt indywidualny - raport
**Temat:** Sugerowanie potraw
**Metoda uczenia:** Decision Tree(CART)
**Autor:** Marcin Jerzy Dobrowolski
**Źródła:** https://github.com/random-forests/tutorials/blob/master/decision_tree.ipynb
**Link do repozytorium projektu:** https://git.wmi.amu.edu.pl/s444427/Sztuczna_Inteligencja_2020
## Wstęp
Projekt rozwiązuje problem zasugerowania dania przez kelnera na podstawie danych reprezentujących preferencje gościa. W tym celu wykorzystałem metodę uczenia drzew decyzyjnych CART, którą zaprogramowałem w czystym języku python. Struktura podprojektu:
* [suggestionDecisionTree.py](src/SubprojectMarcinDobrowolski/suggestionDecisionTree.py) - klasa drzew decyzyjnego
* [question.py](src/SubprojectMarcinDobrowolski/question.py) - klasa pytania
* [leaf.py](src/SubprojectMarcinDobrowolski/leaf.py) - klasa liścia
* [decisionNode.py](src/SubprojectMarcinDobrowolski/decisionNode.py) - klasa wierzchołka pytającego
* [utility.py](src/SubprojectMarcinDobrowolski/utility.py) - klasa użytkowa
* [trainingData.csv](src/SubprojectMarcinDobrowolski/Data/trainingData.csv) - zbiór uczący
* [testData.csv](src/SubprojectMarcinDobrowolski/Data/testData.csv) - klasa użytkowa
Struktura danych reprezentujących preferencje gościa:
type, size, money, appetite, name
['meat', 'small', 31, 55, 'schabowy']
* type - typ potrawy: wegańska lub z mięsem
* appetite - apetyt gościa, świadczy o tym jak duża potrawa spełni jego oczekiwania,
np. duże danie będzie inne dla osób o dużym i małym apetycie
* size - rozmiar dania
* money - zawartość portfela gościa, czyli na jak dużo może sobie pozwolić
* name - etykieta potrawy, dopasowana w taki sposób by spełnić powyższe wymagania
## Opis modułów
W pliku *utility* znajdują się podręczne funkcje użytkowe wykorzystywane przez pozostałe moduły programu.
def uniqueValues(rows, column):
return set([row[column] for row in rows])
Znajduje unikalne wartości wyznaczonej kolumny z podanego zbioru danych.
def classCount(rows):
counts = {}
for row in rows:
label = row[-1]
if label not in counts:
counts[label] = 0
counts[label] += 1
return counts
Podlicza ilość elementów każdej etykiety z danego zbioru danych.
def is_numeric(value):
return isinstance(value, int) or isinstance(value, float)
Sprawdza czy podana wartość jest liczbą.
def partition(rows, question):
trueRows, falseRows = [], []
for row in rows:
if question.match(row):
trueRows.append(row)
else:
falseRows.append(row)
return trueRows, falseRows
Dzieli dany zbiór ze względu na zadane pytanie.
def generateTestData(path, quantity):
with open('path', 'w') as csvFile:
csvWriter = csv.writer(csvFile, delimiter=',')
...
Generuje zbiór danych testowych o zadanej wielkości i zapisuje go w pliku o podanej ścieżce. Ze względu na długość kodu nie zamieściłem go w całości. Dane są generowane przy użyciu liczb pseudo losowych w taki sposób by były zgodne z prawami rządzącymi tym zbiorem danych.
def generateTestExample():
example = []
category = random.randrange(0, 1)
size = random.randrange(0, 2)
if category == 0:
example.append('meat')
...
Działa na takiej samej zasadzie jak jej poprzedniczka. Z tym wyjątkiem, że generuje pojedynczy przykład.
Klasa *Question* reprezentuje pytanie, które służy do podziału danych w wierzchołkach decyzyjnych drzewa. Jej atrybuty oznajczają kolejno: etykiete kolumny zbioru danych, indeks wspomnianej kolumny oraz wartość, której dotyczyy pytanie.
class Question:
def __init__(self, columnLabel, column, value):
self.columnLabel = columnLabel
self.column = column
self.value = value
def match(self, example):
val = example[self.column]
if is_numeric(val):
return val <= self.value
else:
return val == self.value
Metoda match decyduje jak na pytanie odpowiada zadany przykład.
Jądro algorytmu znajduje się w klasie *suggestionTree* w pilku *suggestionDecisionTree*. Metoda *readTrainingData* służy do odczytania zbioru uczącego z pliku *trainingData.csv* i zwrócenia przechowywanych w nim danych wraz z ich etykietami.
def readTrainingData(path):
with open(path) as csv_file:
csvReader = csv.reader(csv_file, delimiter=',')
lineCount = 0
trainingData = []
labels = []
for row in csvReader:
example = []
for column in row:
if lineCount == 0:
labels.append(column)
else:
if column.isdigit():
example.append(int(column))
else:
example.append(column)
if lineCount > 0:
trainingData.append(example)
lineCount += 1
print('Processed lines: ', lineCount)
return trainingData, labels
Jest wykorzystana w inicjalizacji zmiennej globalnej *trainigData* oraz *labels*, z których korzystają niektóre z metod tej klasy.
```
trainingData, labels = SuggestionTree.readTrainingData(
'src/SubprojectMarcinDobrowolski/Data/trainingData.csv')
```
Metoda *gini* oblicza tzw. *gini impurity*. Jest to miara, która mówi jak duże jest prawdopodobieństwo by losowo wybrany element ze zbioru został błędnie oznaczony.
def gini(rows):
counts = classCount(rows)
impurity = 1
for lbl in counts:
prob_of_lbl = counts[lbl] / float(len(rows))
impurity -= prob_of_lbl**2
return impurity
def infoGain(left, right, currentUncertainty):
p = float(len(left)) / (len(left) + len(right))
return currentUncertainty - p * SuggestionTree.gini(left) - (1 - p) * SuggestionTree.gini(right)
Natomiast metoda *infoGain* oblicza w jakim stopniu zmniejsza się wskaźnik *gini impurity* ze względu na wybór lewego i prawego podziału zbioru.
Metoda *findBestSplit* znajduje pytanie, które w danej chwili spowoduje jak największy przyrost informacji.
def findBestSplit(rows):
bestGain = 0
bestQuestion = None
currentUncertainty = SuggestionTree.gini(rows)
nFeatures = len(labels) - 1
for column in range(nFeatures):
values = set([row[column] for row in rows])
for value in values:
question = Question(labels[column], column, value)
trueRows, falseRows = partition(rows, question)
if len(trueRows) == 0 or len(falseRows) == 0:
continue
gain = SuggestionTree.infoGain(
trueRows, falseRows, currentUncertainty)
if gain > bestGain:
bestGain, bestQuestion = gain, question
return bestGain, bestQuestion
Klasa *Leaf* jest reprezentacją liścia drzewa czyli najbardziej zewnętrznego wierzchołka. W atrybucie *predictions* przechowuje oszacowanie etykiet/y, które może przyjąć przykład, który doń trafi. Metoda *printLeaf* służy do eleganckiego wyświetlania oszacowania.
class Leaf:
def __init__(self, rows):
self.predictions = classCount(rows)
def printLeaf(self):
total = sum(self.predictions.values()) * 1.0
probs = {}
for label in self.predictions.keys():
probs[label] = str(
int(self.predictions[label] / total * 100)) + '%'
return probs
Klasa *DecisionNode* reprezentuje wierzchołek drzewa, w którym następuje zadanie pytania oraz podział danych. Atrybuty *trueBranch* oraz *falseBranch* reprezentują odpowiednio lewego i prawego potomka.
class DecisionNode:
def __init__(self,
question,
trueBranch,
falseBranch):
self.question = question
self.trueBranch = trueBranch
self.falseBranch = falseBranch
Metoda *buildTree* odpowiada za rekurencyjne skonstruowanie drzewa na bazie przekazanego zbioru danych. Zwraca korzeń drzewa.
def buildTree(rows):
gain, question = SuggestionTree.findBestSplit(rows)
if gain == 0:
return Leaf(rows)
trueRows, falseRows = partition(rows, question)
trueBranch = SuggestionTree.buildTree(trueRows)
falseBranch = SuggestionTree.buildTree(falseRows)
return DecisionNode(question, trueBranch, falseBranch)
Metoda *printTree* wyświetla strukturę drzewa w postaci tekstu w wierszu poleceń.
def printTree(node, spacing=' '):
if isinstance(node, Leaf):
print(spacing + 'Predict', node.predictions)
return
print(spacing + str(node.question))
print(spacing + '--> True:')
SuggestionTree.printTree(node.trueBranch, spacing + ' ')
print(spacing + '--> False:')
SuggestionTree.printTree(node.falseBranch, spacing + ' ')
def classify(row, node):
if isinstance(node, Leaf):
return node
if node.question.match(row):
return SuggestionTree.classify(row, node.trueBranch)
else:
return SuggestionTree.classify(row, node.falseBranch)
Metoda *classify* przyporządkowuje danemu przykładowi odpowiadającą mu etykiete w drzewie.
## Działanie
Drzewo jest budowane na podstawie zbioru uczącego. Algorytm podejmuje decyzje o tym jak podzielić zbiór na podstawie przyrostu informacji wynikającego z potencjalnego podziału. Przerywa działanie gdy przyrost wynosi *0*, co oznacza, że dotarł do liścia drzewa i podział nie jest możliwy. Kończąc swoje działanie zwraca korzeń klasy *DecisionNode*, który zawiera w atrybutach potomków co umożliwia rekurencyjne schodzenie po drzewie.
W projekcie można je wykorzystać poprzez wciśnięcie, któregoś z klawiszy:
* 0 - sprawdzenie poprawności algorytmu na losowo wygenerowanym zbiorze testowym. =
* 1 - sprawdzenie poprawności algorytmu na losowo wygenerowanym przykładzie.
* 2 - wyświetlenie struktury drzewa
Rezultaty zostają wypisane w konsoli.
```
module: pygames
Python: 3.7.7
```
macOS / Linux
```
pygames
python main.py
```
Windows
```
pygames
python main.py
```

View File

@ -0,0 +1,159 @@
# Sztuczna Inteligencja 2020 - Raport z podprojektu
**Autor:** Maksymilian Kierski
**Raportowany okres:** 15.05.2020-26.05.2020
**Wybrana metoda uczenia:** Splotowe sieci neuronowe (CNN)
## Cel podprojektu
Celem podprojektu jest umożliwienie kelnerowi stwierdzenia czy na talerzu znajdującym się na stole jest jeszcze jedzenie, czy już go nie ma. Do tego celu zastosowałem splotowe sieci neuronowe (CNN), oraz biblioteki:
* numpy, cv2 - tworzenie danych wejściowych
* tensorflow keras - tworzenie modelu
* tensorboard - analiza modelów
## Uczenie modelu
### Dane wejściowe
Dane wejściowe składają się z dwóch rodzajów zdjęć talerzy, full - pełnych, oraz empty - pustych.
Na początku, aby nasz model mógł się nauczać potrzebujemy nasze dane wejściowe odpowiednio przetworzyć.
```
for category in CATEGORIES:
path = os.path.join(DATADIR, category)
class_num = CATEGORIES.index(category)
for img in os.listdir(path):
try:
img_array = cv2.imread(os.path.join(path, img),
cv2.IMREAD_GRAYSCALE)
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
training_data.append([new_array, class_num])
except Exception as e:
pass
random.shuffle(training_data)
```
Tutaj nasze dane wejściowe są odpowiednio przetwarzane. Na początku zdjęcie jest sczytywane, oraz przetwarzane aby każdy pixel był w skali szarości 0 - 255 (ponieważ kolor w tym zadaniu według mnie, nie odgrywa ważnej roli). Następnie skalowany jest do mniejszych rozmiarów i w końcu jako macierz zadeklarowanych wymiarów trafia do tablicy ze swoją etykietą. Na końcu cała tablica jest przetasowywana, aby umożliwić modelowi lepszą naukę.
Teraz dane zostają podzielone na zestaw cech i zestaw etykiet, oraz zostają zapisane do plików za pomocą **pickle**.
```
for features, label in training_data:
X.append(features)
y.append(label)
pickle_in = open(relative_path + 'SavedData/X.pickle', 'rb')
X = pickle.load(pickle_in)
pickle_in = open(relative_path + 'SavedData/y.pickle', 'rb')
y = pickle.load(pickle_in)
```
### Tworzenie modelu i proces jego nauki
#### Wczytywanie danych potrzebnych do nauki modelu
Na początku sczytywane są odpowiednio przygotowane dane (funkcja load_dataset)
```
pickle_in = open(relative_path + 'SavedData/X.pickle', 'rb')
X = pickle.load(pickle_in)
pickle_in = open(relative_path + 'SavedData/y.pickle', 'rb')
y = pickle.load(pickle_in)
return X, y
```
Wczytywanie danych i normalizacja X, ponieważ używam kolorów w skali szarości 0 - 255, to do normalizacji (skala 0-1) wystarczy przemnożyć każde pole macierzy przez 255. Inicjalizacja tensorboardu, który przyda się nam do analizy stworzonych przez nas modeli.
```
X = load_dataset()[0]
y = load_dataset()[1]
X = np.array(X).reshape(-1, IMG_SIZE, IMG_SIZE, 1)
y = np.array(y)
X = X / 255.0
tenserboard = TensorBoard(log_dir='{}/logs/{}'.format(relative_path, NAME))
```
#### Tworzenie sekwencyjnego modelu splotowych sieci neuronowych
Tutaj inicjalizuje model jako sekwencyjny, czyli w którym każda warstwa wykonywana jest po kolei.
```
model = Sequential()
```
Jako iż tworzę splotową sieć neuronową w której podstawową strukturą jest
```mermaid
graph LR
A((splot)) --> B(suma)
B --> C((splot))
C --> D(suma)
D --> E[w pełni połączona warstwa]
E --> F[wynik]
```
to jako pierwsze tworzę warstwę splotu i sumy.
```
model.add(Conv2D(64, (3, 3),input_shape=X.shape[1:]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
```
Warstwa splotu opiera się na 64 filtrach, które sprawdzają pola o powierzchni 9 pixeli.
Jak działa splot? Splot to czynność polegająca na pobieraniu oryginalnych danych i tworzeniu z nich mapy cech z pól o zadeklarowanych wymiarach. Jest ich tak dużo jak pozwala na to nam całość naszych danych.
Po czym wykonywana jest funkcja aktywacyjna ReLu.
A na końcu na naszej wykonujemy pooling z atrybutem **max**, czyli z wielkości 2 na 2 z naszej warstwy splotu wybieramy największą wartość.
W tym pod projekcie najbardziej efektywne okazało się zastosowanie dwóch takich warstw.
```
layer size | conv layer | Dense layer |
64 | 1 | 0 | loss: 0.0443 - accuracy: 0.9942 - val_loss: 0.3614 - val_accuracy: 0.7692
64 | 2 | 0 | loss: 0.0931 - accuracy: 0.9625 - val_loss: 0.4772 - val_accuracy: 0.8462
64 | 3 | 0 | loss: 0.2491 - accuracy: 0.9020 - val_loss: 0.3762 - val_accuracy: 0.7949
64 | 1 | 1 | loss: 0.0531 - accuracy: 0.9971 - val_loss: 0.4176 - val_accuracy: 0.8205
64 | 2 | 1 | loss: 0.0644 - accuracy: 0.9798 - val_loss: 0.5606 - val_accuracy: 0.8462
64 | 3 | 1 | loss: 0.1126 - accuracy: 0.9625 - val_loss: 0.5916 - val_accuracy: 0.8205
```
![accuracy](src/SubprojectMaksymilianKierski/Data/LogsIMG/accuracy.png)
![loss](src/SubprojectMaksymilianKierski/Data/LogsIMG/loss.png)
W drugim przypadku nie musimy już zmieniać naszych danych ponieważ pochodzą one z poprzedniej warstwy.
```
model.add(Conv2D(64, (3, 3),input_shape=X.shape[1:]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
```
Na koniec spłaszczam naszą trójwymiarową tablice, na macierz stosując funkcje Flatten(), oraz używam warstwę Dense z tylko jednym neuronem, który będzie naszym wynikiem. Funkcja aktywacji jest to oczywiście funkcja sigmoid ponieważ chcemy otrzymać wynik (0-1).
```
model.add(Flatten())
model.add(Dense(1))
model.add(Activation('sigmoid'))
```
Następnie zachodzi kompilacja modelu. Używam funkcję straty **binary_crosentropy** , ponieważ mierzymy się z problemem klasyfikacji binarnej. Problem klasyfikacji binarnej jest wtedy, gdy do rozpatrzenia mamy tylko dwa przypadki, w tym zadaniu talerz z jedzeniem(0) oraz bez jedzenia(1). Optymalizator, który użyłem jest to **adam**, a za pomocą **metrics** aktywuje monitorowanie dokładności.
Kilka słów o optymalizatorze **adam** - _Adam_, jest obecnie zalecany przy większości zadań optymalizacyjnych związanych z uczeniem, ponieważ łączy on zalety Adadelty i RMSprop, a zatem lepiej radzi sobie z większością problemów.
Na sam koniec wywołuję funkcję uczenia, oraz zapisuję model do wykorzystania go w projekcie głównym.
```
model.fit(X, y, batch_size=32, epochs=10, validation_split=0.1, callbacks=[tenserboard])
model.save(relative_path + 'SavedModels/{}.model'.format(NAME))
```
## Integracja z projektem
Podprojekt wywołujemy naciskając **m** na klawiaturze, kelner wtedy wybiera losowo stolik i do niego idzie zaimplementowanym wcześniej algorytmem A*. Po dotarciu do wybranego miejsca, możemy wywołać funkcje sprawdzającą talerz **use_model_to_predict('img')**
```
def prepare(filepath):
img_array = cv2.imread(filepath, cv2.IMREAD_GRAYSCALE)
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
plt.imshow(img_array, cmap=plt.cm.binary)
plt.show()
return new_array.reshape(-1, IMG_SIZE, IMG_SIZE, 1)
model = tf.keras.models.load_model(relative_path + 'SavedModels/plate-64x2-cnn.model')
prediction = model.predict([prepare(relative_path + 'TestData/' + name + '.jpg')])
return int(prediction[0][0])
```
Funkcja ta konwertuję zdjęcie zadeklarowane do wylosowanego stolika oraz odpowiednio je konwertuję. Następnie ładuję zadeklarowany przez nas model, który zwraca nam odpowiednią liczbę, która w int() daje 0 lub 1. Odpowiednio 0 to talerz pełny a 1 to pusty. Dzięki czemu funkcją **text_speech()** możemy wyświetlić odpowiednią informacje na ekranie.

56
TaskQueuing.md Normal file
View File

@ -0,0 +1,56 @@
# Kolejkowanie zadań
**Metoda uczenia:** Drzewa decyzyjne
**Autor:** Dominik Zawadzki
Funkcja kolejkowania zadań jak i drzewo decyzyjne znajdują się w pliku:
```sh
decisionTree.py
```
## Funkcje drzewa decyzyjnego:
```sh
BuildDf(self)
```
Buduje Data Frame złożony z trzech atrybutów:
1. actionName - nazwa zadania
2. distance - dystans pomiędzy kelnerem a stolikiem
3. priority - priorytet danej akcji
```sh
FindPriorityEntropy(self,df)
```
Oblicza entropie dla priorytetu za pomocą wzoru:
* ent = pi * log2pi
gdzie i oznacza wartośc priorytetu i = {0, 1, 2, 3, 4}
```sh
FindAttributesEntropy(self, df, attribute)
```
Zwraca entropie danego w parametrze atrybutu, gdzie jako prawdopodobieństwo liczona jest ilość wystąpień tego atrybutu w kolejnych priorytetach.
```sh
FindWinner(self, df):
```
Zwraca atrubut o najwyższym info gain wyliczanym na podstawie wzoru:
* ent(pr) - ent(atr)
gdzie ent(pr) - entropia po priorytecie
a ent(atr) - entropia atrybutu po uwzględnieniu priorytetu
```sh
BuildTree(self, df, tree=None)
```
Rekurencyjna funkcja budująca drzewo decyzyjne. Na początek wyznaczamy atrybut o najwyższym info gain, a następnie budowane jest z niego poddrzewo, jeśli dane poddrzewo jest "puste", dodajemy je do drzewa i kończymy, bo drzewo zostało zbudowane, jesli nie to funkcja wywołuje samą siebie biorąc za parametr poddrzewo.
## Funkcje kolejkowania zadań:
```sh
TasksList(self, name, coordinate):
```
Funkcja oblicza dystans dzielący kelnera od stolika i razem z nazwą zadania dodawane jest do listy zadań.
```sh
Queue(self, tasksList):
```
W tej funkcji wykorzystywane jest drzewo decyzyjne.
Dla każdego zadanie z listy zadań pętla idzie po odpowiednich wierzchołkach i odnajduje priorytet, który z poprzednimi parametrami dodawany jest do listy stanowiącej kolejkę zadań.
Na koniec funkcji kolejka jest sortowana po priorytecie.

255
main.py
View File

@ -1,22 +1,94 @@
import secrets
import sys
from src.graphics import *
from src.waiter import *
from random import randrange
from src.decisionTree import *
from src.plate import *
from src.SubprojectMaksymilianKierski.PlateRecognition import use_model_to_predict, text_speech
# Marcin Dobrowolski
from src.SubprojectMarcinDobrowolski.suggestionDecisionTree import *
from src.SubprojectMarcinDobrowolski.utility import generateTestData, generateTestExample
from src.guest import *
if __name__ == "__main__":
# SETUP
pygame.init()
clock = pygame.time.Clock()
fps = 40
graphics = Graphics()
waiter = Waiter(graphics)
tree = DecisionTree()
# init functions
# Init functions
graphics.drawBackground(waiter.matrix)
graphics.update(waiter.X, waiter.Y)
graphics.update(waiter)
# AStar
goal = None
path = ''
# Dominik
queue = []
fromBar = 0
barNode = (3, 12)
node = ()
goalList = [0, 0]
# Marcin Dobrowolski
suggestionTreeRoot = SuggestionTree.buildTree(trainingData)
newGuests = []
actions = []
# Maksymilian
go = 0
randomPlate = None
plate = 0
changePlateVar = 0
waitPos = [[1, 2], [1, 5], [1, 8], [5, 4], [5, 8],
[8, 2], [8, 5], [8, 8], [12, 3], [12, 7]]
tabPos = [[1, 2], [1, 5], [1, 8], [5, 4], [5, 8],
[8, 2], [8, 5], [8, 8], [12, 3], [12, 7]]
pltPos = [[2, 3], [2, 6], [2, 9], [4, 4], [4, 8],
[9, 3], [9, 6], [9, 9], [11, 4], [11, 8]]
plateArr = []
def changePlate():
if plateArr and len(plateArr) > 0:
if randrange(30) == 17:
plate = random.choice(plateArr)
if not plate.empty:
randomPlate = randrange(4) + 11
plate.changePlate('plate-empty.png', 'test-{}'.format(randomPlate))
def addGuest():
if randrange(10) == 5 and path == '':
newGuests.append(Guest(graphics))
guest = newGuests[len(newGuests) - 1]
goal = [0, 0]
goal[0] = guest.cord[0]
goal[1] = guest.cord[1]
tree.TasksList('order', goal, [waiter.X, waiter.Y])
tree.print()
print(actions)
return tree.ReturnQueueList()
while True:
changePlate()
# queue = addGuest()
for event in pygame.event.get():
# rabbit.check(waiter.matrix, waiter.X, waiter.Y)
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
@ -28,8 +100,173 @@ if __name__ == "__main__":
sys.exit()
break
graphics.clear(waiter.X, waiter.Y)
waiter.update(event, graphics)
graphics.update(waiter.X, waiter.Y)
if event.key == pygame.K_s:
waitPosition = [waiter.X, waiter.Y]
tree.TasksList('check', [2, 3], waitPosition)
tree.TasksList('eat', [9, 6], waitPosition)
tree.TasksList('order', [4, 8], waitPosition)
tree.TasksList('goToBar', [11, 4], waitPosition)
tree.TasksList('check', [2, 9], waitPosition)
tree.TasksList('eat', [1, 1], waitPosition)
queue = tree.ReturnQueueList()
tree.print()
# Marcin Dobrowolski
if event.key == pygame.K_0:
newGuests.append(Guest(graphics))
# Marcin Dobrowolski
if event.key == pygame.K_1:
guest = newGuests[0]
goal = [0, 0]
if guest.cord[0] == 2 or guest.cord[1] == 9:
goal[0] = guest.cord[0] - 1
goal[1] = guest.cord[1]
else:
goal[0] = guest.cord[0] + 1
goal[1] = guest.cord[1]
actions.append(('takeOrder', goal))
print(actions)
# AStar
if event.key == pygame.K_r:
temp = False
while not temp:
x = secrets.randbelow(graphics.width)
y = secrets.randbelow(graphics.height)
print(x, y)
if waiter.matrix.matrix[x][y].walk_through == 1:
temp = True
goal = (x, y)
path = waiter.findPath(goal)
path = waiter.translatePath(path)
if event.key == pygame.K_a:
if path == '':
newGuests.append(Guest(graphics))
guest = newGuests[len(newGuests) - 1]
goalList[0] = guest.cord[0]
goalList[1] = guest.cord[1]
tree.TasksList('order', goalList, [waiter.X, waiter.Y])
tree.TasksList('goToBar', goalList, [waiter.X, waiter.Y])
queue = tree.ReturnQueueList()
if plateArr and event.key == pygame.K_m:
randomPlate = random.choice(plateArr)
if (not [waiter.X, waiter.Y] == randomPlate.table) or go == 0:
print(waiter.X, waiter.Y)
print(randomPlate.table)
print(go)
print([waiter.X, waiter.Y] in randomPlate.table)
if [waiter.X, waiter.Y] in waitPos:
model = 'waiter_' + waiter.direction
for x in range(-1, 2):
waiterX = waiter.X + (x * 0.1)
graphics.clear(waiterX, waiter.Y - 1)
print(randomPlate)
changePlateVar = 0
# if this plate is exists
goal = (randomPlate.table[0], randomPlate.table[1])
path = waiter.findPath(goal)
path = waiter.translatePath(path)
print('sec-1')
go = 1
plate = 0
else:
if randomPlate.checked:
predict = 'CHECKED'
else:
predict = use_model_to_predict(randomPlate.pictureAI)
if predict == 1:
predict = 'EMPTY'
randomPlate.clearTable()
plateArr.remove(randomPlate)
tree.TasksList('goToBar', barNode, [waiter.X, waiter.Y])
queue = tree.ReturnQueueList()
changePlateVar = 1
else:
predict = 'FOOD'
print('sec-3')
text_speech('arialnarrow.ttf', 25, predict, (255, 255, 255), (0, 128, 0),
(waiter.X * 50 + 25), (waiter.Y * 50 - 25), False, False, screen=graphics.screen)
pygame.display.flip()
clock.tick(fps)
go = 0
plate = 0
if plate == 1 and (goalList == [waiter.X - 1, waiter.Y] or goalList == [waiter.X + 1, waiter.Y]):
randTable = goalList
randIndex = pltPos.index(goalList)
tableCord = tabPos[randIndex]
plateCord = pltPos[randIndex]
print('rand{}'.format(randIndex))
pictureAI = 'test-{}'.format(randIndex)
plateArr.append(Plate(graphics, plateCord, tableCord, 'plate-full.png', pictureAI))
tabPos.pop(randIndex)
pltPos.pop(randIndex)
print(plateArr, pltPos)
goal = (plateArr[len(plateArr) - 1].table[0], plateArr[len(plateArr) - 1].table[1])
randGo = len(plateArr) - 1
path = waiter.findPath(goal)
path = waiter.translatePath(path)
print('sec-2')
go = 1
plate = 0
# Dominik
if queue and path == '' and fromBar == 0:
print(queue)
task = queue.pop(0)
print('tasks.{}'.format(task))
tree.RemoveTask()
node = (task[2][0], task[2][1])
if task[0] == "goToBar":
path = waiter.findPath(barNode)
fromBar = 1
else:
changePlateVar = 0
path = waiter.findPath(node)
path = waiter.translatePath(path)
if path == '' and fromBar == 1 and changePlateVar == 0:
path = waiter.findPath(node)
path = waiter.translatePath(path)
fromBar = 0
plate = 1
if path == '' and fromBar == 1:
path = waiter.findPath(node)
path = waiter.translatePath(path)
fromBar = 0
print(plate)
# AStar
if path == '' and actions:
print('Goal: {}'.format(actions[0][1]))
path = waiter.findPath(actions[0][1])
print('Path: {}'.format(path))
path = waiter.translatePath(path)
print('Translated path: {}'.format(path))
if path != '':
nextStep = path[0]
path = path[1:]
waiter.travel(nextStep, graphics)
if path == '':
print('')
# action = actions.pop(0)
# if action[0] == 'takeOrder':
# guest = newGuests.pop(0)
# waiter.takeOrder(suggestionTreeRoot, guest)
# graphics.clearGuest(guest)
# tables.append(guest.cord)
pygame.display.flip()
clock.tick(graphics.fps)

101
path Normal file
View File

@ -0,0 +1,101 @@
type,size,money,appetite
meat,regular,29,34
meat,little,38,47
meat,regular,33,34
meat,regular,54,29
meat,regular,47,44
meat,regular,27,15
meat,regular,32,13
meat,little,53,39
meat,little,40,44
meat,regular,25,39
meat,regular,50,52
meat,regular,29,20
meat,little,58,31
meat,regular,36,45
meat,regular,59,20
meat,regular,54,48
meat,regular,25,32
meat,regular,36,23
meat,regular,57,29
meat,little,13,54
meat,little,43,52
meat,little,58,24
meat,little,30,35
meat,little,24,33
meat,regular,24,30
meat,regular,41,48
meat,regular,35,37
meat,little,36,11
meat,regular,22,24
meat,regular,58,14
meat,little,34,29
meat,little,30,38
meat,regular,54,45
meat,regular,31,43
meat,little,58,46
meat,little,48,38
meat,regular,44,44
meat,regular,51,49
meat,little,53,34
meat,regular,51,48
meat,regular,46,35
meat,little,32,22
meat,little,14,37
meat,little,14,37
meat,little,46,13
meat,regular,40,16
meat,regular,40,49
meat,little,47,40
meat,regular,23,33
meat,regular,27,29
meat,regular,49,32
meat,regular,36,15
meat,regular,59,25
meat,little,17,43
meat,regular,39,49
meat,little,35,33
meat,little,10,32
meat,little,11,49
meat,regular,53,15
meat,regular,29,13
meat,little,12,14
meat,little,27,31
meat,regular,31,21
meat,regular,58,13
meat,little,46,19
meat,little,11,27
meat,regular,44,28
meat,regular,40,14
meat,little,57,24
meat,regular,38,52
meat,little,37,31
meat,regular,34,15
meat,little,41,25
meat,regular,30,22
meat,little,58,15
meat,regular,22,12
meat,little,46,44
meat,regular,47,11
meat,little,52,30
meat,little,49,24
meat,little,28,30
meat,regular,27,19
meat,regular,51,43
meat,little,59,10
meat,regular,38,44
meat,regular,30,37
meat,little,44,24
meat,regular,34,45
meat,regular,50,49
meat,regular,36,50
meat,little,13,22
meat,little,28,25
meat,regular,29,14
meat,regular,56,17
meat,regular,57,45
meat,little,28,22
meat,regular,41,53
meat,little,10,19
meat,little,32,53
meat,little,29,22

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,6 @@
0 | price: 0 x: 0 y: 0
1 | price: 100 x: 0 y: 0
0 | price: 0 x: 1 y: 1
1 | price: 100 x: 1 y: 1
0 | price: 0 x: 2 y: 1
1 | price: 100 x: 2 y: 1

View File

@ -1,13 +1,13 @@
______________
______________
__PW_____PW___
_USWP___USWP__
__OWS____OWSI_
__SWP____SWP__
__OWS____OWS__
__PWO____PWO__
_USW____USW___
__SW_____SW___
__OWP____OWP__
__PWS____PWSI_
_USWO___USWO__
__PWS____PWS__
__SWO____SWO__
__OW_____OW___
______________
______________

77
route_planning.md Normal file
View File

@ -0,0 +1,77 @@
# Raport 2
Algorytm A* znajduje się w pliku:
```sh
waiter.py
```
jako funkcja
```sh
findPath(self, goal)
```
Rozpoczyna się stworzeniem pustych list i określeniem wierzchołka startowego i końcowego.
Dla startowego wierzchołka określany jest rodzic: *None*
## Pętla główna algorytmu
Główna pętla będzie trwać do momentu gdy *openList* będzie pusta.
Na początek *openList* jest sortowana, następnie usuwany jest z niej pierwszy wierzchołek i dodawany jest do *closedList*.
Następnym krokiem jest dodanie do tabeli *children* wierzchołki sąsiednie - sprawdzając czy takie istnieją tj. czy ich pozycja nie wychodzi poza zakres.
### Wyszukiwanie następnika
Wyszukiwanie następnika polega na znalezieniu następnego wierzchołka do jakiego powinien pójść agent. Zawarte jest ono w pętli:
```sh
for child in children:
```
Na początku sprawdzamy czy danego "dziecka" nie ma w *closedList* tzn. wykluczamy możliwość cofnięcia się agenta po wierzchołkach, które już przeszliśmy.
Potem aktualizowane są parametry wierzchołka:
- ustalamy rodzica jako *currentNode*
- modyfikujemy *startCost* dodając jedynkę do *startCost* obecnego wierzchołka
- ustalamy heurystykę jako suma długości przyprostokątnych
- obliczamy *totalCost* dodając do siebie *startCost* i *heuristic*
Na koniec sprawdzamy jeszcze czy danego dziecka nie ma już w *openList*, a jeśli nie, to dodajemy go do *openList*.
### Zwracanie ścieżki
Zwracanie ścieżki odbywa się gdy *currentNode* jest taki sam jak końcowy wierzchołek.
Tworzymy wtedy ścieżkę cofając się od końcowego wierzchołka do wierzchołka, którego rodzic jest równy: *None*
# Translacja ścieżki
Znajduje się w pliku
```sh
waiter.py
```
jako funkcja
```sh
translatePath(self, path):
```
Przyjmuje jako argument ściężkę zwracaną przez algorytm A*.
Funckja ta sprawdza każdy wierzchołek ścieżki i porównując z obecną pozycją kelnera i kierunkiem w jaki jest odwrócony "tłumaczy" go na odpowiedni ciąg znaków, gdzie:
- F - oznacza ruch do przodu
- R - obrót w prawo
- L - obrót w lewo
Następnie zwraca ciąg *output*
[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)
[dill]: <https://github.com/joemccann/dillinger>
[git-repo-url]: <https://github.com/joemccann/dillinger.git>
[john gruber]: <http://daringfireball.net>
[df1]: <http://daringfireball.net/projects/markdown/>
[markdown-it]: <https://github.com/markdown-it/markdown-it>
[Ace Editor]: <http://ace.ajax.org>
[node.js]: <http://nodejs.org>
[Twitter Bootstrap]: <http://twitter.github.com/bootstrap/>
[jQuery]: <http://jquery.com>
[@tjholowaychuk]: <http://twitter.com/tjholowaychuk>
[express]: <http://expressjs.com>
[AngularJS]: <http://angularjs.org>
[Gulp]: <http://gulpjs.com>
[PlDb]: <https://github.com/joemccann/dillinger/tree/master/plugins/dropbox/README.md>
[PlGh]: <https://github.com/joemccann/dillinger/tree/master/plugins/github/README.md>
[PlGd]: <https://github.com/joemccann/dillinger/tree/master/plugins/googledrive/README.md>
[PlOd]: <https://github.com/joemccann/dillinger/tree/master/plugins/onedrive/README.md>
[PlMe]: <https://github.com/joemccann/dillinger/tree/master/plugins/medium/README.md>
[PlGa]: <https://github.com/RahulHP/dillinger/blob/master/plugins/googleanalytics/README.md>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Some files were not shown because too many files have changed in this diff Show More