diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..486bd769d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*iso filter=lfs diff=lfs merge=lfs -text +*pth filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4b6f32db7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +.ruff_cache/ + +Paper/*.aux +Paper/*.log +Paper/*.bbl +Paper/*.blg +Paper/*.pdf + +q.log diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..902b2c90c --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 \ No newline at end of file diff --git a/CombinedMapGenerator/generator.py b/CombinedMapGenerator/generator.py index 39fe2e3ff..c4f32fd9b 100644 --- a/CombinedMapGenerator/generator.py +++ b/CombinedMapGenerator/generator.py @@ -1,18 +1,7 @@ """Generator used to make map pairs""" -import random -import numpy as np -import matplotlib.pyplot as plt -from Terrain.map import Map -import asyncio -class Generator(): + +class Generator: def __init__(self, width, height, octaves=1): self.width = width self.height = height self.octaves = octaves - - def generate_solveable_pair(self ): - """Generates a map pair that is solveable""" - real = Map.solveable_map(self.width, self.height, self.octaves) - predicted = real + Map.built(self.width, self.height, self.octaves) - - diff --git a/CompositeEnvironment.py b/CompositeEnvironment.py index 45beed8b1..c61a7ae75 100644 --- a/CompositeEnvironment.py +++ b/CompositeEnvironment.py @@ -4,10 +4,12 @@ from Terrain import generator import matplotlib.pyplot as plt -from ML.dnoise import EncoderDecoder as ed +from DNoise.dnoise import EncoderDecoder as ed import torch from torch import nn +Image = np.ndarray + class MAELoss(nn.Module): def __init__(self): @@ -22,20 +24,26 @@ def forward(self, y_pred, y_true): class Environment: - def __init__(self, image, noisy, radius, center=None): + def __init__( + self, image: Image, noisy: Image, radius: int, center: None | tuple + ) -> None: self.image = image.copy() self.radius = radius self.noisy_image = noisy.copy() self.center = center - def generate(self): - masked = get_visible_image(self.image, self.radius, self.noisy_image, self.center) + def generate(self) -> Image: + masked = get_visible_image( + self.image, self.radius, self.noisy_image, self.center + ) return masked -def create_circular_mask(h, w, radius, center=None): +def create_circular_mask( + h: int, w: int, radius: int, center: None | tuple = None +) -> np.ndarray: if center is None: # use the middle of the image - center = (int(w / 2), int(h / 2)) + center = (w / 2, h / 2) y, x = np.ogrid[:h, :w] dist_from_center = np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) @@ -44,7 +52,9 @@ def create_circular_mask(h, w, radius, center=None): return mask -def get_visible_image(image, radius, noisy, center): +def get_visible_image( + image: Image, radius: int, noisy: Image, center: None | tuple +) -> Image: # Find the size of the image image = np.abs(image) @@ -64,12 +74,10 @@ def get_visible_image(image, radius, noisy, center): class Visualizer: - def __init__(self, model_path, original): - self.model_path = model_path self.original = original.copy() - self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") def dNoiseVis(self, inputpic): print("Loaded Model") @@ -94,17 +102,22 @@ def dNoiseVis(self, inputpic): de_noised_image = de_noised_image.cpu().numpy() print("Processed Image") fig, ax = plt.subplots(2, 2) - ax[0][0].imshow(de_noised_image, cmap='plasma_r') - ax[0][0].set_title('De-Noised Image') - ax[0][1].imshow(inputpic, cmap='plasma_r') - ax[0][1].set_title('Input') - ax[1][0].imshow(self.original, cmap='plasma_r') - ax[1][0].set_title('Clean Image') + ax[0][0].imshow(de_noised_image, cmap="plasma_r") + ax[0][0].set_title("De-Noised Image") + ax[0][1].imshow(inputpic, cmap="plasma_r") + ax[0][1].set_title("Noisy Image") + ax[1][0].imshow(self.original, cmap="plasma_r") + ax[1][0].set_title("Ground Image") ax[1][1].hist(de_noised_image, bins=25) - ax[1][1].set_title('De-Noised Image Histogram') - - fig.suptitle("Image Size: 256 x 256\nNoise Level: {}%\nAccuracy: {:.2f}%".format(noise_level, loss), - fontsize=16, y=0.9) + ax[1][1].set_title("De-Noised Image Histogram") + + fig.suptitle( + "Image Size: 256 x 256\nNoise Level: {}%\nAccuracy: {:.2f}%".format( + noise_level, loss + ), + fontsize=16, + y=0.9, + ) plt.show() def dNoise(self, image): @@ -141,16 +154,15 @@ def thresholdDNoise(input, x): seed = random.randint(1, 100000000000) x = random.randint(50, 200) y = random.randint(50, 200) - noise_level = 30 + noise_level = 80 print("({}, {})".format(x, y)) pic = np.array(generator.generateClean(256, 256, 5, seed, True)) noisy_pic = np.array(generator.generateNoise(256, 256, 5, noise_level, seed, True)) pic, noisy_pic = np.abs(pic), np.abs(noisy_pic) ev = Environment(pic, noisy_pic, 50, center=(x, y)) - masked = ev.generate() - vi = Visualizer('./ML/models/synthnav-model-0.pth', pic) + vi = Visualizer("./DNoise/models/synthnav-model-0.pth", pic) vi.dNoiseVis(masked) diff --git a/ML/array2img.py b/DNoise/array2img.py similarity index 93% rename from ML/array2img.py rename to DNoise/array2img.py index c6ebe6d8a..8b3d5e265 100644 --- a/ML/array2img.py +++ b/DNoise/array2img.py @@ -9,7 +9,6 @@ import math from Terrain import timers import os -import cv2 def array2image(x, y, octaves, weight, seed: int = 0, iD: int = 0): @@ -22,7 +21,7 @@ def array2image(x, y, octaves, weight, seed: int = 0, iD: int = 0): print(img) timer.stop() timer = timers.FunctionTimer("Clean - Image Save") - img.save('./val_images/clean/' + str(iD) + '_clean.jpeg', bits=1, optimize=True) + img.save("./val_images/clean/" + str(iD) + "_clean.jpeg", bits=1, optimize=True) timer.stop() timer = timers.FunctionTimer("Noisy - Generate") array = generator.generateNoise(x, y, octaves, weight, seed) @@ -32,7 +31,7 @@ def array2image(x, y, octaves, weight, seed: int = 0, iD: int = 0): img = PIL.Image.fromarray(bool_array) timer.stop() timer = timers.FunctionTimer("Noisy - Image Save") - img.save('./val_images/noisy/' + str(iD) + '_noisy.jpeg', bits=1, optimize=True) + img.save("./val_images/noisy/" + str(iD) + "_noisy.jpeg", bits=1, optimize=True) timer.stop() @@ -53,7 +52,7 @@ def convert_to_1bit(input_dir, output_dir): print("Uncertain Image") bool_array = np.array(img_array, dtype=bool) img = PIL.Image.fromarray(bool_array) - img.save('./train_images/noisy/' + file, bits=1, optimize=True) + img.save("./train_images/noisy/" + file, bits=1, optimize=True) threshold = 128 diff --git a/ML/dnoise.py b/DNoise/dnoise.py similarity index 80% rename from ML/dnoise.py rename to DNoise/dnoise.py index c825b9745..d0b41f54a 100644 --- a/ML/dnoise.py +++ b/DNoise/dnoise.py @@ -1,5 +1,4 @@ import os.path -from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -7,12 +6,10 @@ import torch.utils.data import torchsummary -device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print("Device: {}\n".format(device)) -# 2. Define the CNN model -# Define the encoder-decoder model class EncoderDecoder(nn.Module): def __init__(self): super(EncoderDecoder, self).__init__() @@ -24,7 +21,7 @@ def __init__(self): nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding=1), - nn.ReLU() + nn.ReLU(), ) self.decoder = nn.Sequential( @@ -32,7 +29,7 @@ def __init__(self): nn.ReLU(), nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1), nn.ReLU(), - nn.Conv2d(32, 1, 3, padding=1) + nn.Conv2d(32, 1, 3, padding=1), ) def forward(self, x): @@ -52,18 +49,18 @@ def forward(self, y_pred, y_true): def saveModel(model, model_name): - torch.save(model.state_dict(), './models/{}'.format(model_name)) - model_scripted = torch.jit.script(model) # Export to TorchScript - model_scripted.save('./models/synthnav-model-script.pt') + torch.save(model.state_dict(), "./models/{}".format(model_name)) + model_scripted = torch.jit.script(model) # Export to TorchScript + model_scripted.save("./models/synthnav-model-script.pt") def main(): loss_array = list() - # print(summary(model, (1, 1, 256, 256))) - if os.path.exists('./models/{}'.format(model_name)): + # Only load the model if it already exists + if os.path.exists("./models/{}".format(model_name)): print("Loaded Model") - model.load_state_dict(torch.load(f='./models/{}'.format(model_name))) + model.load_state_dict(torch.load(f="./models/{}".format(model_name))) # Create an instance of the custom MSE loss function criterion = MAELoss() @@ -76,14 +73,16 @@ def create_synced_dictionary(folder_name): for sub_folder in folder[1]: for file in os.listdir(os.path.join(folder_name, sub_folder)): try: - name, image_type = file.split('_') + name, image_type = file.split("_") + except ValueError: + continue + + - except: - return - if image_type == 'clean.jpeg': + if image_type == "clean.jpeg": # Add the clean image to the dictionary sync_dir_inner[name] = file - elif image_type == 'noisy.jpeg': + elif image_type == "noisy.jpeg": # Check if there is already a clean image for this pair in the dictionary if name in sync_dir_inner: # Add the noisy image to the dictionary @@ -96,10 +95,10 @@ def create_synced_dictionary(folder_name): dirname = os.path.dirname(__file__) - high_quality = os.path.join(dirname, 'train_images') - sync_dir = create_synced_dictionary('train_images') + high_quality = os.path.join(dirname, "train_images") + sync_dir = create_synced_dictionary("train_images") - val_dir = os.path.join(dirname, 'val_images') + val_dir = os.path.join(dirname, "val_images") val_sync = create_synced_dictionary(val_dir) # Training loop @@ -136,8 +135,15 @@ def create_synced_dictionary(folder_name): loss_array.append(loss.item()) if (i + 1) % batch_size == 0: - print("Epoch: {}/{}, Batch: {}/{}, Loss: {:.4f}%".format(epoch + 1, num_epochs, i + 1, batch_size, - (1 - loss.item()) * 100)) + print( + "Epoch: {}/{}, Batch: {}/{}, Loss: {:.4f}%".format( + epoch + 1, + num_epochs, + i + 1, + batch_size, + (1 - loss.item()) * 100, + ) + ) i += 1 if (epoch + 1) % 20 == 0: @@ -192,22 +198,22 @@ def visualize_predictions(model, val_sync, val_dir): noisy_image = noisy_image.cpu() noisy_image = noisy_image.detach().numpy() - cmap = 'plasma_r' + cmap = "plasma_r" # Visualize the clean, noisy, and de-noised images fig, axs = plt.subplots(2, 3) axs[0, 0].imshow(clean_image, cmap=cmap) - axs[0, 0].set_title('Input (Clean)') + axs[0, 0].set_title("Input (Clean)") axs[1, 0].imshow(clean_deionised, cmap=cmap) - axs[1, 0].set_title('Output (Clean)') + axs[1, 0].set_title("Output (Clean)") axs[0, 1].imshow(noisy_image, cmap=cmap) - axs[0, 1].set_title('Input (Noisy)') + axs[0, 1].set_title("Input (Noisy)") axs[1, 1].imshow(de_noised_image, cmap=cmap) - axs[1, 1].set_title('Output (Noisy)') + axs[1, 1].set_title("Output (Noisy)") axs[0, 2].imshow(clean_image - noisy_image, cmap=cmap) - axs[0, 2].set_title('Input Difference') + axs[0, 2].set_title("Input Difference") axs[1, 2].imshow(clean_deionised - de_noised_image, cmap=cmap) - axs[1, 2].set_title('Output Difference') + axs[1, 2].set_title("Output Difference") plt.show() vis_iter = 1 @@ -242,11 +248,11 @@ def loss_graph(loss_array): # Iterate directory dirname = os.path.dirname(__file__) - high_quality = os.path.join(dirname, 'train_images/noisy') - model_name = 'synthnav-model-0.pth' + high_quality = os.path.join(dirname, "train_images/noisy") + model_name = "synthnav-model-0.pth" for path in os.listdir(high_quality): # check if current path is a file - if os.path.isfile(os.path.join('train_images/noisy', path)): + if os.path.isfile(os.path.join("train_images/noisy", path)): count += 1 batch_size = 128 diff --git a/ML/helper_functions.py b/DNoise/helper_functions.py similarity index 94% rename from ML/helper_functions.py rename to DNoise/helper_functions.py index 88f65521d..7c434de02 100644 --- a/ML/helper_functions.py +++ b/DNoise/helper_functions.py @@ -7,19 +7,18 @@ import matplotlib.pyplot as plt import numpy as np -from torch import nn +# Pred and plot image function from notebook 04 +# See creation: https://www.learnpytorch.io/04_pytorch_custom_datasets/#113-putting-custom-image-prediction-together-building-a-function +from typing import List +import torchvision + +import requests import os import zipfile from pathlib import Path -import requests - -# Walk through an image classification directory and find out how many files (images) -# are in each subdirectory. -import os - def walk_through_dir(dir_path): """ Walks through dir_path returning its contents. @@ -33,7 +32,10 @@ def walk_through_dir(dir_path): name of each subdirectory """ for dirpath, dirnames, filenames in os.walk(dir_path): - print(f"There are {len(dirnames)} directories and {len(filenames)} images in '{dirpath}'.") + print( + f"There are {len(dirnames)} directories and {len(filenames)} images in '{dirpath}'." + ) + def plot_decision_boundary(model: torch.nn.Module, X: torch.Tensor, y: torch.Tensor): """Plots decision boundaries of model predicting on X in comparison to y. @@ -76,8 +78,8 @@ def plot_predictions( train_data, train_labels, test_data, test_labels, predictions=None ): """ - Plots linear training data and test data and compares predictions. - """ + Plots linear training data and test data and compares predictions. + """ plt.figure(figsize=(10, 7)) # Plot training data in blue @@ -114,7 +116,7 @@ def print_train_time(start, end, device=None): """Prints difference between start and end time. Args: - start (float): Start time of computation (preferred in timeit format). + start (float): Start time of computation (preferred in timeit format). end (float): End time of computation. device ([type], optional): Device that compute is running on. Defaults to None. @@ -164,12 +166,6 @@ def plot_loss_curves(results): plt.legend() -# Pred and plot image function from notebook 04 -# See creation: https://www.learnpytorch.io/04_pytorch_custom_datasets/#113-putting-custom-image-prediction-together-building-a-function -from typing import List -import torchvision - - def pred_and_plot_image( model: torch.nn.Module, image_path: str, @@ -185,7 +181,7 @@ def pred_and_plot_image( class_names (List[str], optional): different class names for target image. Defaults to None. transform (_type_, optional): transform of target image. Defaults to None. device (torch.device, optional): target device to compute on. Defaults to "cuda" if torch.cuda.is_available() else "cpu". - + Returns: Matplotlib plot of target image and model prediction as title. @@ -236,7 +232,8 @@ def pred_and_plot_image( plt.title(title) plt.axis(False) -def set_seeds(seed: int=42): + +def set_seeds(seed: int = 42): """Sets random sets for torch operations. Args: @@ -247,19 +244,18 @@ def set_seeds(seed: int=42): # Set the seed for CUDA torch operations (ones that happen on the GPU) torch.cuda.manual_seed(seed) -def download_data(source: str, - destination: str, - remove_source: bool = True) -> Path: + +def download_data(source: str, destination: str, remove_source: bool = True) -> Path: """Downloads a zipped dataset from source and unzips to destination. Args: source (str): A link to a zipped file containing data. destination (str): A target directory to unzip data to. remove_source (bool): Whether to remove the source after downloading and extracting. - + Returns: pathlib.Path to downloaded data. - + Example usage: download_data(source="https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip", destination="pizza_steak_sushi") @@ -268,13 +264,13 @@ def download_data(source: str, data_path = Path("data/") image_path = data_path / destination - # If the image folder doesn't exist, download it and prepare it... + # If the image folder doesn't exist, download it and prepare it... if image_path.is_dir(): print(f"[INFO] {image_path} directory exists, skipping download.") else: print(f"[INFO] Did not find {image_path} directory, creating one...") image_path.mkdir(parents=True, exist_ok=True) - + # Download pizza, steak, sushi data target_file = Path(source).name with open(data_path / target_file, "wb") as f: @@ -284,11 +280,11 @@ def download_data(source: str, # Unzip pizza, steak, sushi data with zipfile.ZipFile(data_path / target_file, "r") as zip_ref: - print(f"[INFO] Unzipping {target_file} data...") + print(f"[INFO] Unzipping {target_file} data...") zip_ref.extractall(image_path) # Remove .zip file if remove_source: os.remove(data_path / target_file) - + return image_path diff --git a/DNoise/models/synthnav-model-0.pth b/DNoise/models/synthnav-model-0.pth new file mode 100644 index 000000000..d06240f56 --- /dev/null +++ b/DNoise/models/synthnav-model-0.pth @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cdf5c844cb63e190a2c8561b612144b827c06671d427d341d4a6fd193e5315d +size 802559 diff --git a/ML/pathgui.py b/DNoise/pathgui.py similarity index 91% rename from ML/pathgui.py rename to DNoise/pathgui.py index e4658bca8..6a217601e 100644 --- a/ML/pathgui.py +++ b/DNoise/pathgui.py @@ -1,8 +1,9 @@ -from random import random, randint +from random import random import numpy as np from PyQt5 import QtCore, QtGui, QtWidgets + class AStarAnimator(QtWidgets.QWidget): def __init__(self, terrains): super().__init__() @@ -43,7 +44,13 @@ def update_terrain(self): # Convert the terrain to a QImage and set it on the QLabel img = np.uint8(terrain * 255) - qimage = QtGui.QImage(img, img.shape[1], img.shape[0], img.strides[0], QtGui.QImage.Format_Grayscale8) + qimage = QtGui.QImage( + img, + img.shape[1], + img.shape[0], + img.strides[0], + QtGui.QImage.Format_Grayscale8, + ) pixmap = QtGui.QPixmap.fromImage(qimage) self.terrain_label.setPixmap(pixmap) self.terrain_label.setFixedSize(pixmap.size()) @@ -66,13 +73,14 @@ def update_terrain(self): def mousePressEvent(self, event): x = event.x() y = event.y() - self.path.append((x,y)) + self.path.append((x, y)) self.update_terrain() def save_path(self): print("Saved") -if __name__ == '__main__': + +if __name__ == "__main__": import sys # Create a random terrain @@ -81,9 +89,8 @@ def save_path(self): for j in range(terrain.shape[1]): terrain[i, j] = random() - # Create the GUI app = QtWidgets.QApplication(sys.argv) gui = AStarAnimator([terrain]) gui.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/ML/train_images.zip b/DNoise/train_images.zip similarity index 100% rename from ML/train_images.zip rename to DNoise/train_images.zip diff --git a/ML/models/synthnav-model-0.pth b/ML/models/synthnav-model-0.pth deleted file mode 100644 index aac2519ee..000000000 Binary files a/ML/models/synthnav-model-0.pth and /dev/null differ diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..2aa140d1b --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +LATEX = pdflatex +LATEX_FLAGS = + +BIBTEX = bibtex +BIBTEX_FLAGS = + +MAIN = main + +.PHONY: all clean + +all: synthnav.pdf + +synthnav.pdf: Paper/$(MAIN).tex + cd Paper; $(LATEX) $(LATEX_FLAGS) $(MAIN); \ + $(BIBTEX) $(BIBTEX_FLAGS) $(MAIN); \ + $(LATEX) $(LATEX_FLAGS) $(MAIN); \ + $(LATEX) $(LATEX_FLAGS) $(MAIN); + + +clean: + rm -f Paper/*.aux Paper/*.log Paper/*.pdf Paper/*.bbl Paper/*.blg q.log diff --git a/Paper/main.tex b/Paper/main.tex new file mode 100644 index 000000000..e0da95c8d --- /dev/null +++ b/Paper/main.tex @@ -0,0 +1,72 @@ +\documentclass{article} + +% Packages +\usepackage{natbib} + +\title{Synthetic Navigation: Machine Learning Autonomous Navigation Using Predicted and Ground-Truthed Maps} + +\author{ + David Rubin \\ + Horizons CIS \\ + % \texttt{daviru007@example.com} + \and + Marcus E. Kauffman \\ + De La Salle High School \\ + % \texttt{marcus email} +} + +\date{\today} + +\begin{document} + +\maketitle + +\begin{abstract} + TODO: abstract +\end{abstract} + +\section{Rationale} + Navigation over uncharted terrain has always required precise instruments +and careful data collection. Thanks to modern technology, such as satellite +systems and GPS, no area is left completely undocumented \citep{deschamps-berger2020, kervyn2007, li1988}. +Those who want to autonomously explore areas are left with two options: +they can use an open-loop navigation system, relying solely on potentially +inaccurate collected data, or they can use a closed-loop system which uses +only sensor (ground-truthed) input, disregarding any preexisting data. +This preexisting data, while it cannot be relied on, can be useful for +navigation. + + A pathfinding situation consists of a “predicted” map, which is available +immediately and a “ground-truthed”, “observed” or “actual” map which is +revealed to the algorithm or model as it explores the map. These two maps +have a variation between them, the degree of which is referred to as the +“edit distance” and expressed as a percentage, where 0\% is zero difference +between the maps and 100\% means that the second map was generated separately +from the first map. The combination of both maps is referred to as the +“environment,” the shortest possible path between two points, the “start” +and the “goal,” is the “best path” or “shortest path,” and the path that +the algorithm takes is the “path” or “chosen path.” Due to the iterative +nature of the D* algorithm and the machine learning model, the chosen path +will most often not be the best path. + + The most popular open-loop pathfinding algorithm is A* (pronounced “a-star”) +uses node based weighted pathfinding and prioritizes pathfinding in directions +that appear to be better. D* is an adaptation of A* for dynamic environments +using an “incremental search strategy” when the final environment cannot be +determined (X. Sun et al., 2010). + + Many different techniques for machine learning +(ML) exist (Amit Patel, 2022). For this application, reinforcement learning +is best as it has shown success with pathfinding (Roy et al., 2017) and is +able to make a choice between exploration and using past data which is necessary +for using a combination of both maps. + + This research will describe the +feasibility and limits of the use of a ML model for navigation of real +autonomous vehicles compared to traditional (A* and D*) pathfinding algorithms. + +% Bibliography +\bibliographystyle{plainnat} +\bibliography{references} + +\end{document} diff --git a/Paper/references.bib b/Paper/references.bib new file mode 100644 index 000000000..297ddfc21 --- /dev/null +++ b/Paper/references.bib @@ -0,0 +1,61 @@ +// Amit Patel. (2022, October 15). AI techniques. Amit’s Thoughts on Pathfinding. https://theory.stanford.edu/~amitp/GameProgramming/AITechniques.html +@misc{patel2022, + author = {Patel, A.}, + year = {2022}, + title = {AI techniques}, + howpublished = {Amit’s Thoughts on Pathfinding}, + url = {https://theory.stanford.edu/~amitp/GameProgramming/AITechniques.html} +} + +// Deschamps-Berger, C., Gascoin, S., Berthier, E., Deems, J., Gutmann, E., Dehecq, A., Shean, D., & Dumont, M. (2020). Snow depth mapping from stereo satellite imagery in mountainous terrain: Evaluation using airborne laser-scanning data. The Cryosphere, 14(9), 2925–2940. https://doi.org/10.5194/tc-14-2925-2020 +@article{deschamps-berger2020, + author = {Deschamps-Berger, C. and Gascoin, S. and Berthier, E. and Deems, J. and Gutmann, E. and Dehecq, A. and Shean, D. and Dumont, M.}, + year = {2020}, + title = {Snow depth mapping from stereo satellite imagery in mountainous terrain: Evaluation using airborne laser-scanning data}, + journal = {The Cryosphere}, + volume = {14}, + number = {9}, + pages = {2925--2940}, + doi = {10.5194/tc-14-2925-2020} +} + +// Kervyn, M., Kervyn, F., Goossens, R., Rowland, S. K., & Ernst, G. G. J. (2007). Mapping volcanic terrain using high-resolution and 3D satellite remote sensing. Geological Society, London, Special Publications, 283(1), 5–30. https://doi.org/10.1144/SP283.2 +@article{kervyn2007, + author = {Kervyn, M. and Kervyn, F. and Goossens, R. and Rowland, S. K. and Ernst, G. G. J.}, + year = {2007}, + title = {Mapping volcanic terrain using high-resolution and 3D satellite remote sensing}, + journal = {Geological Society, London, Special Publications}, + volume = {283}, + number = {1}, + pages = {5--30}, + doi = {10.1144/SP283.2} +} + +// Li, R. (1988). Potential of High-Resolution Satellite Imagery for National Mapping Products. Photogrammetric Engineering & Remote Sensin, 64, 6. +@article{li1988, + author = {Li, R.}, + year = {1988}, + title = {Potential of High-Resolution Satellite Imagery for National Mapping Products}, + journal = {Photogrammetric Engineering \& Remote Sensing}, + volume = {64}, + number = {6} +} + +// Roy, N., Chatterjee, R., Mukherjee, A., & Bhuiya, A. (2017). Implementation of Image Processing and Reinforcement Learning in Path Planning of Mobile Robots. International Journal of Engineering Science and Computing, 7, 15211–15213. +@article{roy2017, + author = {Roy, N. and Chatterjee, R. and Mukherjee, A. and Bhuiya, A.}, + year = {2017}, + title = {Implementation of Image Processing and Reinforcement Learning in Path Planning of Mobile Robots}, + journal = {International Journal of Engineering Science and Computing}, + volume = {7}, + pages = {15211--15213} +} + +// X. Sun, W. Yeoh, & S. Koenig. (2010). Moving Target D* Lite. Proc. of 9th Int. Conf. on Autonomous Agents and Multiagent Sys- Tems (AAMAS 2010). http://idm-lab.org/bib/abstracts/papers/aamas10a.pdf +@misc{sun2010, + author = {Sun, X. and Yeoh, W. and Koenig, S.}, + year = {2010}, + title = {Moving Target D* Lite}, + howpublished = {Proc. of 9th Int. Conf. on Autonomous Agents and Multiagent Sys- Tems (AAMAS 2010)}, + url = {http://idm-lab.org/bib/abstracts/papers/aamas10a.pdf} +} \ No newline at end of file diff --git a/Pathfinding/Environment.py b/Pathfinding/Environment.py index 42407c1dc..e59dd3add 100644 --- a/Pathfinding/Environment.py +++ b/Pathfinding/Environment.py @@ -3,7 +3,7 @@ import numpy as np import enum -from typing import List, Tuple +from typing import Tuple from math import sqrt import random @@ -12,10 +12,9 @@ import sys -from Terrain.map import Map # adding Folder_2 to the system path -sys.path.insert(0, '..') +sys.path.insert(0, "..") OPEN = 0 WALL = 1 @@ -40,7 +39,10 @@ class ACTS(enum.Enum): def circleUpdate(base, add, radius, coordinates: Tuple[int, int]): for y, ny in enumerate(base): for x, nx in enumerate(base): - distance = sqrt((coordinates[0] - x) * (coordinates[0] - x) + (coordinates[1] - y) * (coordinates[1] - y)) + distance = sqrt( + (coordinates[0] - x) * (coordinates[0] - x) + + (coordinates[1] - y) * (coordinates[1] - y) + ) if distance < radius: base[y][x] = add[y][x] @@ -110,8 +112,7 @@ def update(i): env.viewing_state() - -if __name__ == '__main__': +if __name__ == "__main__": import Terrain.pathcheck as pathcheck amap = pathcheck.path(256, 256, 3, setseed=1239082) diff --git a/Pathfinding/astar.py b/Pathfinding/astar.py index cab28e3a6..8d1f57bfb 100644 --- a/Pathfinding/astar.py +++ b/Pathfinding/astar.py @@ -45,7 +45,10 @@ def a_star(self): neighbor = (x + dx, y + dy) if (0 <= neighbor[0] < 256) and (0 <= neighbor[1] < 256): - if self.terrain[neighbor[0], neighbor[1]] == 0 and neighbor not in visited: + if ( + self.terrain[neighbor[0], neighbor[1]] == 0 + and neighbor not in visited + ): distance = self.heuristic(neighbor, self.end) heapq.heappush(queue, (distance, neighbor)) self.path.append(neighbor) @@ -65,10 +68,10 @@ def path2matrix(path): return matrix def animate(self): - self.ax[1][0].imshow(self.terrain, cmap='plasma') + self.ax[1][0].imshow(self.terrain, cmap="plasma") path_matrix = self.path2matrix(path) # overlay_matrix = self.overlay_path(path_matrix, self.terrain) - self.ax[1][0].imshow(path_matrix, cmap='Reds') + self.ax[1][0].imshow(path_matrix, cmap="Reds") self.ax[0][1].imshow(masked) self.ax[1][0].set_title("Path") @@ -77,19 +80,21 @@ def animate(self): self.ax[1][1].hist(de_noised_original, bins=25) self.ax[1][1].set_title("De-Noised Image Histogram") - self.ax[0][0].imshow(de_noised_original, cmap='plasma') + self.ax[0][0].imshow(de_noised_original, cmap="plasma") self.ax[0][0].set_title("De-Noised Image") - self.fig.suptitle("A* Pathfinding Example" - "\nImage Size: 256 x 256\n" - "Noise Level: {}%\nAccuracy: {:.2f}%".format(noise_level, loss), - fontsize=16) + self.fig.suptitle( + "A* Pathfinding Example" + "\nImage Size: 256 x 256\n" + "Noise Level: {}%\nAccuracy: {:.2f}%".format(noise_level, loss), + fontsize=16, + ) self.fig.set_size_inches(18.5, 10.5) plt.show() -if __name__ == '__main__': +if __name__ == "__main__": x = random.randint(50, 200) y = random.randint(50, 200) noise_level = 30 @@ -105,7 +110,7 @@ def animate(self): masked = ev.generate() - vi = Visualizer('../ML/models/synthnav-model-0.pth', noisy_pic) + vi = Visualizer("../DNoise/models/synthnav-model-0.pth", noisy_pic) de_noised_original, loss = vi.dNoise(masked) de_noised = vi.thresholdDNoise(de_noised_original, 0.5) diff --git a/Terrain/blobcheck.py b/Terrain/blobcheck.py index d36a9d5f9..17bc49860 100644 --- a/Terrain/blobcheck.py +++ b/Terrain/blobcheck.py @@ -1,5 +1,3 @@ -import copy -from collections import deque import Terrain.generator as generator import matplotlib.pyplot as plt import numpy as np @@ -49,11 +47,18 @@ def mark_island(array, visited, i, j): neighbors = [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)] for x, y in neighbors: # If the adjacent cell is land and has not been visited yet, it's part of the same island - if x >= 0 and x < len(array) and y >= 0 and y < len(array[i]) and array[x][y] == 1 and not visited[x][y]: + if ( + x >= 0 + and x < len(array) + and y >= 0 + and y < len(array[i]) + and array[x][y] == 1 + and not visited[x][y] + ): mark_island(array, visited, x, y) -if __name__ == '__main__': +if __name__ == "__main__": size = 256 octaves = 5 weight = 30 @@ -69,6 +74,10 @@ def mark_island(array, visited, i, j): axes[1].imshow(noisepic) axes[1].set_title("Noisy") - print("Clean pic islands: {}, Noisy pic islands: {}".format(pic_islands, noisepic_islands)) + print( + "Clean pic islands: {}, Noisy pic islands: {}".format( + pic_islands, noisepic_islands + ) + ) plt.show() diff --git a/Terrain/border.py b/Terrain/border.py index c10c093ad..338249334 100644 --- a/Terrain/border.py +++ b/Terrain/border.py @@ -4,6 +4,8 @@ from Terrain.timers import FunctionTimer from typing import Union import numpy as np + + def bordercheck(pic: Union[list[list[int]], np.ndarray]): """Returns a modified array where the borders of the blobs are ``0`` and everything else is ``1``. @@ -18,12 +20,14 @@ def bordercheck(pic: Union[list[list[int]], np.ndarray]): Returns ------- borderpic : list[list[{1,0}]] - """ + """ f = FunctionTimer("Border Check") for i in range(len(pic)): pic[i] = [abs(ele) for ele in pic[i]] - borderpic = copy.deepcopy(pic) # create a deep copy so that stuff doesn't get messed up + borderpic = copy.deepcopy( + pic + ) # create a deep copy so that stuff doesn't get messed up for x in range(len(pic)): for y in range(len(pic)): diff --git a/Terrain/map.py b/Terrain/map.py index df6c5a5e9..a734c3497 100644 --- a/Terrain/map.py +++ b/Terrain/map.py @@ -9,7 +9,8 @@ class Map: """Map class""" - def __init__(self, width, height,threshold=0.5): + + def __init__(self, width, height, threshold=0.5): self.width = width self.height = height self.map = np.array([[0 for x in range(width)] for y in range(height)]) @@ -36,7 +37,12 @@ def from_file(cls, filename): @property def thresholded(self): """Get the thresholded map""" - return np.array([[1 if self.map[y][x] > self.threshold else 0 for x in range(self.width)] for y in range(self.height)]) + return np.array( + [ + [1 if self.map[y][x] > self.threshold else 0 for x in range(self.width)] + for y in range(self.height) + ] + ) @property def negative(self): @@ -66,7 +72,9 @@ def __neg__(self): def build(self, octaves): """build the map""" - self.map = terraingen.terrain(self.width, self.height, octaves, seed=random.randint(0, 1000000)) + self.map = terraingen.terrain( + self.width, self.height, octaves, seed=random.randint(0, 1000000) + ) def __mul__(self, other): """Multiply the map by a given value""" @@ -85,7 +93,6 @@ def __add__(self, other): else: raise TypeError("Map object Can only be added to another Map object") - def get(self, x, y): """Get the value at a given position""" return self.map[y][x] @@ -111,4 +118,3 @@ def border(self): border.bordercheck """ return border.bordercheck(self.thresholded) - diff --git a/Terrain/noiseadder.py b/Terrain/noiseadder.py index 7c092e020..ca60e1444 100644 --- a/Terrain/noiseadder.py +++ b/Terrain/noiseadder.py @@ -48,10 +48,10 @@ def addnoise(pic: list[list[int]], weight: int): return noisepic -if __name__ == '__main__': +if __name__ == "__main__": pic = terrain(256, 256, 4, True, 12391823) noisepic = addnoise(pic, 5) fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 6)) - axes[0].imshow(pic, cmap='Greys') - axes[1].imshow(noisepic, cmap='Greys') + axes[0].imshow(pic, cmap="Greys") + axes[1].imshow(noisepic, cmap="Greys") plt.show() diff --git a/Terrain/pathcheck.py b/Terrain/pathcheck.py index d13fed03f..e8baa07ac 100644 --- a/Terrain/pathcheck.py +++ b/Terrain/pathcheck.py @@ -56,8 +56,7 @@ def isPath(arr): for i in range(1, row): for j in range(1, col): if arr[i][j] != -1: - arr[i][j] = max(arr[i][j - 1], - arr[i - 1][j]) + arr[i][j] = max(arr[i][j - 1], arr[i - 1][j]) return arr[row - 1][col - 1] == 1 @@ -73,11 +72,16 @@ def isPath(arr): seed = random.randint(1, x * y * 1000) if failedSeeds.count(seed) == 0 and setseed == 0: solved = isPath(terraingen.terrain(x, y, octaves, seed=seed)) - sys.stdout.write("\rChecking seed: " + str(seed) + ", Number: " + str(len(failedSeeds) + 1)) + sys.stdout.write( + "\rChecking seed: " + + str(seed) + + ", Number: " + + str(len(failedSeeds) + 1) + ) sys.stdout.flush() if solved: - print(f'\nWorking Seed: ' + str(seed)) - print(f'Failed Seeds: ' + str(len(failedSeeds))) + print("\nWorking Seed: " + str(seed)) + print("Failed Seeds: " + str(len(failedSeeds))) failedSeeds.append(seed) A = terraingen.terrain(x, y, octaves, progress, seed=seed) for i in range(len(A)): diff --git a/Terrain/reverser.py b/Terrain/reverser.py index ba79a5346..dd923e9cb 100644 --- a/Terrain/reverser.py +++ b/Terrain/reverser.py @@ -11,7 +11,7 @@ def reverse(pic: list): return pic -if __name__ == '__main__': +if __name__ == "__main__": pic = terraingen.terrain(10, 10, 3, True, 12309) print(pic) pic = reverse(pic) diff --git a/Terrain/sumPerlin.py b/Terrain/sumPerlin.py index 37ee04288..9b2005d22 100644 --- a/Terrain/sumPerlin.py +++ b/Terrain/sumPerlin.py @@ -5,8 +5,14 @@ from perlin_noise import PerlinNoise -def noiseMaps(x: int, y: int, octaves1, difference: float, seed: Optional[int] = 0, - seedDifference: Optional[int] = 1): +def noiseMaps( + x: int, + y: int, + octaves1, + difference: float, + seed: Optional[int] = 0, + seedDifference: Optional[int] = 1, +): """function that generates two maps with a "difference" in between works by generating two perlin noise maps and then adding them and returning both the sum and one individual Parameters @@ -30,14 +36,25 @@ def noiseMaps(x: int, y: int, octaves1, difference: float, seed: Optional[int] = map1 = PerlinNoise(octaves1, seed) map2 = PerlinNoise(octaves1, seed + seedDifference) discrete1 = [[map1([i / x, j / y]) for j in range(y)] for i in range(x)] - discrete2 = [[(difference * map2([i / x, j / y])) + (1 - difference) * (discrete1[i][j]) for j in range(y)] for i in - range(x)] + discrete2 = [ + [ + (difference * map2([i / x, j / y])) + (1 - difference) * (discrete1[i][j]) + for j in range(y) + ] + for i in range(x) + ] return discrete1, discrete2 -def thresholdedNoiseMaps(x: int, y: int, octaves1, difference: float, seed: Optional[int] = 0, - seedDifference: Optional[int] = 1): +def thresholdedNoiseMaps( + x: int, + y: int, + octaves1, + difference: float, + seed: Optional[int] = 0, + seedDifference: Optional[int] = 1, +): d1, d2 = noiseMaps(x, y, octaves1, difference, seed, seedDifference) td1 = [[-int(np.floor(x)) for x in d1[y]] for y in range(y)] td2 = [[-int(np.floor(x)) for x in d2[y]] for y in range(y)] @@ -45,13 +62,19 @@ def thresholdedNoiseMaps(x: int, y: int, octaves1, difference: float, seed: Opti return np.array(td1), np.array(td2) -def correctNoiseMaps(x: int, y: int, octaves1, difference: float, seed: Optional[int] = 0, - seedDifference: Optional[int] = 1): +def correctNoiseMaps( + x: int, + y: int, + octaves1, + difference: float, + seed: Optional[int] = 0, + seedDifference: Optional[int] = 1, +): """same as noiseMaps only it gets threshold""" d1, d2 = noiseMaps(x, y, octaves1, difference, seed, seedDifference) td1 = [[-int(np.floor(x)) for x in d1[y]] for y in range(y)] - td2 = [[-int(np.floor(x)) for x in d2[y]] for y in range(y)] + # td2 = [[-int(np.floor(x)) for x in d2[y]] for y in range(y)] return td1 diff --git a/Terrain/terraingen.py b/Terrain/terraingen.py index 878069c63..7d3b34387 100644 --- a/Terrain/terraingen.py +++ b/Terrain/terraingen.py @@ -27,10 +27,15 @@ def terrain(x: int, y: int, octaves: int, progress: bool = False, seed: int = 0) noise = PerlinNoise(octaves=octaves, seed=seed) if progress: - picarr = [[int(np.floor(noise([i / x, j / y]))) for j in range(y)] for i in tqdm(range(x))] + picarr = [ + [int(np.floor(noise([i / x, j / y]))) for j in range(y)] + for i in tqdm(range(x)) + ] return picarr if not progress: - picarr = [[int(np.floor(noise([i / x, j / y]))) for j in range(y)] for i in range(x)] + picarr = [ + [int(np.floor(noise([i / x, j / y]))) for j in range(y)] for i in range(x) + ] return picarr @@ -55,7 +60,6 @@ def threedterrain(x: int, y: int, octaves: int, progress: bool = False, seed: in pic : list[list[0,1]] threshold list of smooth noise """ - cutoff = 0 multiplier = 1.5 noise = PerlinNoise(octaves=octaves, seed=seed) if progress: @@ -84,5 +88,5 @@ def threedterrain(x: int, y: int, octaves: int, progress: bool = False, seed: in import matplotlib.pyplot as plt pic = threedterrain(256, 256, 4, True, seed=12308) - plt.imshow(pic, cmap='Greys') + plt.imshow(pic, cmap="Greys") plt.show() diff --git a/Terrain/timers.py b/Terrain/timers.py index 8ce50a11e..d0c9b6d36 100644 --- a/Terrain/timers.py +++ b/Terrain/timers.py @@ -1,6 +1,6 @@ import time -from math import * +from math import floor, log10 class BaseTimer: """class for timing things such as testing performance @@ -28,7 +28,7 @@ class FunctionTimer(BaseTimer): def __init__(self, name): self.name = name - print(f"----- Starting \"{self.name}\" -----") + print(f'----- Starting "{self.name}" -----') super().__init__() def stop(self): @@ -39,7 +39,7 @@ def round_sig(x, sig=2): ts = round_sig(ts, 5) - print(f"----- Done. \"{self.name}\" took {ts}s -----") + print(f'----- Done. "{self.name}" took {ts}s -----') def final(self): totalTime = 1 diff --git a/Terrain/visualizer.py b/Terrain/visualizer.py index 4878b5f08..627fd4fa5 100644 --- a/Terrain/visualizer.py +++ b/Terrain/visualizer.py @@ -8,7 +8,15 @@ from Terrain.timers import FunctionTimer -def visualize(x: int, y: int, octaves: int, noiselevel: int, progress: bool = False, setseed: int = 0, show: bool = False): +def visualize( + x: int, + y: int, + octaves: int, + noiselevel: int, + progress: bool = False, + setseed: int = 0, + show: bool = False, +): """ Simple Function used to quickly visualize multi-view """ @@ -16,7 +24,7 @@ def visualize(x: int, y: int, octaves: int, noiselevel: int, progress: bool = Fa noisepic = noiseadder.addnoise(pic, noiselevel) borderpic = border.bordercheck(pic) f = FunctionTimer("Island Check") - perlinnoise = sumPerlin.correctNoiseMaps(x, y, octaves, noiselevel/100, setseed) + perlinnoise = sumPerlin.correctNoiseMaps(x, y, octaves, noiselevel / 100, setseed) islands = blobcheck.blobs(pic) noiseislands = blobcheck.blobs(noisepic) totalnoise = noiseadder.addnoise(perlinnoise, noiselevel) @@ -29,13 +37,13 @@ def visualize(x: int, y: int, octaves: int, noiselevel: int, progress: bool = Fa if show: fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 6)) - axes[0][0].imshow(pic, cmap='binary') - axes[0][1].imshow(totalnoise, cmap='binary') - axes[1][0].imshow(pic, cmap='winter_r') - axes[1][0].imshow(borderpic, cmap='binary', alpha=0.8) - axes[1][1].imshow(borderpic, cmap='binary') - axes[0][2].imshow(perlinnoise, cmap='binary') - axes[1][2].imshow(noisepic, cmap='binary') + axes[0][0].imshow(pic, cmap="binary") + axes[0][1].imshow(totalnoise, cmap="binary") + axes[1][0].imshow(pic, cmap="winter_r") + axes[1][0].imshow(borderpic, cmap="binary", alpha=0.8) + axes[1][1].imshow(borderpic, cmap="binary") + axes[0][2].imshow(perlinnoise, cmap="binary") + axes[1][2].imshow(noisepic, cmap="binary") plt.show() return pic, totalnoise, perlinnoise, noisepic diff --git a/requirements.txt b/requirements.txt index c0ef57e5e..b6b86305a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ matplotlib~=3.6.2 numpy~=1.23.5 -# torch~=1.13.0+cu117 -# torchvision~=0.14.0+cu117 Pillow~=9.3.0 torchsummary~=1.5.1 scipy~=1.9.3