From 8851493d9de5b629aa5d3983f4f927523402a29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Cupa=C5=82?= Date: Wed, 23 Jun 2021 14:26:59 +0200 Subject: [PATCH] add auto mode moving - integrate astar, neural network and decision tree --- Readme.md | 19 +++++++++-- app/__init__.py | 59 ++++++++++++++++++++++++++------- app/a_star.py | 2 +- app/cmd_parser.py | 4 ++- app/createModel.py | 68 --------------------------------------- app/fields.py | 6 ++-- app/loadFromSavedModel.py | 30 ----------------- app/tractor.py | 67 +++++++++++++++++++++++++++++--------- config.py | 11 ++++--- 9 files changed, 128 insertions(+), 138 deletions(-) delete mode 100644 app/createModel.py delete mode 100644 app/loadFromSavedModel.py diff --git a/Readme.md b/Readme.md index 50caf1c..b1611e1 100644 --- a/Readme.md +++ b/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. \ No newline at end of file +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 +``` \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 0841a2b..7b497d8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -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() diff --git a/app/a_star.py b/app/a_star.py index e881d25..f3f4141 100644 --- a/app/a_star.py +++ b/app/a_star.py @@ -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)) diff --git a/app/cmd_parser.py b/app/cmd_parser.py index 5620a4a..7311740 100644 --- a/app/cmd_parser.py +++ b/app/cmd_parser.py @@ -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: diff --git a/app/createModel.py b/app/createModel.py deleted file mode 100644 index a141a1c..0000000 --- a/app/createModel.py +++ /dev/null @@ -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) - diff --git a/app/fields.py b/app/fields.py index 89d42b0..8041a5c 100644 --- a/app/fields.py +++ b/app/fields.py @@ -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): diff --git a/app/loadFromSavedModel.py b/app/loadFromSavedModel.py deleted file mode 100644 index 976d5ae..0000000 --- a/app/loadFromSavedModel.py +++ /dev/null @@ -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]}') diff --git a/app/tractor.py b/app/tractor.py index f725b65..9892d9b 100644 --- a/app/tractor.py +++ b/app/tractor.py @@ -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 diff --git a/config.py b/config.py index 22bdcfd..a6e40f5 100644 --- a/config.py +++ b/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'