From a70c9b7fefb4e01b6940ea157b7b69648ef2778e Mon Sep 17 00:00:00 2001
From: Si11ium <steffen.illium@ifi.lmu.de>
Date: Sun, 29 Sep 2019 09:37:30 +0200
Subject: [PATCH] Visualization approach n

---
 dataset.py                                    |  58 ++-
 eval/metrices.py                              |   6 +
 networks/adverserial_auto_encoder.py          |   8 +-
 networks/attention_based_auto_enoder.py       |   8 +-
 networks/auto_encoder.py                      |   4 +-
 networks/modules.py                           |  75 +++-
 .../seperating_adversarial_auto_encoder.py    |  19 +-
 networks/variational_auto_encoder.py          |  14 +-
 run_models.py                                 |  77 ++--
 viz/output.png                                | Bin 0 -> 55875 bytes
 viz/print_movement_in_map.py                  |  50 +++
 viz/utils.py                                  | 341 +++++++++++++++++-
 viz/viz_latent.py                             |  84 ++---
 viz/viz_map.py                                |  50 ---
 viz/viz_prediction_in_map.py                  |  55 +++
 15 files changed, 652 insertions(+), 197 deletions(-)
 create mode 100644 eval/metrices.py
 create mode 100644 viz/output.png
 create mode 100644 viz/print_movement_in_map.py
 delete mode 100644 viz/viz_map.py
 create mode 100644 viz/viz_prediction_in_map.py

diff --git a/dataset.py b/dataset.py
index 19c4499..7987c99 100644
--- a/dataset.py
+++ b/dataset.py
@@ -1,9 +1,11 @@
 import argparse
+import bisect
 from collections import defaultdict
 from distutils.util import strtobool
 import os
 import ast
 from abc import ABC, abstractmethod
+from torch.nn.modules import BatchNorm1d
 
 from tqdm import tqdm
 import numpy as np
@@ -61,10 +63,9 @@ class AbstractDataset(ConcatDataset, ABC):
     def processed_paths(self):
         return [os.path.join(self.path, 'processed', x) for x in self.processed_filenames]
 
-    def __init__(self, path, refresh=False, transforms=None, **kwargs):
+    def __init__(self, path, refresh=False, **kwargs):
         self.path = path
         self.refresh = refresh
-        self.transforms = transforms or None
         self.maps = list(set([x.name.split('_')[0] for x in os.scandir(os.path.join(self.path, 'raw'))]))
         super(AbstractDataset, self).__init__(datasets=self._load_datasets())
 
@@ -139,7 +140,19 @@ class DataContainer(AbstractDataset):
                     for attr, x in zip(headers, line.rstrip().split(delimiter)[None:None]):
                         if attr not in ['inDoor']:
                             dataDict[attr].append(ast.literal_eval(x))
-        return Trajectories(self.size, self.step, headers, transforms=self.transforms, **dataDict)
+        return Trajectories(self.size, self.step, headers, **dataDict, normalize=True)
+
+    def get_both_by_key(self, item):
+        if item < 0:
+            if -item > len(self):
+                raise ValueError("absolute value of index should not exceed dataset length")
+            item = len(self) + item
+        dataset_idx = bisect.bisect_right(self.cumulative_sizes, item)
+        if dataset_idx == 0:
+            sample_idx = item
+        else:
+            sample_idx = item - self.cumulative_sizes[dataset_idx - 1]
+        return self.datasets[dataset_idx].get_both_by_key(sample_idx)
 
 
 class Trajectories(Dataset):
@@ -153,12 +166,12 @@ class Trajectories(Dataset):
     def features(self):
         return len(self.isovistMeasures)
 
-    def __init__(self, size, step, headers, transforms=None,  **kwargs):
+    def __init__(self, size, step, headers, normalize=True,  **kwargs):
         super(Trajectories, self).__init__()
         self.size: int = size
         self.step: int = step
         self.headers: list = headers
-        self.transforms: list = transforms or list()
+        self.normalize: bool = normalize
         self.data = self.__init_data_(**kwargs)
         pass
 
@@ -170,9 +183,10 @@ class Trajectories(Dataset):
         # Check if all keys are of same length
         assert len(set(x.size()[0] for x in dataDict.values() if torch.is_tensor(x))) <= 1
         data = torch.stack([dataDict[key] for key in self.isovistMeasures], dim=-1)
-        for transformation in self.transforms:
+        if self.normalize:
             # All but x,y
-            data[:, 2:] = transformation(data[:, 2:])
+            std, mean = torch.std_mean(data[:, 2:], dim=0)
+            data[:, 2:] = (data[:, 2:] - mean) / std
         return data
 
     def __iter__(self):
@@ -180,15 +194,18 @@ class Trajectories(Dataset):
         for i in range(len(self)):
             yield self[i]
 
-    def __getitem__(self, item, coords=False):
-        """
-        Return a trajectory sample from the dataset by a specific key.
-        :param item: The index number of the trajectory to return.
-        :return:
-        """
-        subList = self.data[item:item + self.size * self.step or None:self.step]
-        xy, tensor = subList[:, :2], subList[:, 2:]
-        return (xy, tensor) if coords else tensor
+    def __getitem__(self, item):
+        return self.data[item:item + self.size * self.step or None:self.step][:, 2:]
+
+    def get_isovist_measures_by_key(self, item):
+        return self[item]
+
+    def get_coordinates_by_key(self, item):
+        return self.data[item:item + self.size * self.step or None:self.step][:, :2]
+
+    def get_both_by_key(self, item):
+        data = self.data[item:item + self.size * self.step or None:self.step]
+        return data
 
     def __len__(self):
         total_len = self.data.size()[0]
@@ -224,18 +241,21 @@ class MapContainer(AbstractDataset):
                     for attr, x in zip(headers, line.rstrip().split(delimiter)[None:None]):
                         dataDict[attr].append(ast.literal_eval(x))
 
-        return Map(np.asarray([dataDict[head] for head in headers]))
+        return Map(np.asarray([dataDict[head] for head in headers]),
+                   name=os.path.splitext(os.path.basename(filepath))[0]
+                   )
 
 
 class Map(object):
 
-    def __init__(self, mapData: np.ndarray):
+    def __init__(self, mapData: np.ndarray, name='MapName'):
         """
         This is a Container Class for triangulated basemaps in csv format.
         :param mapData: The map as np.ndarray, already read from disk.
         """
 
-        self.map: np.ndarray = mapData
+        self.map: np.ndarray = np.transpose(mapData)
+        self.name = name
 
         self.minx, self.maxx = np.min(self.map[[0, 2, 4]]), np.max(self.map[[0, 2, 4]])
         self.miny, self.maxy = np.min(self.map[[1, 3, 5]]), np.max(self.map[[1, 3, 5]])
diff --git a/eval/metrices.py b/eval/metrices.py
new file mode 100644
index 0000000..7f9e1b8
--- /dev/null
+++ b/eval/metrices.py
@@ -0,0 +1,6 @@
+#ToDo: We need a metric that analysis sequences of coordinates of arbitrary length and clusters them based
+# on their embedded type of mevement
+
+# ToDo: we ne a function, that compares the clustering outcome of our movement analysis with the AE output.
+
+# Do the variants of AE really adjust their latent space regarding the embedded moveement type?
diff --git a/networks/adverserial_auto_encoder.py b/networks/adverserial_auto_encoder.py
index d4b10d1..3da06c9 100644
--- a/networks/adverserial_auto_encoder.py
+++ b/networks/adverserial_auto_encoder.py
@@ -9,10 +9,10 @@ import torch
 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
 
-class AdversarialAutoEncoder(AutoEncoder):
+class AdversarialAE(AutoEncoder):
 
     def __init__(self, *args, **kwargs):
-        super(AdversarialAutoEncoder, self).__init__(*args, **kwargs)
+        super(AdversarialAE, self).__init__(*args, **kwargs)
         self.discriminator = Discriminator(self.latent_dim, self.features)
 
     def forward(self, batch):
@@ -26,10 +26,10 @@ class AdversarialAutoEncoder(AutoEncoder):
         return z, x_hat
 
 
-class AdversarialAELightningOverrides(LightningModuleOverrides):
+class AdversarialAE_LO(LightningModuleOverrides):
     
     def __init__(self):
-        super(AdversarialAELightningOverrides, self).__init__()
+        super(AdversarialAE_LO, self).__init__()
 
     def training_step(self, batch, _, optimizer_i):
         if optimizer_i == 0:
diff --git a/networks/attention_based_auto_enoder.py b/networks/attention_based_auto_enoder.py
index 3ef456e..1cb12b1 100644
--- a/networks/attention_based_auto_enoder.py
+++ b/networks/attention_based_auto_enoder.py
@@ -7,11 +7,11 @@ from torch import Tensor
 
 #######################
 # Basic AE-Implementation
-class AutoEncoder(AbstractNeuralNetwork, ABC):
+class AE_WithAttention(AbstractNeuralNetwork, ABC):
 
     def __init__(self, latent_dim: int=0, features: int = 0, **kwargs):
         assert latent_dim and features
-        super(AutoEncoder, self).__init__()
+        super(AE_WithAttention, self).__init__()
         self.latent_dim = latent_dim
         self.features = features
         self.encoder = Encoder(self.latent_dim)
@@ -28,10 +28,10 @@ class AutoEncoder(AbstractNeuralNetwork, ABC):
         return z, x_hat
 
 
-class AutoEncoderLightningOverrides(LightningModuleOverrides):
+class AE_WithAttention_LO(LightningModuleOverrides):
 
     def __init__(self):
-        super(AutoEncoderLightningOverrides, self).__init__()
+        super(AE_WithAttention_LO, self).__init__()
 
     def training_step(self, x, batch_nb):
         # ToDo: We need a new loss function, fullfilling all attention needs
diff --git a/networks/auto_encoder.py b/networks/auto_encoder.py
index a834d1d..74f351a 100644
--- a/networks/auto_encoder.py
+++ b/networks/auto_encoder.py
@@ -28,10 +28,10 @@ class AutoEncoder(AbstractNeuralNetwork, ABC):
         return z, x_hat
 
 
-class AutoEncoderLightningOverrides(LightningModuleOverrides):
+class AutoEncoder_LO(LightningModuleOverrides):
 
     def __init__(self):
-        super(AutoEncoderLightningOverrides, self).__init__()
+        super(AutoEncoder_LO, self).__init__()
 
     def training_step(self, x, batch_nb):
         # z, x_hat
diff --git a/networks/modules.py b/networks/modules.py
index f3be740..cf95c55 100644
--- a/networks/modules.py
+++ b/networks/modules.py
@@ -1,9 +1,13 @@
 import os
+from operator import mul
+from functools import reduce
 
 import torch
+from torch import randn
 import pytorch_lightning as pl
 from pytorch_lightning import data_loader
-from torch.nn import Module, Linear, ReLU, Tanh, Sigmoid, Dropout, GRU
+from torch.nn import Module, Linear, ReLU, Sigmoid, Dropout, GRU
+from torchvision.transforms import Normalize
 
 from abc import ABC, abstractmethod
 
@@ -29,8 +33,16 @@ class LightningModuleOverrides:
     @data_loader
     def tng_dataloader(self):
         num_workers = 0  # os.cpu_count() // 2
-        return DataLoader(DataContainer(os.path.join('data', 'training'), self.size, self.step),
+        return DataLoader(DataContainer(os.path.join('data', 'training'),
+                                        self.size, self.step, transforms=[Normalize]),
                           shuffle=True, batch_size=10000, num_workers=num_workers)
+    """
+    @data_loader
+    def val_dataloader(self):
+        num_workers = 0  # os.cpu_count() // 2
+        return DataLoader(DataContainer(os.path.join('data', 'validation'), self.size, self.step),
+                          shuffle=True, batch_size=100, num_workers=num_workers)
+    """
 
 
 class AbstractNeuralNetwork(Module):
@@ -82,6 +94,7 @@ class LightningModule(pl.LightningModule, ABC):
         # return DataLoader(MNIST(os.getcwd(), train=True, download=True,
         # transform=transforms.ToTensor()), batch_size=32)
 
+    """
     @pl.data_loader
     def val_dataloader(self):
         # OPTIONAL
@@ -91,7 +104,7 @@ class LightningModule(pl.LightningModule, ABC):
     def test_dataloader(self):
         # OPTIONAL
         pass
-
+    """
 
 #######################
 # Utility Modules
@@ -185,7 +198,7 @@ class DecoderLinearStack(Module):
         self.l1 = Linear(10, 100, bias=True)
         self.l2 = Linear(100, out_shape, bias=True)
         self.activation = ReLU()
-        self.activation_out = Tanh()
+        self.activation_out = Sigmoid()
 
     def forward(self, x):
         tensor = self.l1(x)
@@ -197,30 +210,53 @@ class DecoderLinearStack(Module):
 
 class EncoderLinearStack(Module):
 
-    def __init__(self):
+    @property
+    def shape(self):
+        x = randn(self.features).unsqueeze(0)
+        output = self(x)
+        return output.shape[1:]
+
+    def __init__(self, features=6, separated=False, use_bias=True):
         super(EncoderLinearStack, self).__init__()
         # FixMe: Get Hardcoded shit out of here
-        self.l1 = Linear(6, 100, bias=True)
-        self.l2 = Linear(100, 10, bias=True)
+        self.separated = separated
+        self.features = features
+        if self.separated:
+            self.l1s = [Linear(1, 10, bias=use_bias) for _ in range(self.features)]
+            self.l2s = [Linear(10, 5, bias=use_bias) for _ in range(self.features)]
+        else:
+            self.l1 = Linear(self.features, self.features * 10, bias=use_bias)
+            self.l2 = Linear(self.features * 10, self.features * 5, bias=use_bias)
+        self.l3 = Linear(self.features * 5, 10, use_bias)
         self.activation = ReLU()
 
     def forward(self, x):
-        tensor = self.l1(x)
-        tensor = self.activation(tensor)
-        tensor = self.l2(tensor)
+        if self.separated:
+            x = x.unsqueeze(-1)
+            tensors = [self.l1s[idx](x[:, idx, :]) for idx in range(len(self.l1s))]
+            tensors = [self.activation(tensor) for tensor in tensors]
+            tensors = [self.l2s[idx](tensors[idx]) for idx in range(len(self.l2s))]
+            tensors = [self.activation(tensor) for tensor in tensors]
+            tensor = torch.cat(tensors, dim=-1)
+        else:
+            tensor = self.l1(x)
+            tensor = self.activation(tensor)
+            tensor = self.l2(tensor)
+        tensor = self.l3(tensor)
         tensor = self.activation(tensor)
         return tensor
 
 
 class Encoder(Module):
 
-    def __init__(self, lat_dim, variational=False):
+    def __init__(self, lat_dim, variational=False, separate_features=False, with_dense=True, features=6):
         self.lat_dim = lat_dim
+        self.features = features
         self.variational = variational
-
         super(Encoder, self).__init__()
-        self.l_stack = TimeDistributed(EncoderLinearStack())
-        self.gru = GRU(10, 10, batch_first=True)
+        self.l_stack = TimeDistributed(EncoderLinearStack(separated=separate_features,
+                                                          features=features)) if with_dense else False
+        self.gru = GRU(10 if with_dense else self.features, 10, batch_first=True)
         self.filter = RNNOutputFilter(only_last=True)
         if variational:
             self.mu = Linear(10, self.lat_dim)
@@ -229,8 +265,9 @@ class Encoder(Module):
             self.lat_dim_layer = Linear(10, self.lat_dim)
 
     def forward(self, x):
-        tensor = self.l_stack(x)
-        tensor = self.gru(tensor)
+        if self.l_stack:
+            x = self.l_stack(x)
+        tensor = self.gru(x)
         tensor = self.filter(tensor)
         if self.variational:
             tensor = self.mu(tensor), self.logvar(tensor)
@@ -262,10 +299,10 @@ class PoolingEncoder(Module):
         self.p = AvgDimPool()
         self.l = EncoderLinearStack()
         if variational:
-            self.mu = Linear(10, self.lat_dim)
-            self.logvar = Linear(10, self.lat_dim)
+            self.mu = Linear(self.l.shape, self.lat_dim)
+            self.logvar = Linear(self.l.shape, self.lat_dim)
         else:
-            self.lat_dim_layer = Linear(10, self.lat_dim)
+            self.lat_dim_layer = Linear(reduce(mul, self.l.shape), self.lat_dim)
 
     def forward(self, x):
         tensor = self.p(x)
diff --git a/networks/seperating_adversarial_auto_encoder.py b/networks/seperating_adversarial_auto_encoder.py
index ad914d9..daae05b 100644
--- a/networks/seperating_adversarial_auto_encoder.py
+++ b/networks/seperating_adversarial_auto_encoder.py
@@ -4,15 +4,15 @@ from networks.modules import *
 import torch
 
 
-class SeperatingAdversarialAutoEncoder(Module):
+class SeperatingAAE(Module):
 
     def __init__(self, latent_dim, features):
-        super(SeperatingAdversarialAutoEncoder, self).__init__()
+        super(SeperatingAAE, self).__init__()
 
         self.latent_dim = latent_dim
         self.features = features
         self.spatial_encoder = PoolingEncoder(self.latent_dim)
-        self.temporal_encoder = Encoder(self.latent_dim)
+        self.temporal_encoder = Encoder(self.latent_dim, with_dense=False)
         self.decoder = Decoder(self.latent_dim * 2, self.features)
         self.spatial_discriminator = Discriminator(self.latent_dim, self.features)
         self.temporal_discriminator = Discriminator(self.latent_dim, self.features)
@@ -29,10 +29,19 @@ class SeperatingAdversarialAutoEncoder(Module):
         return z_spatial, z_temporal, x_hat
 
 
-class SeparatingAdversarialAELightningOverrides(LightningModuleOverrides):
+class SuperSeperatingAAE(SeperatingAAE):
+    def __init__(self, *args):
+        super(SuperSeperatingAAE, self).__init__(*args)
+        self.temporal_encoder = Encoder(self.latent_dim, separate_features=True)
+
+    def forward(self, batch):
+        return batch
+
+
+class SeparatingAAE_LO(LightningModuleOverrides):
 
     def __init__(self):
-        super(SeparatingAdversarialAELightningOverrides, self).__init__()
+        super(SeparatingAAE_LO, self).__init__()
 
     def training_step(self, batch, _, optimizer_i):
         spatial_latent_fake, temporal_latent_fake, batch_hat = self.network.forward(batch)
diff --git a/networks/variational_auto_encoder.py b/networks/variational_auto_encoder.py
index aad4a54..31c61ba 100644
--- a/networks/variational_auto_encoder.py
+++ b/networks/variational_auto_encoder.py
@@ -6,7 +6,7 @@ from torch.nn.functional import mse_loss
 
 #######################
 # Basic AE-Implementation
-class VariationalAutoEncoder(AbstractNeuralNetwork, ABC):
+class VariationalAE(AbstractNeuralNetwork, ABC):
 
     @property
     def name(self):
@@ -14,7 +14,7 @@ class VariationalAutoEncoder(AbstractNeuralNetwork, ABC):
 
     def __init__(self, latent_dim=0, features=0, **kwargs):
         assert latent_dim and features
-        super(VariationalAutoEncoder, self).__init__()
+        super(VariationalAE, self).__init__()
         self.features = features
         self.latent_dim = latent_dim
         self.encoder = Encoder(self.latent_dim, variational=True)
@@ -32,16 +32,16 @@ class VariationalAutoEncoder(AbstractNeuralNetwork, ABC):
         z = self.reparameterize(mu, logvar)
         repeat = Repeater((batch.shape[0], batch.shape[1], -1))
         x_hat = self.decoder(repeat(z))
-        return x_hat, mu, logvar
+        return mu, logvar, x_hat
 
 
-class VariationalAutoEncoderLightningOverrides(LightningModuleOverrides):
+class VAE_LO(LightningModuleOverrides):
 
     def __init__(self):
-        super(VariationalAutoEncoderLightningOverrides, self).__init__()
+        super(VAE_LO, self).__init__()
 
     def training_step(self, x, _):
-        x_hat, logvar, mu = self.forward(x)
+        mu, logvar, x_hat = self.forward(x)
         BCE = mse_loss(x_hat, x, reduction='mean')
 
         # see Appendix B from VAE paper:
@@ -52,7 +52,7 @@ class VariationalAutoEncoderLightningOverrides(LightningModuleOverrides):
         return {'loss': BCE + KLD}
 
     def configure_optimizers(self):
-        return [Adam(self.parameters(), lr=0.02)]
+        return [Adam(self.parameters(), lr=0.004)]
 
 
 if __name__ == '__main__':
diff --git a/run_models.py b/run_models.py
index 4405a5b..294728a 100644
--- a/run_models.py
+++ b/run_models.py
@@ -1,30 +1,32 @@
 from torch.distributions import Normal
-from networks.auto_encoder import *
 
 import time
-from networks.variational_auto_encoder import *
-from networks.adverserial_auto_encoder import *
-from networks.seperating_adversarial_auto_encoder import *
-from networks.modules import LightningModule
-from pytorch_lightning import Trainer
-from test_tube import Experiment
+import os
 
 from argparse import Namespace
 from argparse import ArgumentParser
 from distutils.util import strtobool
 
+from networks.auto_encoder import AutoEncoder, AutoEncoder_LO
+from networks.variational_auto_encoder import VariationalAE, VAE_LO
+from networks.adverserial_auto_encoder import AdversarialAE_LO, AdversarialAE
+from networks.seperating_adversarial_auto_encoder import SeperatingAAE, SeparatingAAE_LO, SuperSeperatingAAE
+from networks.modules import LightningModule
+
+from pytorch_lightning import Trainer
+from test_tube import Experiment
+
+
 args = ArgumentParser()
-args.add_argument('--step', default=6)
+args.add_argument('--step', default=5)
 args.add_argument('--features', default=6)
 args.add_argument('--size', default=9)
-args.add_argument('--latent_dim', default=4)
-args.add_argument('--model', default='Model')
+args.add_argument('--latent_dim', default=2)
+args.add_argument('--model', default='VAE_Model')
 args.add_argument('--refresh', type=strtobool, default=False)
 
 
-# ToDo: How to implement this better?
-# other_classes = [AutoEncoder, AutoEncoderLightningOverrides]
-class Model(AutoEncoderLightningOverrides, LightningModule):
+class AE_Model(AutoEncoder_LO, LightningModule):
 
     def __init__(self, parameters):
         assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']])
@@ -32,11 +34,23 @@ class Model(AutoEncoderLightningOverrides, LightningModule):
         self.latent_dim = parameters.latent_dim
         self.features = parameters.features
         self.step = parameters.step
-        super(Model, self).__init__()
+        super(AE_Model, self).__init__()
         self.network = AutoEncoder(self.latent_dim, self.features)
 
 
-class AdversarialModel(AdversarialAELightningOverrides, LightningModule):
+class VAE_Model(VAE_LO, LightningModule):
+
+    def __init__(self, parameters):
+        assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']])
+        self.size = parameters.size
+        self.latent_dim = parameters.latent_dim
+        self.features = parameters.features
+        self.step = parameters.step
+        super(VAE_Model, self).__init__()
+        self.network = VariationalAE(self.latent_dim, self.features)
+
+
+class AAE_Model(AdversarialAE_LO, LightningModule):
 
     def __init__(self, parameters: Namespace):
         assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']])
@@ -44,13 +58,13 @@ class AdversarialModel(AdversarialAELightningOverrides, LightningModule):
         self.latent_dim = parameters.latent_dim
         self.features = parameters.features
         self.step = parameters.step
-        super(AdversarialModel, self).__init__()
+        super(AAE_Model, self).__init__()
         self.normal = Normal(0, 1)
-        self.network = AdversarialAutoEncoder(self.latent_dim, self.features)
+        self.network = AdversarialAE(self.latent_dim, self.features)
         pass
 
 
-class SeparatingAdversarialModel(SeparatingAdversarialAELightningOverrides, LightningModule):
+class SAAE_Model(SeparatingAAE_LO, LightningModule):
 
     def __init__(self, parameters: Namespace):
         assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']])
@@ -58,9 +72,23 @@ class SeparatingAdversarialModel(SeparatingAdversarialAELightningOverrides, Ligh
         self.latent_dim = parameters.latent_dim
         self.features = parameters.features
         self.step = parameters.step
-        super(SeparatingAdversarialModel, self).__init__()
+        super(SAAE_Model, self).__init__()
         self.normal = Normal(0, 1)
-        self.network = SeperatingAdversarialAutoEncoder(self.latent_dim, self.features)
+        self.network = SeperatingAAE(self.latent_dim, self.features)
+        pass
+
+
+class SSAAE_Model(SeparatingAAE_LO, LightningModule):
+
+    def __init__(self, parameters: Namespace):
+        assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']])
+        self.size = parameters.size
+        self.latent_dim = parameters.latent_dim
+        self.features = parameters.features
+        self.step = parameters.step
+        super(SSAAE_Model, self).__init__()
+        self.normal = Normal(0, 1)
+        self.network = SuperSeperatingAAE(self.latent_dim, self.features)
         pass
 
 
@@ -84,8 +112,13 @@ if __name__ == '__main__':
         period=4
     )
 
-    trainer = Trainer(experiment=exp, max_nb_epochs=250, gpus=[0],
-                      add_log_row_interval=1000, checkpoint_callback=checkpoint_callback)
+    trainer = Trainer(experiment=exp,
+                      max_nb_epochs=250,
+                      gpus=[0],
+                      add_log_row_interval=1000,
+                      # checkpoint_callback=checkpoint_callback
+                      )
+
     trainer.fit(model)
     trainer.save_checkpoint(os.path.join(outpath, 'weights.ckpt'))
 
diff --git a/viz/output.png b/viz/output.png
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f1ad32caeaf6b35460d463b0870a2141e04bd
GIT binary patch
literal 55875
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

literal 0
HcmV?d00001

diff --git a/viz/print_movement_in_map.py b/viz/print_movement_in_map.py
new file mode 100644
index 0000000..5d002a1
--- /dev/null
+++ b/viz/print_movement_in_map.py
@@ -0,0 +1,50 @@
+from argparse import ArgumentParser
+import os
+
+from dataset import DataContainer
+from viz.utils import MotionAnalyser, Printer, MapContainer, search_for_weights
+import torch
+from run_models import SAAE_Model, AAE_Model, VAE_Model, AE_Model
+
+arguments = ArgumentParser()
+arguments.add_argument('--data', default='output')
+
+device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+
+def load_and_viz(path_like_element):
+    # Define Loop to search for models and folder with visualizations
+    splitpath = path_like_element.split(os.sep)
+    base_dir = os.path.join(*splitpath[:4])
+    model = globals()[splitpath[2]]
+    print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}')
+    pretrained_model = model.load_from_metrics(
+        weights_path=path_like_element,
+        tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'),
+        on_gpu=True if torch.cuda.is_available() else False,
+        # map_location=None
+    )
+
+    # Init model and freeze its weights ( for faster inference)
+    pretrained_model = pretrained_model.to(device)
+    pretrained_model.eval()
+    pretrained_model.freeze()
+
+    dataIndex = 0
+
+    datasets = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device)
+    dataset = datasets.datasets[dataIndex]
+    # ToDO: use dataloader for iteration instead!  -  dataloader = DataLoader(dataset, )
+
+    maps = MapContainer(os.path.join(os.pardir, 'data', 'validation'))
+    base_map = maps.datasets[dataIndex]
+
+    p = Printer(pretrained_model)
+    p.print_trajec_on_basemap(dataset, base_map, save=os.path.join(base_dir, f'{base_map.name}_movement.png'),
+                              color_by_movement=True)
+    return True
+
+
+if __name__ == '__main__':
+    args = arguments.parse_args()
+    search_for_weights(load_and_viz, args.data, file_type='movement')
diff --git a/viz/utils.py b/viz/utils.py
index 0248b79..0407ed8 100644
--- a/viz/utils.py
+++ b/viz/utils.py
@@ -1,13 +1,30 @@
-import os
+from typing import Union
+from functools import reduce
+
+from statistics import stdev
+
+from sklearn.cluster import Birch, KMeans, DBSCAN
+from sklearn.manifold import TSNE
+from sklearn.decomposition import PCA
+
+from dataset import *
+from networks.modules import AbstractNeuralNetwork
+
+from matplotlib import pyplot as plt
+from matplotlib.patches import Polygon
+from matplotlib.collections import LineCollection, PatchCollection
+import matplotlib.colors as mcolors
+import matplotlib.cm as cmaps
+
+from math import pi
 
 
-def search_for_weights(func, folder):
+def search_for_weights(func, folder, file_type='latent_space'):
     while not os.path.exists(folder):
         if len(os.path.split(folder)) >= 50:
             raise FileNotFoundError(f'The folder "{folder}" could not be found')
         folder = os.path.join(os.pardir, folder)
-
-    if any([x.name.endswith('.png') for x in os.scandir(folder)]):
+    if any([file_type in x.name for x in os.scandir(folder)]):
         return
 
     if any(['.ckpt' in element.name and element.is_dir() for element in os.scandir(folder)]):
@@ -19,12 +36,324 @@ def search_for_weights(func, folder):
     for element in os.scandir(folder):
         if os.path.exists(element):
             if element.is_dir():
-                search_for_weights(func, element.path)
+                search_for_weights(func, element.path, file_type=file_type)
             elif element.is_file() and element.name.endswith('.ckpt'):
-                func(element)
+                func(element.path)
             else:
                 continue
 
 
+class Printer(object):
+
+    def __init__(self, model: AbstractNeuralNetwork, ax=None):
+        self.norm = mcolors.Normalize(vmin=0, vmax=1)
+        self.colormap = cmaps.gist_rainbow
+        self.network = model
+        self.fig = plt.figure(dpi=300)
+        self.ax = ax if ax else plt.subplot(1, 1, 1)
+        pass
+
+    def colorize(self, x, min_val: Union[float, None] = None, max_val: Union[float, None] = None,
+                 colormap=cmaps.rainbow, **kwargs):
+        norm = mcolors.Normalize(vmin=min_val, vmax=max_val)
+        colored = colormap(norm(x))
+        return colored
+
+    @staticmethod
+    def project_to_2d(data: np.ndarray, method: str = 'tsne') -> np.ndarray:
+        projector = TSNE() if method.lower() == 'tsne' else PCA()
+        print('Starting TSNE Transformation')
+        projected_data = projector.fit_transform(data)
+        assert projected_data.shape[-1] == 2
+        print('TSNE Projection Successfull')
+        return projected_data
+
+    @staticmethod
+    def cluster_data(data: np.ndarray, cluster_center_file: str = None) -> np.ndarray:
+        print('Start Clustering with Birch')
+        if cluster_center_file:
+            with open(cluster_center_file, 'r') as f:
+                cluster_center_string = f.readlines()[0]
+                centers = ast.literal_eval(cluster_center_string)
+                clusterer = Birch(n_clusters=len(centers))
+                clusterer.init = np.asarray(centers)
+        else:
+            # clusterer = Birch(n_clusters=None)
+            clusterer = Birch()
+
+        labels = clusterer.fit_predict(data)
+        print('Birch Clustering Sucessfull')
+        return labels
+
+    def print_possible_latent_spaces(self, data: Trajectories, n: Union[int, str] = 1000, **kwargs):
+        predictions, _ = self._gather_predictions(data, n)
+        if len(predictions) >= 2:
+            predictions += (torch.cat(predictions, dim=-1), )
+
+        labels = self.cluster_data(predictions[-1])
+        for idx, prediction in enumerate(predictions):
+            self.print_latent_space(prediction, labels, running_index=idx, **kwargs)
+
+    def print_latent_space(self, prediction, labels, running_index=0, save=None):
+
+        self.colormap = cmaps.tab20
+
+        if isinstance(prediction, torch.Tensor):
+            prediction = prediction.numpy()
+        elif isinstance(prediction, np.ndarray):
+            pass
+        elif isinstance(prediction, list):
+            prediction = np.asarray(prediction)
+        else:
+            raise RuntimeError
+
+        if prediction.shape[-1] > 2:
+            fig, axs = plt.subplots(ncols=2, nrows=1)
+            transformers = [TSNE(2), PCA(2)]
+            print('Starting Dimensional Reduction')
+            for idx, transformer in enumerate(transformers):
+                transformed = transformer.fit_transform(prediction)
+                print(f'{transformer.__class__.__name__} Projection Sucessfull')
+                colored = self.colormap(labels)
+                ax = axs[idx]
+                ax.scatter(x=transformed[:, 0], y=transformed[:, 1], c=colored)
+                ax.set_title(transformer.__class__.__name__)
+                ax.set_xlim(np.min(transformed[:, 0])*1.1, np.max(transformed[:, 0]*1.1))
+                ax.set_ylim(np.min(transformed[:, 1]*1.1), np.max(transformed[:, 1]*1.1))
+        elif prediction.shape[-1] == 2:
+            fig, axs = plt.subplots()
+
+            # TODO: Build transformation for lat_dim_size >= 3
+            print('All Predictions sucesfully Gathered and Shaped ')
+            axs.set_xlim(np.min(prediction[:, 0]), np.max(prediction[:, 0]))
+            axs.set_ylim(np.min(prediction[:, 1]), np.max(prediction[:, 1]))
+            # ToDo: Insert Normalization
+            colored = self.colormap(labels)
+            plt.scatter(prediction[:, 0], prediction[:, 1], c=colored)
+        else:
+            raise NotImplementedError("Latent Dimensions can not be one-dimensional (yet).")
+
+        if save:
+            plt.savefig(f'{save}_{running_index}.png')
+
+    def print_latent_density(self):  # , data: DataContainer):
+        raise NotImplementedError("My Future Self has to come up with smth")
+
+        # fig, ax = plt.subplots()
+
+        # preds = []
+        # for i in range(data.len - data.width * data.stepsize):
+        # for i in range(5000):
+        #
+        #    seq = data.sub_trajectory_by_key(i, stepsize=data.stepsize)
+        #
+        #    preds.append(self.nn.encoder([seq[None, ...]])[0])
+        #
+        # TODO: Build transformation for lat_dim_size >= 3
+        # pred_array = np.asarray(preds).reshape((-1, nn.latDim))
+        # k = KernelDensity()
+        # k.fit(pred_array)
+        # z = np.exp(k.score_samples(pred_array))
+        #
+        # levels = np.linspace(0, z.max(), 25)
+        # xgrid, ygrid = np.meshgrid(pred_array[::5, 0], pred_array[::5, 1])
+        # xy = np.vstack([xgrid.ravel(), ygrid.ravel()]).T
+        # z = np.exp(k.score_samples(xy)).reshape(xgrid.shape)
+        #
+        # plt.contourf(xgrid, ygrid, z, levels=levels, cmap=plt.cm.Reds)
+        # plt.show()
+
+    def _gather_predictions(self, data: Trajectories, n: int = 1000,
+                            color_by_movement=False, **kwargs):
+        """
+        Check if any value for n is given and gather some random datapoints from the dataset. In accordance with the
+               maximal possible trajectory amount that is given by stepsize * width.
+        Also retunr the keys for all possible predictions.
+        :param data:
+        :type data: Dataset
+        :param n:
+        :param tsne:
+        :param kwargs:
+        :return:
+        """
+        print("Gathering Predictions")
+
+        n = n if isinstance(n, int) and n else len(data) - (data.size * data.step)
+        idxs = np.random.choice(np.arange(len(data) - data.step * data.size), n, replace=False)
+        complete_data = torch.stack([data.get_both_by_key(idx) for idx in idxs], dim=0)
+        segment_coords, trajectories = complete_data[:, :, :2], complete_data[:, :, 2:]
+        if color_by_movement:
+            motion_analyser = MotionAnalyser()
+            predictions = (motion_analyser.cluster_motion(segment_coords), )
+
+        else:
+            with torch.no_grad():
+                predictions = self.network(trajectories)[:-1]
+
+        return predictions, segment_coords
+
+    @staticmethod
+    def colorize_as_hsv(self, x, min_val: Union[float, None] = None, max_val: Union[float, None] = None,
+                        colormap=cmaps.rainbow, **kwargs):
+        norm = mcolors.Normalize(vmin=min_val, vmax=max_val)
+        colored = colormap(norm(x))
+        return colored
+
+    def _build_trajectory_shapes(self, predictions: np.ndarray, segment_coordinates,
+                                 axis=None, transformation=TSNE, **kwargs):
+        if not isinstance(predictions, np.ndarray):
+            predictions = tuple((x if torch.is_tensor(x) else torch.from_numpy(x) for x in predictions))
+            predictions = torch.cat(predictions, dim=-1)
+
+        if axis is not None:
+            predictions = predictions[:, axis][..., None]
+
+        if predictions.shape[-1] >= 4:
+            if True:
+                predictions = Birch(n_clusters=3).fit_predict(predictions).reshape(-1, 1)
+            else:
+                transformer = transformation(n_components=3, random_state=42)
+                predictions = transformer.fit_transform(predictions)
+
+        if predictions.shape[-1] == 1:
+            colored = self.colorize(predictions.reshape(-1), **kwargs)
+
+        elif predictions.shape[-1] == 2:
+            colored = self.colorize(predictions[:, 0], **kwargs)
+
+            if kwargs.get('min_val', None):
+                lightning = mcolors.Normalize(vmin=kwargs.get('min_val', None), vmax=kwargs.get('max_val', None))
+            else:
+                lightning = mcolors.Normalize()
+            alpha = lightning(predictions[:, 1])
+            colored[:, -1] = alpha
+
+        elif predictions.shape[-1] == 3:
+            norm = mcolors.Normalize()
+            colored = [(r, g, b) for r,g,b in norm(predictions)]
+
+        else:
+            raise NotImplementedError('Full Prediction Shape was: {}'.format(predictions.shape))
+            # TODO Build a isomap or tsne transformation here to get a two dimensional space
+
+        segment_coordinates = segment_coordinates.cpu() if torch.is_tensor(segment_coordinates) else segment_coordinates
+
+        return LineCollection(segment_coordinates, linewidths=(1, 1, 1, 1),
+                              colors=colored, linestyle='solid')
+
+    @staticmethod
+    def _build_map_shapes(base_map: Map):
+        # Base Map Plotting
+        # filled Triangle
+        patches = [Polygon(base_map[i], True, color='black') for i in range(len(base_map))]
+        return PatchCollection(patches, color='black')
+
+    def print_trajec_on_basemap(self, data, base_map: Map, save=False, color_by_movement=False, **kwargs):
+        """
+
+        :rtype: object
+        """
+        prediction_segments = self._gather_predictions(data, color_by_movement=color_by_movement, **kwargs)
+        trajectory_shapes = self._build_trajectory_shapes(*prediction_segments, **kwargs)
+        map_shapes = self._build_map_shapes(base_map)
+        self.ax.add_collection(trajectory_shapes)
+        self.ax.axis('auto')
+        self.ax.add_collection(map_shapes)
+
+        self.ax.set_title('Trajectories on BaseMap')
+        if save:
+            if isinstance(save, str):
+                self.save(save)
+            else:
+                self.save(base_map.name)
+        pass
+
+    @staticmethod
+    def show():
+        plt.show()
+        return True
+
+    @staticmethod
+    def save(filename):
+        plt.savefig(filename)
+
+
+class MotionAnalyser(object):
+
+    def __init__(self):
+        pass
+
+    def _sequential_pairwise_map(self, func, xy_sequence, on_deltas=False):
+        zipped_list = [x for x in zip(xy_sequence[:-1], xy_sequence[1:])]
+
+        if on_deltas:
+            zipped_list = [self.delta(*movement) for movement in zipped_list]
+        else:
+            pass
+
+        return [func(*xy) for xy in zipped_list]
+
+    @staticmethod
+    def delta(x1y1, x2y2):
+        x1, y1 = x1y1
+        x2, y2 = x2y2
+        return x2-x1, y2-y1
+
+    @staticmethod
+    def get_r(deltax, deltay):
+        # https://mathinsight.org/polar_coordinates
+        r = torch.sqrt(deltax**2 + deltay**2)
+        return r
+
+    @staticmethod
+    def get_theta(deltax, deltay, rad=False):
+        # https://mathinsight.org/polar_coordinates
+        theta = torch.atan2(deltay, deltax)
+        return theta if rad else theta * 180 / pi
+
+    def get_theta_for_sequence(self, xy_sequence):
+        ts = self._sequential_pairwise_map(self.get_theta, xy_sequence, on_deltas=True)
+        return ts
+
+    def get_r_for_sequence(self, xy_sequence):
+        rs = self._sequential_pairwise_map(self.get_r, xy_sequence, on_deltas=True)
+        return rs
+
+    def get_unique_seq_identifier(self, xy_sequence):
+
+        # Globals
+        global_delta = self.delta(xy_sequence[0], xy_sequence[-1])
+        global_theta = self.get_theta(*global_delta)
+        global_r = self.get_r(*global_delta)
+
+        # For Each
+        theta_seq = self.get_theta_for_sequence(xy_sequence)
+        mean_theta = sum(theta_seq) / len(theta_seq)
+        theta_sum = sum([abs(theta) for theta in theta_seq])
+        std_theta = stdev(map(float, theta_seq))
+
+        return torch.stack((global_r, torch.as_tensor(std_theta), mean_theta, global_theta))
+
+    def cluster_motion(self, trajectory_samples, cluster_class=KMeans):
+        cluster_class = cluster_class(3)
+
+        std, mean = torch.std_mean(trajectory_samples, dim=0)
+        trajectory_samples = (trajectory_samples - mean) / std
+
+        unique_seq_identifiers = torch.stack([self.get_unique_seq_identifier(trajectory)
+                                              for trajectory in trajectory_samples])
+
+        clustered_movement = cluster_class.fit_predict(unique_seq_identifiers)
+        if False:
+            from sklearn.decomposition import PCA
+            p = PCA(2)
+            t = p.fit_transform(unique_seq_identifiers)
+            f = plt.figure()
+            plt.scatter(t[:, 0], t[:,1])
+            plt.show()
+
+        return clustered_movement.reshape(-1, 1)
+
+
 if __name__ == '__main__':
     raise PermissionError('This file should not be called.')
diff --git a/viz/viz_latent.py b/viz/viz_latent.py
index ab16f3f..ceaa132 100644
--- a/viz/viz_latent.py
+++ b/viz/viz_latent.py
@@ -1,12 +1,12 @@
-from sklearn.manifold import TSNE
-from sklearn.decomposition import PCA
-
-import seaborn as sns
-import matplotlib.pyplot as plt
-
+import warnings
+warnings.filterwarnings('ignore', category=FutureWarning)
+import torch
+from dataset import DataContainer
+from viz.utils import search_for_weights, Printer
 from run_models import *
 
-sns.set()
+
+device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
 
 def load_and_predict(path_like_element):
@@ -14,73 +14,39 @@ def load_and_predict(path_like_element):
     splitpath = path_like_element.split(os.sep)
     base_dir = os.path.join(*splitpath[:4])
     model = globals()[splitpath[2]]
-    print(f'... loading model named: "{Model.name}" from timestamp: {splitpath[3]}')
     pretrained_model = model.load_from_metrics(
         weights_path=path_like_element,
         tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'),
         on_gpu=True if torch.cuda.is_available() else False,
-        map_location=None
+        # map_location=None
     )
+    print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}')
 
     # Init model and freeze its weights ( for faster inference)
     pretrained_model = pretrained_model.to(device)
     pretrained_model.eval()
     pretrained_model.freeze()
 
-    with torch.no_grad():
+    # Load the data for prediction
 
-        # Load the data for prediction
+    # TODO!!!!!!!!!:
+    # Hier müssen natürlich auch die date parameter geladen werden!
+    # Muss ich die val-sets automatisch neu setzen, also immer auf refresh haben, wenn ich validieren möchte?
+    # Was ist denn eigentlich mein Val Dataset?
+    # Hab ich irgendwo eine ganze karte?
+    # Wie sorge ich dafür, dass gewisse karten, also größenverhältnisse usw nicht überrepräsentiert sind?
+    dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device)
 
-        # TODO!!!!!!!!!: 
-        # Hier müssen natürlich auch die date parameter geladen werden!
-        # Muss ich die val-sets automatisch neu setzen, also immer auf refresh haben, wenn ich validieren möchte?
-        # Was ist denn eigentlich mein Val Dataset?
-        # Hab ich irgendwo eine ganze karte?
-        # Wie sorge ich dafür, dass gewisse karten, also größenverhältnisse usw nicht überrepräsentiert sind?
-        dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device)
-        dataloader = DataLoader(dataset, shuffle=True, batch_size=len(dataset))
+    # Do the inference
+    # test_pred = [pretrained_model(test_sample)[:-1] for test_sample in dataloader][0]
 
-        # Do the inference
-        test_pred = [pretrained_model(test_sample)[:-1] for test_sample in dataloader][0]
-
-    for idx, prediction in enumerate(test_pred):
-        plot, _ = viz_latent(prediction)
-        plot.savefig(os.path.join(base_dir, f'latent_space_{idx}.png'))
-
-
-def viz_latent(prediction):
-    try:
-        prediction = prediction.cpu()
-        prediction = prediction.numpy()
-    except AttributeError:
-        pass
-
-    if prediction.shape[-1] <= 1:
-        raise ValueError('How did this happen?')
-    elif prediction.shape[-1] == 2:
-        ax = sns.scatterplot(x=prediction[:, 0], y=prediction[:, 1])
-        try:
-            plt.show()
-        except:
-            pass
-        return ax.figure, (ax)
-    else:
-        fig, axs = plt.subplots(ncols=2)
-        plots = []
-        for idx, dim_reducer in enumerate([PCA, TSNE]):
-            predictions_reduced = dim_reducer(n_components=2).fit_transform(prediction)
-            plot = sns.scatterplot(x=predictions_reduced[:, 0], y=predictions_reduced[:, 1],
-                                         ax=axs[idx])
-            plot.set_title(dim_reducer.__name__)
-            plots.append(plot)
-
-        try:
-            plt.show()
-        except:
-            pass
-        return fig, (*plots, )
+    p = Printer(pretrained_model)
+    # Important:
+    # Use all given valdiation samples, even if they relate to differnt maps. This is important since we want to have a
+    # view on the complete latent space, not just in relation to a single basemap, which would be a major bias.
+    p.print_possible_latent_spaces(dataset, save=os.path.join(base_dir, f'latent_space'))
 
 
 if __name__ == '__main__':
     path = 'output'
-    search_for_weights(search_for_weights, path)
\ No newline at end of file
+    search_for_weights(load_and_predict, path, file_type='latent')
\ No newline at end of file
diff --git a/viz/viz_map.py b/viz/viz_map.py
deleted file mode 100644
index 4b5a6eb..0000000
--- a/viz/viz_map.py
+++ /dev/null
@@ -1,50 +0,0 @@
-
-from dataset import *
-# Plotting
-# import matplotlib as mlp
-from matplotlib import pyplot as plt
-from matplotlib.patches import Polygon
-from matplotlib.collections import LineCollection, PatchCollection
-import matplotlib.colors as mcolors
-import matplotlib.cm as cmaps
-
-from sklearn.manifold import TSNE
-from sklearn.decomposition import PCA
-
-import seaborn as sns
-from argparse import ArgumentParser
-
-from viz.utils import search_for_weights
-
-from run_models import *
-
-sns.set()
-
-
-arguments = ArgumentParser()
-arguments.add_argument('--data', default=os.path.join('data', 'validation'))
-
-dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device)
-dataloader = DataLoader(dataset, shuffle=True, batch_size=len(dataset))
-
-
-
-def viz_map(self, base_map: MapContainer):
-    # Base Map Plotting
-    # filled Triangle
-    patches = [Polygon(base_map.get_triangle_by_key(i), True, color='k') for i in range(len(base_map))]
-    patch_collection = PatchCollection(patches, color='k')
-
-    self.ax.add_collection(patch_collection)
-    print('Basemap Plotted')
-
-    patches = [Polygon(base_map.get_triangle_by_key(i), True, color='k') for i in range(len(base_map))]
-    return PatchCollection(patches, color='k')
-
-def load_and_predict(folder):
-    pass
-
-
-if __name__ == '__main__':
-    search_for_weights(load_and_predict, arguments.data)
-    # ToDo: THIS
diff --git a/viz/viz_prediction_in_map.py b/viz/viz_prediction_in_map.py
new file mode 100644
index 0000000..72e2975
--- /dev/null
+++ b/viz/viz_prediction_in_map.py
@@ -0,0 +1,55 @@
+from argparse import ArgumentParser
+import os
+
+from dataset import DataContainer
+from viz.utils import MotionAnalyser, Printer, MapContainer, search_for_weights
+import torch
+
+arguments = ArgumentParser()
+arguments.add_argument('--data', default='output')
+
+device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+from viz.utils import *
+from run_models import *
+
+arguments = ArgumentParser()
+arguments.add_argument('--data', default='output')
+
+device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+
+def load_and_viz(path_like_element):
+    # Define Loop to search for models and folder with visualizations
+    splitpath = path_like_element.split(os.sep)
+    base_dir = os.path.join(*splitpath[:4])
+    model = globals()[splitpath[2]]
+    print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}')
+    pretrained_model = model.load_from_metrics(
+        weights_path=path_like_element,
+        tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'),
+        on_gpu=True if torch.cuda.is_available() else False,
+        # map_location=None
+    )
+
+    # Init model and freeze its weights ( for faster inference)
+    pretrained_model = pretrained_model.to(device)
+    pretrained_model.eval()
+    pretrained_model.freeze()
+
+    dataIndex = 0
+
+    datasets = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device)
+    dataset = datasets.datasets[dataIndex]
+    # ToDO: use dataloader for iteration instead!  -  dataloader = DataLoader(dataset, )
+
+    maps = MapContainer(os.path.join(os.pardir, 'data', 'validation'))
+    base_map = maps.datasets[dataIndex]
+
+    p = Printer(pretrained_model)
+    p.print_trajec_on_basemap(dataset, base_map, save=os.path.join(base_dir, f'{base_map.name}_map.png'))
+
+
+if __name__ == '__main__':
+    args = arguments.parse_args()
+    search_for_weights(load_and_viz, args.data, file_type='map')