add auto mode moving - integrate astar, neural network and decision tree
This commit is contained in:
parent
f07eabf522
commit
8851493d9d
17
Readme.md
17
Readme.md
@ -10,6 +10,12 @@ virtualenv venv
|
||||
source activate
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
#### 2.1 Graphviz
|
||||
For ubuntu, we probably need to download graphviz library by apt-get
|
||||
```bash
|
||||
sudo apt-get install graphviz
|
||||
```
|
||||
|
||||
### 3. Run application
|
||||
```bash
|
||||
python3 main.py
|
||||
@ -37,13 +43,20 @@ HORIZONTAL_NUM_OF_FIELDS = 3
|
||||
|
||||
#### 4.1 Save generated map:
|
||||
```bash
|
||||
python main.py --save-map
|
||||
python3 main.py --save-map
|
||||
```
|
||||
Map will be saved in maps directory.
|
||||
Generated filename: map-uuid
|
||||
|
||||
#### 4.2 Load map
|
||||
```bash
|
||||
python main.py --load-map=name_of_map
|
||||
python3 main.py --load-map=name_of_map
|
||||
```
|
||||
Map must be in the maps directory with json extension.
|
||||
|
||||
#### 4.3 Auto mode
|
||||
Tractor will make own decisions such as harvesting, hydrating and so on using a decision tree.
|
||||
It also will be moving by a star algorithm and it will be checking fields using a neural network during harvesting crops.
|
||||
```bash
|
||||
python3 main.py --auto-mode
|
||||
```
|
@ -24,6 +24,13 @@ class App:
|
||||
self.__tractor = Tractor(self.__board)
|
||||
self.__bot_is_running = Event()
|
||||
self.__moves = None
|
||||
self.__auto_mode = self.get_auto_mode_arg(args)
|
||||
|
||||
def get_auto_mode_arg(self, args: list[Arg]):
|
||||
for arg in args:
|
||||
if arg.get_arg_name() == AUTO_MODE:
|
||||
return True
|
||||
return False
|
||||
|
||||
def initialize(self):
|
||||
pygame.init()
|
||||
@ -79,7 +86,7 @@ class App:
|
||||
self.__tractor.run_bot_handler(self.__moves, self.__bot_is_running)
|
||||
|
||||
if keys[pygame.K_c]:
|
||||
self.get_moves_by_a_star()
|
||||
self.get_moves_by_a_star_with_random_coords()
|
||||
if not self.__moves:
|
||||
print(f"A Star is failed")
|
||||
else:
|
||||
@ -88,7 +95,7 @@ class App:
|
||||
self.__tractor.run_bot_handler(self.__moves, self.__bot_is_running)
|
||||
|
||||
if keys[pygame.K_a]:
|
||||
self.auto_moving()
|
||||
self.auto_moving_mode()
|
||||
|
||||
def update_screen(self) -> None:
|
||||
pygame.display.flip()
|
||||
@ -96,19 +103,46 @@ class App:
|
||||
def quit(self) -> None:
|
||||
pygame.quit()
|
||||
|
||||
def auto_moving(self) -> None:
|
||||
self.__tractor.choose_action()
|
||||
|
||||
def get_moves_by_a_star(self) -> None:
|
||||
def auto_moving_mode(self):
|
||||
coords, action = self.get_coords_and_action()
|
||||
x, y = self.__tractor.get_position()
|
||||
node = Node(None, x, y, self.__tractor.get_direction(), 0, "movement", "initial state")
|
||||
board = copy.deepcopy(self.__board)
|
||||
if coords is not None:
|
||||
x1, y1 = coords
|
||||
else:
|
||||
x1, y1 = None, None
|
||||
print(action, coords)
|
||||
if action != A_DO_NOTHING and x1 == x and y == y1:
|
||||
self.__tractor.do_action_on_fields(action)
|
||||
elif action == A_DO_NOTHING:
|
||||
print(action)
|
||||
elif not self.__moves:
|
||||
print(f"A Star is failed")
|
||||
else:
|
||||
print(f"A Star is succeed")
|
||||
self.__bot_is_running.set()
|
||||
self.__tractor.run_auto_bot_handler(action, self.__moves, self.__bot_is_running)
|
||||
|
||||
def get_coords_and_action(self) -> tuple[tuple[int, int], str]:
|
||||
coords, action = self.__tractor.choose_action()
|
||||
if coords is not None:
|
||||
self.get_moves_by_a_star(coords)
|
||||
else:
|
||||
self.__moves = None
|
||||
return coords, action[0]
|
||||
|
||||
def get_moves_by_a_star_with_random_coords(self) -> None:
|
||||
x1 = random.randint(0, HORIZONTAL_NUM_OF_FIELDS)
|
||||
y1 = random.randint(0, VERTICAL_NUM_OF_FIELDS)
|
||||
dest = (x1, y1)
|
||||
self.__moves = AStar.search_solution(PriorityQueue(), Queue(), dest, node,
|
||||
self.get_moves_by_a_star(dest)
|
||||
|
||||
def get_moves_by_a_star(self, coords: tuple[int, int]) -> None:
|
||||
x, y = self.__tractor.get_position()
|
||||
node = Node(None, x, y, self.__tractor.get_direction(), 0, "movement", "initial state")
|
||||
board = copy.deepcopy(self.__board)
|
||||
self.__moves = AStar.search_solution(PriorityQueue(), Queue(), coords, node,
|
||||
lambda n=node, b=board: AStar.succ(n, b),
|
||||
lambda d=dest, n=node: AStar.goaltest_by_coords(d, n), board)
|
||||
lambda d=coords, n=node: AStar.goaltest_by_coords(d, n), board)
|
||||
|
||||
def get_moves_by_bfs(self) -> None:
|
||||
x, y = self.__tractor.get_position()
|
||||
@ -123,11 +157,14 @@ class App:
|
||||
|
||||
while self.__running:
|
||||
self.__clock.tick(FPS)
|
||||
|
||||
for event in pygame.event.get():
|
||||
self.event_handler(event)
|
||||
|
||||
if not self.__bot_is_running.is_set():
|
||||
if not self.__bot_is_running.is_set() and not self.__auto_mode:
|
||||
self.keys_pressed_handler()
|
||||
if not self.__bot_is_running.is_set() and self.__auto_mode:
|
||||
self.auto_moving_mode()
|
||||
|
||||
self.loop_handler()
|
||||
self.update_screen()
|
||||
|
@ -89,7 +89,7 @@ class AStar(Graphsearch):
|
||||
@staticmethod
|
||||
def search_solution(fringe: PriorityQueue, explored: Queue, destination: tuple[int, int], istate: Node,
|
||||
succ: Callable[[Node, Board], list],
|
||||
goaltest: Callable[[tuple[int,int], Node], bool], board: Board) -> Union[bool, list]:
|
||||
goaltest: Callable[[tuple[int, int], Node], bool], board: Board) -> Union[bool, list]:
|
||||
|
||||
print(f"Start A*")
|
||||
fringe.put(PriorityItem(istate, 0))
|
||||
|
@ -26,7 +26,9 @@ class CommandLineParser:
|
||||
if arg == SAVE_MAP:
|
||||
print("Saving map")
|
||||
results.append(Arg(SAVE_MAP))
|
||||
|
||||
if arg == AUTO_MODE:
|
||||
print("Auto mode")
|
||||
results.append(Arg(AUTO_MODE))
|
||||
elif arg.find(LOAD_MAP, 0, len(LOAD_MAP)-1):
|
||||
cmd = arg.split("=")
|
||||
if len(cmd) == 2:
|
||||
|
@ -1,68 +0,0 @@
|
||||
from tensorflow.keras.models import Sequential, save_model
|
||||
from tensorflow.keras.layers import Dense, Flatten, Conv2D
|
||||
from tensorflow.keras.losses import sparse_categorical_crossentropy
|
||||
from tensorflow.keras.optimizers import Adam
|
||||
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
||||
from config import RESOURCE_DIR
|
||||
import os
|
||||
|
||||
# labels
|
||||
|
||||
labels = ["cabbage", "carrot", "corn", "lettuce", "paprika", "potato", "tomato"]
|
||||
|
||||
# Data configuration
|
||||
training_set_folder = os.path.join(RESOURCE_DIR, 'smaller_train')
|
||||
test_set_folder = os.path.join(RESOURCE_DIR, 'smaller_test')
|
||||
|
||||
# Model config
|
||||
batch_size = 25
|
||||
img_width, img_height, img_num_channels = 25, 25, 3
|
||||
loss_function = sparse_categorical_crossentropy
|
||||
no_classes = 7
|
||||
no_epochs = 40
|
||||
optimizer = Adam()
|
||||
verbosity = 1
|
||||
|
||||
# Determine shape of the data
|
||||
input_shape = (img_width, img_height, img_num_channels)
|
||||
|
||||
# Create a generator
|
||||
train_datagen = ImageDataGenerator(
|
||||
rescale=1./255
|
||||
)
|
||||
train_datagen = train_datagen.flow_from_directory(
|
||||
training_set_folder,
|
||||
save_to_dir=os.path.join(RESOURCE_DIR, "adapted-images"),
|
||||
save_format='jpeg',
|
||||
batch_size=batch_size,
|
||||
target_size=(25, 25),
|
||||
class_mode='sparse')
|
||||
|
||||
# Create the model
|
||||
model = Sequential()
|
||||
model.add(Conv2D(16, kernel_size=(5, 5), activation='relu', input_shape=input_shape))
|
||||
model.add(Conv2D(32, kernel_size=(5, 5), activation='relu'))
|
||||
model.add(Conv2D(64, kernel_size=(5, 5), activation='relu'))
|
||||
model.add(Conv2D(128, kernel_size=(5, 5), activation='relu'))
|
||||
model.add(Flatten())
|
||||
model.add(Dense(16, activation='relu'))
|
||||
model.add(Dense(no_classes, activation='softmax'))
|
||||
|
||||
# Display a model summary
|
||||
model.summary()
|
||||
|
||||
# Compile the model
|
||||
model.compile(loss=loss_function,
|
||||
optimizer=optimizer,
|
||||
metrics=['accuracy'])
|
||||
|
||||
# Start training
|
||||
model.fit(
|
||||
train_datagen,
|
||||
epochs=no_epochs,
|
||||
shuffle=False)
|
||||
|
||||
# Saving model
|
||||
filepath = os.path.join(RESOURCE_DIR, 'saved_model')
|
||||
save_model(model, filepath)
|
||||
|
@ -59,10 +59,10 @@ class Sand(Soil):
|
||||
self._value = VALUE_OF_SAND
|
||||
|
||||
def transform(self) -> list:
|
||||
if not self.is_hydrated :
|
||||
return [0, 1, 0, 0]
|
||||
else:
|
||||
if not self.is_sowed :
|
||||
return [0, 0, 1, 0]
|
||||
else:
|
||||
return [0, 1, 0, 0]
|
||||
|
||||
|
||||
class Grass(Plant):
|
||||
|
@ -1,30 +0,0 @@
|
||||
from tensorflow.keras.models import load_model
|
||||
from tensorflow import keras as k
|
||||
import numpy as np
|
||||
from config import RESOURCE_DIR
|
||||
import os
|
||||
|
||||
# Load the model
|
||||
model = load_model(
|
||||
os.path.join(RESOURCE_DIR, "saved_model"),
|
||||
custom_objects=None,
|
||||
compile=True
|
||||
)
|
||||
|
||||
# Data fror predictions
|
||||
img_width, img_height, img_num_channels = 25, 25, 3
|
||||
labels = ["cabbage", "carrot", "corn", "lettuce", "paprika", "potato", "tomato"]
|
||||
|
||||
# Predictions
|
||||
filename = 'Image_1.jpg'
|
||||
loaded_image = k.preprocessing.image.load_img(path=RESOURCE_DIR + '/smaller_test/potato/' + filename,
|
||||
target_size=(img_width, img_height, img_num_channels))
|
||||
# convert to array and resample dividing by 255
|
||||
img_array = k.preprocessing.image.img_to_array(loaded_image) / 255.
|
||||
|
||||
# add sample dimension. the predictor is expecting (1, CHANNELS, IMG_WIDTH, IMG_HEIGHT)
|
||||
img_np_array = np.expand_dims(img_array, axis=0)
|
||||
|
||||
predictions = model.predict(img_np_array)
|
||||
prediction = np.argmax(predictions[0])
|
||||
print(f'Ground truth: {filename} - Prediction: {labels[prediction]}')
|
@ -31,7 +31,7 @@ class Tractor(BaseField):
|
||||
self.__board = board
|
||||
self.__harvested_corps = []
|
||||
self.__fuel = 10
|
||||
self.__neural_network = None
|
||||
self.__neural_network = NeuralNetwork()
|
||||
self.__tree = DecisionTree()
|
||||
self.__weather = Weather()
|
||||
|
||||
@ -105,7 +105,10 @@ class Tractor(BaseField):
|
||||
field.is_sowed = True
|
||||
|
||||
def harvest(self) -> None:
|
||||
if self.check_field(Crops):
|
||||
field = self.get_field_from_board()
|
||||
prediction = self.__neural_network.check(field)
|
||||
|
||||
if prediction.capitalize() in CROPS:
|
||||
print('Harvest')
|
||||
field = self.get_field_from_board()
|
||||
self.harvest_crops(field)
|
||||
@ -194,26 +197,47 @@ class Tractor(BaseField):
|
||||
print(f"Length of Moves {len(moves)}") # - {3 ** len(moves)}")
|
||||
while len(moves) > 0:
|
||||
movement, action = moves.pop(0)
|
||||
|
||||
# do action
|
||||
time.sleep(0.5)
|
||||
print(f"Action {action}")
|
||||
if action == A_FERTILIZE:
|
||||
self.fertilize()
|
||||
elif action == A_SOW:
|
||||
self.sow()
|
||||
self.hydrate()
|
||||
elif action == A_HYDRATE:
|
||||
self.hydrate()
|
||||
elif action == A_HARVEST:
|
||||
self.harvest()
|
||||
time.sleep(1)
|
||||
self.do_action_on_fields(action)
|
||||
|
||||
# move
|
||||
time.sleep(1)
|
||||
self.move_or_rotate(movement)
|
||||
|
||||
time.sleep(TIME_OF_MOVING)
|
||||
is_running.clear()
|
||||
|
||||
def do_action_on_fields(self, action: str) -> None:
|
||||
print(f"Action {action}")
|
||||
if action == A_FERTILIZE:
|
||||
self.fertilize()
|
||||
elif action == A_SOW:
|
||||
self.sow()
|
||||
# self.hydrate()
|
||||
elif action == A_HYDRATE:
|
||||
self.hydrate()
|
||||
elif action == A_HARVEST:
|
||||
self.harvest()
|
||||
|
||||
def run_auto_bot_handler(self, action: str, moves: list[tuple[str, str]], is_running: threading.Event) -> None:
|
||||
thread = threading.Thread(target=self.run_auto_bot, args=(action, moves, is_running), daemon=True)
|
||||
thread.start()
|
||||
|
||||
def run_auto_bot(self, action_type: str, moves: list[tuple[str, str]], is_running: threading.Event) -> None:
|
||||
print(moves)
|
||||
# print('Auto mode')
|
||||
while len(moves) > 0:
|
||||
movement, action = moves.pop(0)
|
||||
# move
|
||||
self.move_or_rotate(movement)
|
||||
time.sleep(0.7)
|
||||
|
||||
self.do_action_on_fields(action_type)
|
||||
time.sleep(1)
|
||||
is_running.clear()
|
||||
|
||||
def move_or_rotate(self, movement: str) -> None:
|
||||
print(f"Move {movement}")
|
||||
if movement == M_GO_FORWARD:
|
||||
@ -309,16 +333,27 @@ class Tractor(BaseField):
|
||||
|
||||
is_running.clear()
|
||||
|
||||
def choose_action(self) -> tuple[tuple[int,int],str]:
|
||||
def choose_action(self) -> tuple[tuple[int, int], str]:
|
||||
vectors = self.__board.convert_fields_to_vectors()
|
||||
print(vectors)
|
||||
coords = None
|
||||
action = None
|
||||
for i in range(HORIZONTAL_NUM_OF_FIELDS):
|
||||
for j in range(VERTICAL_NUM_OF_FIELDS):
|
||||
|
||||
i_max = HORIZONTAL_NUM_OF_FIELDS
|
||||
j_max = VERTICAL_NUM_OF_FIELDS
|
||||
|
||||
i_min = random.randint(0, HORIZONTAL_NUM_OF_FIELDS - 1)
|
||||
j_min = random.randint(0, VERTICAL_NUM_OF_FIELDS - 1)
|
||||
|
||||
i = i_min
|
||||
flag = True
|
||||
while i < i_max and flag:
|
||||
for j in range(j_min, j_max):
|
||||
action = self.__tree.make_decision(self.__weather, vectors[i][j])
|
||||
if action != A_DO_NOTHING:
|
||||
coords = (i, j)
|
||||
flag = False
|
||||
break
|
||||
i += 1
|
||||
print(coords, action)
|
||||
return coords, action
|
||||
|
11
config.py
11
config.py
@ -14,7 +14,7 @@ __all__ = (
|
||||
'A_SOW', 'A_HARVEST', 'A_HYDRATE', 'A_FERTILIZE', 'A_DO_NOTHING',
|
||||
'TYPES_OF_ACTION', 'D_NORTH', 'D_EAST', 'D_SOUTH', 'D_WEST',
|
||||
'VALUE_OF_CROPS', 'VALUE_OF_PLANT', 'VALUE_OF_SAND', 'VALUE_OF_CLAY',
|
||||
'MAP_FILE_NAME', 'JSON', 'SAVE_MAP', 'LOAD_MAP',
|
||||
'MAP_FILE_NAME', 'JSON', 'SAVE_MAP', 'LOAD_MAP', 'AUTO_MODE',
|
||||
'TRAINING_SET_DIR', 'TEST_SET_DIR', 'ADAPTED_IMG_DIR', 'MODEL_DIR',
|
||||
'DATA_DIR','IMG_DECISION_TREE','MODEL_TREE_FILENAME','DATA_TRAINING_FOR_DECISION_TREE'
|
||||
)
|
||||
@ -86,10 +86,10 @@ A_DO_NOTHING = "do nothing"
|
||||
TYPES_OF_ACTION = [A_SOW, A_HARVEST, A_HYDRATE, A_FERTILIZE, A_DO_NOTHING]
|
||||
|
||||
# Costs fields:
|
||||
VALUE_OF_CROPS = 0.25
|
||||
VALUE_OF_PLANT = 0.5
|
||||
VALUE_OF_SAND = 0.75
|
||||
VALUE_OF_CLAY = 1
|
||||
VALUE_OF_CROPS = 0.2
|
||||
VALUE_OF_PLANT = 0.3
|
||||
VALUE_OF_SAND = 0.4
|
||||
VALUE_OF_CLAY = 0.5
|
||||
|
||||
# Weather
|
||||
W_SUNNY = 'Sunny'
|
||||
@ -113,3 +113,4 @@ TIME_OF_MOVING = 2
|
||||
# Args
|
||||
SAVE_MAP = '--save-map'
|
||||
LOAD_MAP = '--load-map'
|
||||
AUTO_MODE = '--auto-mode'
|
||||
|
Loading…
Reference in New Issue
Block a user