From 91ecf157d62f68a65ab75fff648bc3d7ce47c246 Mon Sep 17 00:00:00 2001 From: Steffen Illium Date: Thu, 13 Feb 2020 20:28:20 +0100 Subject: [PATCH] initial --- .idea/.gitignore | 2 + .idea/hom_traj_gen.iml | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + README.md | 0 build_data.py | 11 + dataset/__pycache__/dataset.cpython-37.pyc | Bin 0 -> 4152 bytes dataset/dataset.py | 96 ++++ evaluation/classification.py | 37 ++ lib/__init__.py | 0 lib/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 168 bytes lib/models/__init__.py | 0 lib/models/blocks.py | 468 ++++++++++++++++++ lib/objects/__init__.py | 0 .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 176 bytes lib/objects/__pycache__/map.cpython-37.pyc | Bin 0 -> 4503 bytes .../__pycache__/trajectory.cpython-37.pyc | Bin 0 -> 2855 bytes .../__pycache__/variables.cpython-37.pyc | Bin 0 -> 494 bytes lib/objects/map.py | 125 +++++ lib/objects/trajectory.py | 65 +++ lib/objects/variables.py | 9 + lib/utils/__init__.py | 0 lib/utils/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 174 bytes lib/utils/__pycache__/config.cpython-37.pyc | Bin 0 -> 3596 bytes lib/utils/__pycache__/logging.cpython-37.pyc | Bin 0 -> 2962 bytes lib/utils/config.py | 96 ++++ lib/utils/logging.py | 69 +++ lib/utils/model_io.py | 76 +++ lib/utils/transforms.py | 12 + main.py | 71 +++ .../__pycache__/generator.cpython-37.pyc | Bin 0 -> 3274 bytes preprocessing/generator.py | 121 +++++ res/maps/Map.bmp | Bin 0 -> 21494 bytes res/maps/doom.bmp | Bin 0 -> 198358 bytes res/maps/home.bmp | Bin 0 -> 35534 bytes res/maps/maze.bmp | Bin 0 -> 40474 bytes res/maps/oet.bmp | Bin 0 -> 253750 bytes res/maps/priz.bmp | Bin 0 -> 202678 bytes res/maps/tate.bmp | Bin 0 -> 104958 bytes res/maps/tate_sw.bmp | Bin 0 -> 68070 bytes res/maps/tum.bmp | Bin 0 -> 132550 bytes visualization/bars.py | 0 visualization/tools.py | 26 + 45 files changed, 1319 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/hom_traj_gen.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 build_data.py create mode 100644 dataset/__pycache__/dataset.cpython-37.pyc create mode 100644 dataset/dataset.py create mode 100644 evaluation/classification.py create mode 100644 lib/__init__.py create mode 100644 lib/__pycache__/__init__.cpython-37.pyc create mode 100644 lib/models/__init__.py create mode 100644 lib/models/blocks.py create mode 100644 lib/objects/__init__.py create mode 100644 lib/objects/__pycache__/__init__.cpython-37.pyc create mode 100644 lib/objects/__pycache__/map.cpython-37.pyc create mode 100644 lib/objects/__pycache__/trajectory.cpython-37.pyc create mode 100644 lib/objects/__pycache__/variables.cpython-37.pyc create mode 100644 lib/objects/map.py create mode 100644 lib/objects/trajectory.py create mode 100644 lib/objects/variables.py create mode 100644 lib/utils/__init__.py create mode 100644 lib/utils/__pycache__/__init__.cpython-37.pyc create mode 100644 lib/utils/__pycache__/config.cpython-37.pyc create mode 100644 lib/utils/__pycache__/logging.cpython-37.pyc create mode 100644 lib/utils/config.py create mode 100644 lib/utils/logging.py create mode 100644 lib/utils/model_io.py create mode 100644 lib/utils/transforms.py create mode 100644 main.py create mode 100644 preprocessing/__pycache__/generator.cpython-37.pyc create mode 100644 preprocessing/generator.py create mode 100644 res/maps/Map.bmp create mode 100644 res/maps/doom.bmp create mode 100644 res/maps/home.bmp create mode 100644 res/maps/maze.bmp create mode 100644 res/maps/oet.bmp create mode 100644 res/maps/priz.bmp create mode 100644 res/maps/tate.bmp create mode 100644 res/maps/tate_sw.bmp create mode 100644 res/maps/tum.bmp create mode 100644 visualization/bars.py create mode 100644 visualization/tools.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e7e9d11 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml diff --git a/.idea/hom_traj_gen.iml b/.idea/hom_traj_gen.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/hom_traj_gen.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8656114 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0b3a4df --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build_data.py b/build_data.py new file mode 100644 index 0000000..1528552 --- /dev/null +++ b/build_data.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from lib.objects.map import Map +from preprocessing.generator import Generator + +if __name__ == '__main__': + data_root = Path() / 'data' + maps_root = Path() / 'res' / 'maps' + map_object = Map('Tate').from_image(maps_root / 'tate_sw.bmp') + generator = Generator(data_root, map_object) + generator.generate_n_trajectories_m_alternatives(100, 10, 'test') diff --git a/dataset/__pycache__/dataset.cpython-37.pyc b/dataset/__pycache__/dataset.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06e264d17ace2f0be8a8cdee57f20f5fff4fb38a GIT binary patch literal 4152 zcmbVPTXP&o74DvyoteE!vMo!#1XqA?*$`HyD4?h^j)R>T0#=d4;W8D{HQH%OE6-li z-7@jk>II34d7^j(iYn2tnw*`GF> zHHPb7tv5gXb%U|b=wW(AVD4b(k3kAn@RDV84@%w(vVi+{VHVY zI5=UPjUo6d8#57t3mg}L3msP}7z9U~wvm@cs(x;|kEOOXgMDNNsz0%DnQzHbw_;n- z`$MTzf-P(+M7Q5HLZ}pvs!D`>2*X5LmfJQeU>LH35S=pDT8K~C7Y{df2R*r=jojXr z{f+yB!A>b(zNfJ1#xEbdzwxfrGFP45jU8{gZhuGYikUK9sW(V0>^;4=hffp`!)Rzy zh4Cd`!*8^@V5|02+YYBCxadxXyNjVoaRU?W@k8=8-^3}xQuf{1T3j_TuU2N{0yvup zffYhr#DjLO;G;{Yw{-KT+(cM*JH7-4`(a1WU#0c#EvvHBgEwe<1Ujg z)g{PNYaq5cacf7GB|tDU4y?6Ii^xU}DRnS921$7%PDA&ugU8uLw!4F&UjdoMgNp@r zHWrEmwn^<0#lU;gAkc=uNQa$-t!ce&?wkc1jbgNzJeQqY&Rdu^FinAe*>$g zfLlAW^;yi3P43b+&K|%V;hb>hb|KIvBI70`3lm{t#q}78!cH9CBpMb(hU~ zR`FRpi;G6lL>^7BYN=R0Vpx@eUnwp)z6$~py78O8di;m-Hn_1Wt? zfBzd@|CuvybtoCrMXHc(eu1GkLB^3oen`l7_z%XF59o;o%~ec1M!o}=6gY)Q$LzRz z!akrm@S=uM$7oFBriq|GRVA9IGP9+KafhfIm3R9>56iBx)yH``VQWTe<9S6OvQ-K; zp#}rvAPD*8ATMUYStI!n1i1)Ql0#foI5A*bsYmN~AxpJDvIaizbql4!<;!f<%b>64 z+uKCRt^ICUSiX?E<^ldHgy|&^7RCtin6IFsAk;5BoYdd5K~Cv_9N>Uqz!-LjX5~5> zmFw()Q$^kj4*4-sJaPX(v+_^OobUsr|KsR{=^vWPAGs?-F}cO9iD-?R9hwC1;&E^o z9E9WW7%tkYV!%y%!AXb~rvebhn>JJ^;1SHDM^SsLHv}DaA3bc)1Erj4DSKzHOiHp8{khwv7Cny%-#Dmmlk{(av?Ax5 ztV)VF7iTrFVz5R2vDJw*Y%wbx&iwYWaE@^x5QDxbWH0X*KY&Q@5R~_qc!OWVSmmP| zbBCPw*(_|SKcRuBV*1}806T-t8-~N?!EtB+`@ITA49{?~`XN8ze|h?6W)jz&1x0w2 zK4r)03A=~6Lk^!-k87q*K}23O4bMYHa}_HN!i6_W?oG7tX4$=|i~|H;_$Tyb2k`AU z97m2%z@s;FeEJ)-xbLuc*>9v3$Nc@317!Px zJqlH1*i(qO3g!M9d_*G9t5O_sSC!H91nrwPd?cS>f*0O1l$yV&-6{c(&Y(BE{RRoA zonkh6b#DE|z-`|u2c5jsx4}$5Yptjs<0JJW5F6`VSw5CF8VqIMhT2SkMlr0&MK*#G z+E%D{-nFR$0?>K(6g68m?DmbK2vy%9@)}61>N5e=Mw@i|1@%iAb+MF(x+9sTE)5Y^ z^=ZJf)BKVplk}3M)9m7o57YdFHgN^24emCErc%fNz#D+!QgD&u7c}s^#z$-Cn`GuA zsvUGfmnJB~_`Z{Z_%QHQ#8*9ElsB;A1*N3vPe&;f{qO@UoYs`N4yTrUSsiY^Scf`U zZKAWkoqY5yIn`;e%9M&#%J zLkF}yQyb{t&2-?SxekjfHd__-brMPKuKETM>IdFxHJ)pkY&ukk4`rUHuhA;kLAe+r z96qO;dYj(OQ>o(|XsKnRT{OiGdhm$xS}m=n%Z-SBisoC(>UG>~GQ?lovb*Ig1wquV zxDagbn{KJwWM~(kimF|Z~4C>w<%^^_X_ocl&?^%g)2)djsF4yhMk50 literal 0 HcmV?d00001 diff --git a/dataset/dataset.py b/dataset/dataset.py new file mode 100644 index 0000000..fb78e2f --- /dev/null +++ b/dataset/dataset.py @@ -0,0 +1,96 @@ +import shelve +from pathlib import Path + +import torch +from torch.utils.data import ConcatDataset, Dataset + +from lib.objects.map import Map +from preprocessing.generator import Generator + + +class TrajDataset(Dataset): + def __init__(self, data): + super(TrajDataset, self).__init__() + self.alternatives = data['alternatives'] + self.trajectory = data['trajectory'] + self.labels = data['labels'] + + def __len__(self): + return len(self.alternatives) + + def __getitem__(self, item): + return self.trajectory.vertices, self.alternatives[item].vertices, self.labels[item] + + +class DataSetMapping(Dataset): + def __init__(self, dataset, mapping): + self._dataset = dataset + self._mapping = mapping + + def __len__(self): + return self._mapping.shape[0] + + def __getitem__(self, item): + return self._dataset[self._mapping[item]] + + +class TrajData(object): + @property + def name(self): + return self.__class__.__name__ + + def __init__(self, data_root, mapname='tate_sw', trajectories=1000, alternatives=10, + train_val_test_split=(0.6, 0.2, 0.2), rebuild=False, equal_samples=True, **_): + + self.rebuild = rebuild + self.equal_samples = equal_samples + self._alternatives = alternatives + self._trajectories = trajectories + self.mapname = mapname + self.train_split, self.val_split, self.test_split = train_val_test_split + self.data_root = Path(data_root) + self._dataset = None + self._dataset, self._train_map, self._val_map, self._test_map = self._load_dataset() + + def _build_data_on_demand(self): + maps_root = Path() / 'res' / 'maps' + map_object = Map(self.mapname).from_image(maps_root / f'{self.mapname}.bmp') + assert maps_root.exists() + dataset_file = Path(self.data_root) / f'{self.mapname}.pik' + if dataset_file.exists() and self.rebuild: + dataset_file.unlink() + if not dataset_file.exists(): + generator = Generator(self.data_root, map_object) + generator.generate_n_trajectories_m_alternatives(self._trajectories, self._alternatives, + self.mapname, equal_samples=self.equal_samples) + return True + + def _load_dataset(self): + assert self._build_data_on_demand() + with shelve.open(str(self.data_root / f'{self.mapname}.pik')) as d: + dataset = ConcatDataset([TrajDataset(d[key]) for key in d.keys() if key != 'map']) + indices = torch.randperm(len(dataset)) + + train_size = int(len(dataset) * self.train_split) + val_size = int(len(dataset) * self.val_split) + test_size = int(len(dataset) * self.test_split) + + train_map = indices[:train_size] + val_map = indices[train_size:val_size] + test_map = indices[test_size:] + return dataset, train_map, val_map, test_map + + @property + def train_dataset(self): + return DataSetMapping(self._dataset, self._train_map) + + @property + def val_dataset(self): + return DataSetMapping(self._dataset, self._val_map) + + @property + def test_dataset(self): + return DataSetMapping(self._dataset, self._test_map) + + def get_datasets(self): + return self.train_dataset, self.val_dataset, self.test_dataset diff --git a/evaluation/classification.py b/evaluation/classification.py new file mode 100644 index 0000000..4e56c10 --- /dev/null +++ b/evaluation/classification.py @@ -0,0 +1,37 @@ +import matplotlib.pyplot as plt + +from sklearn.metrics import roc_curve, auc + + +class ROCEvaluation(object): + + BINARY_PROBLEM = 2 + linewidth = 2 + + def __init__(self, save_fig=True): + self.epoch = 0 + pass + + def __call__(self, prediction, label, prepare_fig=True): + + # Compute ROC curve and ROC area + fpr, tpr, _ = roc_curve(prediction, label) + roc_auc = auc(fpr, tpr) + + if prepare_fig: + fig = self._prepare_fig() + fig.plot(fpr, tpr, color='darkorange', + lw=2, label=f'ROC curve (area = {roc_auc})') + self._prepare_fig() + return roc_auc + + def _prepare_fig(self): + fig = plt.gcf() + fig.plot([0, 1], [0, 1], color='navy', lw=self.linewidth, linestyle='--') + fig.xlim([0.0, 1.0]) + fig.ylim([0.0, 1.05]) + fig.xlabel('False Positive Rate') + fig.ylabel('True Positive Rate') + + fig.legend(loc="lower right") + return fig diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/__pycache__/__init__.cpython-37.pyc b/lib/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3e442df27be49915ed798e9bece81afba033f8e GIT binary patch literal 168 zcmZ?b<>g`kf)DbpaUl9Jh=2h`Aj1KOi&=m~3PUi1CZpd%0aTAZ3#l$@cTo|>0hlvt9PmmZ%HUs9Bqm6}|V pUzC|zte=yaq#qxjnU`4-AFo$Xd5gm)H$SB`C)EyQ)n_1P004ePD|-L{ literal 0 HcmV?d00001 diff --git a/lib/models/__init__.py b/lib/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/models/blocks.py b/lib/models/blocks.py new file mode 100644 index 0000000..f1697b3 --- /dev/null +++ b/lib/models/blocks.py @@ -0,0 +1,468 @@ +from abc import ABC +from pathlib import Path +from typing import Union + +import torch +from torch import nn +import torch.nn.functional as F +import pytorch_lightning as pl + + +# Utility - Modules +################### +from torch.utils.data import DataLoader + +from dataset.dataset import TrajData + + +class Flatten(nn.Module): + def __init__(self, to=(-1, )): + super(Flatten, self).__init__() + self.to = to + + def forward(self, x): + return x.view(x.size(0), *self.to) + + +class Interpolate(nn.Module): + def __init__(self, size=None, scale_factor=None, mode='nearest', align_corners=None): + super(Interpolate, self).__init__() + self.interp = nn.functional.interpolate + self.size = size + self.scale_factor = scale_factor + self.align_corners = align_corners + self.mode = mode + + def forward(self, x): + x = self.interp(x, size=self.size, scale_factor=self.scale_factor, + mode=self.mode, align_corners=self.align_corners) + return x + + +class AutoPad(nn.Module): + + def __init__(self, interpolations=3, base=2): + super(AutoPad, self).__init__() + self.fct = base ** interpolations + + def forward(self, x): + x = F.pad(x, + [0, + (x.shape[-1] // self.fct + 1) * self.fct - x.shape[-1] if x.shape[-1] % self.fct != 0 else 0, + (x.shape[-2] // self.fct + 1) * self.fct - x.shape[-2] if x.shape[-2] % self.fct != 0 else 0, + 0]) + return x + + +class LightningBaseModule(pl.LightningModule, ABC): + + @classmethod + def name(cls): + raise NotImplementedError('Give your model a name!') + + @property + def shape(self): + try: + x = torch.randn(self.in_shape).unsqueeze(0) + output = self(x) + return output.shape[1:] + except Exception as e: + print(e) + return -1 + + def __init__(self, params): + super(LightningBaseModule, self).__init__() + self.hparams = params + + # Data loading + # ============================================================================= + # Dataset + self.dataset = TrajData('data') + + def size(self): + return self.shape + + def _move_to_model_device(self, x): + return x.cuda() if next(self.parameters()).is_cuda else x.cpu() + + def save_to_disk(self, model_path): + Path(model_path, exist_ok=True).mkdir(parents=True, exist_ok=True) + if not (model_path / 'model_class.obj').exists(): + with (model_path / 'model_class.obj').open('wb') as f: + torch.save(self.__class__, f) + return True + + @pl.data_loader + def train_dataloader(self): + return DataLoader(dataset=self.dataset.train_dataset, shuffle=True, + batch_size=self.hparams.data_param.batchsize, + num_workers=self.hparams.data_param.worker) + + @pl.data_loader + def test_dataloader(self): + return DataLoader(dataset=self.dataset.test_dataset, shuffle=True, + batch_size=self.hparams.data_param.batchsize, + num_workers=self.hparams.data_param.worker) + + @pl.data_loader + def val_dataloader(self): + return DataLoader(dataset=self.dataset.val_dataset, shuffle=True, + batch_size=self.hparams.data_param.batchsize, + num_workers=self.hparams.data_param.worker) + + def configure_optimizers(self): + raise NotImplementedError + + def forward(self, *args, **kwargs): + raise NotImplementedError + + def validation_step(self, *args, **kwargs): + raise NotImplementedError + + def validation_end(self, outputs): + raise NotImplementedError + + def training_step(self, batch_xy, batch_nb, *args, **kwargs): + raise NotImplementedError + + def test_step(self, *args, **kwargs): + raise NotImplementedError + + def test_end(self, outputs): + from sklearn.metrics import roc_auc_score + + y_scores, y_true = [], [] + for output in outputs: + y_scores.append(output['y_pred']) + y_true.append(output['y_true']) + + y_true = torch.cat(y_true, dim=0) + # FIXME: What did this do do i need it? + # y_true = (y_true != V.HOMOTOPIC).long() + y_scores = torch.cat(y_scores, dim=0) + + roc_auc_scores = roc_auc_score(y_true.cpu().numpy(), y_scores.cpu().numpy()) + print(f'AUC Score: {roc_auc_scores}') + return {'roc_auc_scores': roc_auc_scores} + + def init_weights(self): + def _weight_init(m): + if hasattr(m, 'weight'): + if isinstance(m.weight, torch.Tensor): + torch.nn.init.xavier_uniform_(m.weight) + if hasattr(m, 'bias'): + if isinstance(m.bias, torch.Tensor): + m.bias.data.fill_(0.01) + self.apply(_weight_init) + + +# +# Sub - Modules +################### +class ConvModule(nn.Module): + + @property + def shape(self): + x = torch.randn(self.in_shape).unsqueeze(0) + output = self(x) + return output.shape[1:] + + def __init__(self, in_shape, activation: nn.Module = nn.ELU, pooling_size=None, use_bias=True, use_norm=True, + dropout: Union[int, float] = 0, + conv_filters=64, conv_kernel=5, conv_stride=1, conv_padding=0): + super(ConvModule, self).__init__() + + # Module Paramters + self.in_shape = in_shape + in_channels, height, width = in_shape[0], in_shape[1], in_shape[2] + self.activation = activation() + + # Convolution Paramters + self.padding = conv_padding + self.stride = conv_stride + + # Modules + self.dropout = nn.Dropout2d(dropout) if dropout else False + self.pooling = nn.MaxPool2d(pooling_size) if pooling_size else False + self.norm = nn.BatchNorm2d(in_channels, eps=1e-04, affine=False) if use_norm else False + self.conv = nn.Conv2d(in_channels, conv_filters, conv_kernel, bias=use_bias, + padding=self.padding, stride=self.stride + ) + + def forward(self, x): + x = self.norm(x) if self.norm else x + + tensor = self.conv(x) + tensor = self.dropout(tensor) if self.dropout else tensor + tensor = self.pooling(tensor) if self.pooling else tensor + tensor = self.activation(tensor) + return tensor + + +class DeConvModule(nn.Module): + + @property + def shape(self): + x = torch.randn(self.in_shape).unsqueeze(0) + output = self(x) + return output.shape[1:] + + def __init__(self, in_shape, conv_filters=3, conv_kernel=5, conv_stride=1, conv_padding=0, + dropout: Union[int, float] = 0, autopad=False, + activation: Union[None, nn.Module] = nn.ReLU, interpolation_scale=None, + use_bias=True, normalize=False): + super(DeConvModule, self).__init__() + in_channels, height, width = in_shape[0], in_shape[1], in_shape[2] + self.padding = conv_padding + self.stride = conv_stride + self.in_shape = in_shape + self.conv_filters = conv_filters + + self.autopad = AutoPad() if autopad else False + self.interpolation = Interpolate(scale_factor=interpolation_scale) if interpolation_scale else False + self.norm = nn.BatchNorm2d(in_channels, eps=1e-04, affine=False) if normalize else False + self.dropout = nn.Dropout2d(dropout) if dropout else False + self.de_conv = nn.ConvTranspose2d(in_channels, self.conv_filters, conv_kernel, bias=use_bias, + padding=self.padding, stride=self.stride) + + self.activation = activation() if activation else None + + def forward(self, x): + x = self.norm(x) if self.norm else x + x = self.dropout(x) if self.dropout else x + x = self.autopad(x) if self.autopad else x + x = self.interpolation(x) if self.interpolation else x + + tensor = self.de_conv(x) + tensor = self.activation(tensor) if self.activation else tensor + return tensor + + def size(self): + return self.shape + + +# +# Full Model Parts +################### +class Generator(nn.Module): + @property + def shape(self): + x = torch.randn(self.lat_dim).unsqueeze(0) + output = self(x) + return output.shape[1:] + + # noinspection PyUnresolvedReferences + def __init__(self, out_channels, re_shape, lat_dim, use_norm=False, use_bias=True, dropout: Union[int, float] = 0, + filters: List[int] = None, activation=nn.ReLU): + super(Generator, self).__init__() + assert filters, '"Filters" has to be a list of int len 3' + self.filters = filters + self.activation = activation + self.inner_activation = activation() + self.out_activation = None + self.lat_dim = lat_dim + self.dropout = dropout + self.l1 = nn.Linear(self.lat_dim, reduce(mul, re_shape), bias=use_bias) + # re_shape = (self.lat_dim // reduce(mul, re_shape[1:]), ) + tuple(re_shape[1:]) + + self.flat = Flatten(to=re_shape) + + self.deconv1 = DeConvModule(re_shape, conv_filters=self.filters[0], + conv_kernel=5, + conv_padding=2, + conv_stride=1, + normalize=use_norm, + activation=self.activation, + interpolation_scale=2, + dropout=self.dropout + ) + + self.deconv2 = DeConvModule(self.deconv1.shape, conv_filters=self.filters[1], + conv_kernel=3, + conv_padding=1, + conv_stride=1, + normalize=use_norm, + activation=self.activation, + interpolation_scale=2, + dropout=self.dropout + ) + + self.deconv3 = DeConvModule(self.deconv2.shape, conv_filters=self.filters[2], + conv_kernel=3, + conv_padding=1, + conv_stride=1, + normalize=use_norm, + activation=self.activation, + interpolation_scale=2, + dropout=self.dropout + ) + + self.deconv4 = DeConvModule(self.deconv3.shape, conv_filters=out_channels, + conv_kernel=3, + conv_padding=1, + # normalize=use_norm, + activation=self.out_activation + ) + + def forward(self, z): + tensor = self.l1(z) + tensor = self.inner_activation(tensor) + tensor = self.flat(tensor) + tensor = self.deconv1(tensor) + tensor = self.deconv2(tensor) + tensor = self.deconv3(tensor) + tensor = self.deconv4(tensor) + return tensor + + def size(self): + return self.shape + + +class UnitGenerator(Generator): + + def __init__(self, *args, **kwargs): + kwargs.update(use_norm=True) + super(UnitGenerator, self).__init__(*args, **kwargs) + self.norm_f = nn.BatchNorm1d(self.l1.out_features, eps=1e-04, affine=False) + self.norm1 = nn.BatchNorm2d(self.deconv1.conv_filters, eps=1e-04, affine=False) + self.norm2 = nn.BatchNorm2d(self.deconv2.conv_filters, eps=1e-04, affine=False) + self.norm3 = nn.BatchNorm2d(self.deconv3.conv_filters, eps=1e-04, affine=False) + + def forward(self, z_c1_c2_c3): + z, c1, c2, c3 = z_c1_c2_c3 + tensor = self.l1(z) + tensor = self.inner_activation(tensor) + tensor = self.norm(tensor) + tensor = self.flat(tensor) + + tensor = self.deconv1(tensor) + c3 + tensor = self.inner_activation(tensor) + tensor = self.norm1(tensor) + + tensor = self.deconv2(tensor) + c2 + tensor = self.inner_activation(tensor) + tensor = self.norm2(tensor) + + tensor = self.deconv3(tensor) + c1 + tensor = self.inner_activation(tensor) + tensor = self.norm3(tensor) + + tensor = self.deconv4(tensor) + return tensor + + +class BaseEncoder(nn.Module): + @property + def shape(self): + x = torch.randn(self.in_shape).unsqueeze(0) + output = self(x) + return output.shape[1:] + + # noinspection PyUnresolvedReferences + def __init__(self, in_shape, lat_dim=256, use_bias=True, use_norm=False, dropout: Union[int, float] = 0, + latent_activation: Union[nn.Module, None] = None, activation: nn.Module = nn.ELU, + filters: List[int] = None): + super(BaseEncoder, self).__init__() + assert filters, '"Filters" has to be a list of int len 3' + + # Optional Padding for odd image-sizes + # Obsolet, already Done by autopadding module on incoming tensors + # in_shape = [x+1 if x % 2 != 0 and idx else x for idx, x in enumerate(in_shape)] + + # Parameters + self.lat_dim = lat_dim + self.in_shape = in_shape + self.use_bias = use_bias + self.latent_activation = latent_activation() if latent_activation else None + + # Modules + self.conv1 = ConvModule(self.in_shape, conv_filters=filters[0], + conv_kernel=3, + conv_padding=1, + conv_stride=1, + pooling_size=2, + use_norm=use_norm, + dropout=dropout, + activation=activation + ) + + self.conv2 = ConvModule(self.conv1.shape, conv_filters=filters[1], + conv_kernel=3, + conv_padding=1, + conv_stride=1, + pooling_size=2, + use_norm=use_norm, + dropout=dropout, + activation=activation + ) + + self.conv3 = ConvModule(self.conv2.shape, conv_filters=filters[2], + conv_kernel=5, + conv_padding=2, + conv_stride=1, + pooling_size=2, + use_norm=use_norm, + dropout=dropout, + activation=activation + ) + + self.flat = Flatten() + + def forward(self, x): + tensor = self.conv1(x) + tensor = self.conv2(tensor) + tensor = self.conv3(tensor) + tensor = self.flat(tensor) + return tensor + + +class UnitEncoder(BaseEncoder): + # noinspection PyUnresolvedReferences + def __init__(self, *args, **kwargs): + kwargs.update(use_norm=True) + super(UnitEncoder, self).__init__(*args, **kwargs) + self.l1 = nn.Linear(reduce(mul, self.conv3.shape), self.lat_dim, bias=self.use_bias) + + def forward(self, x): + c1 = self.conv1(x) + c2 = self.conv2(c1) + c3 = self.conv3(c2) + tensor = self.flat(c3) + l1 = self.l1(tensor) + return c1, c2, c3, l1 + + +class VariationalEncoder(BaseEncoder): + # noinspection PyUnresolvedReferences + def __init__(self, *args, **kwargs): + super(VariationalEncoder, self).__init__(*args, **kwargs) + + self.logvar = nn.Linear(reduce(mul, self.conv3.shape), self.lat_dim, bias=self.use_bias) + self.mu = nn.Linear(reduce(mul, self.conv3.shape), self.lat_dim, bias=self.use_bias) + + @staticmethod + def reparameterize(mu, logvar): + std = torch.exp(0.5*logvar) + eps = torch.randn_like(std) + return mu + eps*std + + def forward(self, x): + tensor = super(VariationalEncoder, self).forward(x) + mu = self.mu(tensor) + logvar = self.logvar(tensor) + z = self.reparameterize(mu, logvar) + return mu, logvar, z + + +class Encoder(BaseEncoder): + # noinspection PyUnresolvedReferences + def __init__(self, *args, **kwargs): + super(Encoder, self).__init__(*args, **kwargs) + + self.l1 = nn.Linear(reduce(mul, self.conv3.shape), self.lat_dim, bias=self.use_bias) + + def forward(self, x): + tensor = super(Encoder, self).forward(x) + tensor = self.l1(tensor) + tensor = self.latent_activation(tensor) if self.latent_activation else tensor + return tensor diff --git a/lib/objects/__init__.py b/lib/objects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/objects/__pycache__/__init__.cpython-37.pyc b/lib/objects/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27d6e98eedc519627516f832c632f414778e5849 GIT binary patch literal 176 zcmZ?b<>g`kf|)X|aUl9Jh=2h`Aj1KOi&=m~3PUi1CZpd%0aTAZ3#l$@cTo|>0hlvt9PmmZ%HUs9Bqm6}|V uUzC|zte=yaq@SMz;uq`3$7kkcmc+;F6;$5hu*uC&Da}c>16lbQh#3IIB`!h$ literal 0 HcmV?d00001 diff --git a/lib/objects/__pycache__/map.cpython-37.pyc b/lib/objects/__pycache__/map.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73bd9147da565425ddd9db265a58dcfc2f6f6527 GIT binary patch literal 4503 zcma)ATXWmS6~-Zo;##>K$gB?$=x zs0Cz+8gM34sgt~T+8@vd>9K#JPyG>{eeIJU`biN{R?#lTyy0k=WCf_khKe?2r^wW(Lpw|_l7z1OUudKiFkyjv zCU}nrwy=9FaD*e=Lo0BlCp=L*v<_HM1MQ2tp?%N|F=yyH=%$!AbOZE)STyvUcuy>e z^M@>Ger0u;xF9Yb`oX+ti%a4%dKcuPY>Ab>GjT<{ap>TLbK*^L6{jwVx5RhwJTI<^ z>v&!eZ;S8Zc~RUD@8G#C-WA`&^U_zAV+EI?q4%}@X*7JsAkVYsXcSQ$YUrY;jr*6l zb24yY9kJ3nW?0GAp+={Z4M*A+QVxkS3YJOtAC_9c>~A*K`q@CP<%R5aWx94Z%X*2t zc3Z_yD8m`iDVMvFC z;dx?gfIg5AWCb~+MKap$Btp7R5a3V-j9Z|&D1<*8SblQkzSY3 zEaR>Yqf2EoHly(8QR^AMbW=<_7v0mZBMCJ+y9?a zyuOTk{o<`t$23Tos{4g=W_t55nWU+V&NL72zD5Q=%EO+DhW)os?QbkI9r#u!sB@JZ zuP}wCBaFq~eZ>)#DXsMmftD_M&a8sCWR<+MLaPLbLpbjQ#^yZjq|rdO?Fs-Zn5(av z{lX;Pk|7~Q$)N&b(*j7%(la0-SjK5wgyH*Go0HNk=HWMmW;OT%YZOn2#ry|o3abEH z2mFYGJ$}p?*c5Dwt5qo97S>jss3YeX#t2=*Jb-0X;Lf#wDw)mGw(jmyeqzKdP6_W2O@9*eIQ{SI!-~3pcPZX9U=|Sph6lTeHy5c)0phSj@e(A$8ghsQABJD4bC?|t6?{zVYT-@+}4Fq{FJQtp7yccjB+l52096O~`1sYd7 zNt_poLYJP;`&%Ax-rX+qq9P+v$0i{>8?8@1v5bPe|lVeq=*v zM<8Gwuv0y5)dNo>rFcj1TliW-*kiWcnD~~15@MsxF0GA8s? zl!|Piy-q)a0@MYtp(ycJ8v8z|X)LR!SrK;Ob0SzkM2kf@%<}kEp?aw&i}1xM8`vnP zpa)zdXurpr%)xJenN;=i^qGzb2o!bi;?D1*DXarTyS1b)qD{wK`j%+5M9gh;M;f zwo@c=Dz(#%ljP1u`<%9;B+>Pa(NKP@RHoET;$V)NkI`sfNdPUEqKe*jDtAx$H(5nH z^lcW@hFLP|q2!{FpeO}ZiPFw@25qmZ*nW=Qpp_MUsZJq6`?S=sWygb_X3-f9==m4q z>|>XlVvNO>;r&EAQ`B_${<)X*GBXjGXNDuvbXE7D<;N&NDQW^QRjdTQo+1JO zK_*!U`>74nIqN~~u9BlXRLGZ3XAlkH1`n~7w!3jp^JuT_X?vIyf!*tLsfhwF9w3kG zs!KG@5pkzzJC#2Eh#vI?HMGT9sT~2m?qXo#E=x{}?fWDi4IsA?%J9Lw(O#j#mP+F= z)QvD4WMT(7Ce+O^+}??jYD7``*6!mZ>TD^hv!Qo|WUPvj`R1u2wu#;J9c9>6ZxBV* zr70}*oGCpAvgl_*nPTckG)Cb-nZWP~QTM3%12rKUJwK^fI})YOq-v$uh7XkMG|5!3 zJ}BzUDe?Lo@tym2)0zGnZqscnbJlWv*RT5?+C@`Ix6kVuC1-qtRtynzwKJf6P4Pp! zsN064DpA>=-MepatOy2ypCa{UYHQDgXx+df`g4J{qL8B~vIfg|U7dX0uAT%`s%y$yG{hO6BZc)9Nlh&J%t%-W91-LeO)BYdE`2TMJ literal 0 HcmV?d00001 diff --git a/lib/objects/__pycache__/trajectory.cpython-37.pyc b/lib/objects/__pycache__/trajectory.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a38c7db2ee00bdb5b0a01ab231d2ac1a35a45945 GIT binary patch literal 2855 zcmb_eTW=dh6rP#Acw}R{H1wCLh4`O1!%+8taKdRqy40r>d6uxQq)D|C7Gb*Dw6uLMR)u!fck)DN=TRDGDQvrWFHf=pxaw{w z<8UJpD#BMsj|0qg80rNOL43-I&xFM(XNTl~`Zn+uw+-(AZ&ZPG7x)VI%z6d*Dz6#d z1AdCv4PWJpq9&$9T{M=+65)+D13LXHd*28ejZlN z@CJZf*0psRZi%SKWG|wiAU^odVBCVC>Oe}eg@d*kyhGQTOgpLwWua{@6hgO^NZP*< ziNb$=-`dQ&qNNJaZi}>aH_JMSxNuv>yP|dP{-f46LWxjDo2`yWg$#>0?F5@aG14Oz zs+GhWt!xAJQmvzn-b&unRgv;Mi_=13P$*QBu>JXC6e~vpr{lrb5D%wI1kkWGiEsgf zw<}~3M?y8JoPs3-W`r5ie|g{Qzu?*YQ z={L>78-u7qe77OC0H3vXB#gGge0hi`&jaazA)eIxw1kjcBEPVwHoEsU+QZ-mKeLr9 zn(m!2#h&%-f#`i4@Np;>xI%1jQButBO~E}ig}_JSZz<01&qX37IX=rZ-FOZU%? z!5K+S06-9v)sJqG?P2vBrtaWsJ)0GqLLQf|-bp>CViky|J7E$O*(FrPSc>g0j43fD zvL0EeDm>8Z2sV$eV5nUnB{&)EwOU$Gj#)vs*fx{PoTiMkqxofS!QAG~b4E!?;hSKz3=-FFIW%Y^`{zyQyb)(hHqi)!hXcIj~MxdlF{u4xN`LOUHP zMCw;R&$?NW<#FUs-AD={Qz+rP0-HjVC7IMt5^jjZXB!!|$8IRMg@hoq;%-M;WLa(g*ieXB>MlZp_A6zj zWZRZpDCtYsDcv@E1->aOefD$#f8T-M_#m-ukSmNF*|Lok*>YgldXB~29yBkL>!Z@@ znnop;KtG9*rrj{lp(jdgD!Lj3Q4%T@1WoTab^IEv6+xInqlPN{iDo?*d#muj>Nlbt zDMdQmK55K)H%^05=lm3|Fr9_aX}SpJ^x)rRF3>vNKR*`m<8PTSiuJYT9HwjoADKmvmpK4~PD tad{btKLyj3;TMj?M3+qzb_Yx5_|r7g-qnFwu3;B6MXgTfS)JCczX872Y6k!S literal 0 HcmV?d00001 diff --git a/lib/objects/__pycache__/variables.cpython-37.pyc b/lib/objects/__pycache__/variables.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7faada2bae1a652ee08c029dbfae41942c29d40 GIT binary patch literal 494 zcmXv~%Z{5s5S;PiN3y%;Tyx367Y}yiD3OvVyKC04;bCFGVh%yTjA1ZiEWk!^&DZ3Y zxaPFKkW(O!lDfL<(A}lFzrB44I=l&1-w*`gXD|L^&<$U@D#VTmATR|SVh90+Wbhs0 z0Ri}mTw#F-cu0nLM2B?r3g9srZNYT>+--p=Y=w?tF%S%pn@QQtZz zl{e-x4P|p4aQp1;4orRc`@vs0OC`w|=89;nwMK93(y*erz7<8ci599ACtBl{+U}9L ziCt+cyVepXRgU5(eL88!r_OoGg1k?n9eSo=SnUhh&>2goPgi9hrUqd4Of6(!y8=@G57EK2F?Rn9IHshW*UT2Ma^ zV-(RN&Ha)cBQN&K+)rp&W;vq;>O7$==yicEe$M= 2: + break + + query_node = (y + ydif, x + xdif) + if graph.has_node(query_node): + graph.add_edge(idx, query_node, weight=weight) + return graph + + @classmethod + def from_image(cls, imagepath: Path): + with Image.open(imagepath) as image: + return cls(name=imagepath.name, array_like_map_representation=np.array(image)) + + def simple_trajectory_between(self, start, dest): + vertices = list(nx.shortest_path(self._G, start, dest)) + trajectory = Trajectory(vertices) + return trajectory + + def get_valid_position(self): + not_found, valid_position = True, (-9999, -9999) + while not_found: + valid_position = int(np.random.choice(self.height, 1)), int(np.random.choice(self.width, 1)) + if self._G.has_node(valid_position): + not_found = False + pass + return valid_position + + def get_trajectory_from_vertices(self, *args): + coords = list() + for start, dest in zip(args[:-1], args[1:]): + coords.extend(nx.shortest_path(self._G, start, dest)) + return Trajectory(coords) + + def get_random_trajectory(self): + start = self.get_valid_position() + dest = self.get_valid_position() + return self.simple_trajectory_between(start, dest) + + def are_homotopic(self, trajectory, other_trajectory): + if not all(isinstance(x, Trajectory) for x in [trajectory, other_trajectory]): + raise TypeError + polyline = trajectory.vertices.copy() + polyline.extend(reversed(other_trajectory.vertices)) + + img = Image.new('L', (self.height, self.width), 0) + ImageDraw.Draw(img).polygon(polyline, outline=1, fill=1) + + a = (np.array(img) * self.map_array).sum() + if a >= 1: + return False + else: + return True + + def draw(self): + fig, ax = plt.gcf(), plt.gca() + # The standard colormaps also all have reversed versions. + # They have the same names with _r tacked on to the end. + # https: // matplotlib.org / api / pyplot_summary.html?highlight = colormaps + img = ax.imshow(self.as_array, cmap='Greys_r') + return dict(img=img, fig=fig, ax=ax) diff --git a/lib/objects/trajectory.py b/lib/objects/trajectory.py new file mode 100644 index 0000000..a1cbe3a --- /dev/null +++ b/lib/objects/trajectory.py @@ -0,0 +1,65 @@ +from math import atan2 +from typing import List, Tuple, Union + +from matplotlib import pyplot as plt +from lib.objects import variables as V + + +class Trajectory(object): + + @property + def endpoints(self): + return self.start, self.dest + + @property + def start(self): + return self.vertices[0] + + @property + def dest(self): + return self.vertices[-1] + + @property + def xs(self): + return [x[1] for x in self.vertices] + + @property + def ys(self): + return [x[0] for x in self.vertices] + + @property + def as_paired_list(self): + return list(zip(self.vertices[:-1], self.vertices[1:])) + + def __init__(self, vertices: Union[List[Tuple[int]], None] = None): + assert any((isinstance(vertices, list), vertices is None)) + if vertices is not None: + self.vertices = vertices + pass + + def is_equal_to(self, other_trajectory): + # ToDo: do further equality Checks here + return self.vertices == other_trajectory.vertices + + def draw(self, highlights=True, label=None, **kwargs): + if label is not None: + kwargs.update(color='red' if label == V.HOMOTOPIC else 'green', + label='Homotopic' if label == V.HOMOTOPIC else 'Alternative') + if highlights: + kwargs.update(marker='bo') + fig, ax = plt.gcf(), plt.gca() + img = plt.plot(self.xs, self.ys, **kwargs) + return dict(img=img, fig=fig, ax=ax) + + def min_vertices(self, vertices): + vertices, last_angle = [self.start], 0 + for (x1, y1), (x2, y2) in self.as_paired_list: + current_angle = atan2(x1-x2, y1-y2) + if current_angle != last_angle: + vertices.append((x2, y2)) + last_angle = current_angle + else: + continue + if vertices[-1] != self.dest: + vertices.append(self.dest) + return self.__class__(vertices=vertices) diff --git a/lib/objects/variables.py b/lib/objects/variables.py new file mode 100644 index 0000000..c931827 --- /dev/null +++ b/lib/objects/variables.py @@ -0,0 +1,9 @@ +from pathlib import Path +_ROOT = Path('..') + +HOMOTOPIC = 0 +ALTERNATIVE = 1 + +_key_1 = 'eyJhcGlfYWRkcmVzcyI6Imh0dHBzOi8vdWkubmVwdHVuZS5haSIsImFwaV91cmwiOiJodHRwczovL3VpLm' +_key_2 = '5lcHR1bmUuYWkiLCJhcGlfa2V5IjoiZmI0OGMzNzUtOTg1NS00Yzg2LThjMzYtMWFiYjUwMDUyMjVlIn0=' +NEPTUNE_KEY = _key_1 + _key_2 diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/utils/__pycache__/__init__.cpython-37.pyc b/lib/utils/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d17a88019991c5d94e3ae14ee6448b4446bb734 GIT binary patch literal 174 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 literal 0 HcmV?d00001 diff --git a/lib/utils/__pycache__/logging.cpython-37.pyc b/lib/utils/__pycache__/logging.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3a4e2cb133ac8f12c7ae259bd761d070e71b865 GIT binary patch literal 2962 zcmb_e&5zqe6!%vWZ{lRPA538pg z^f$gNejW%P!z1rP!z^ZJ*36D<8)YYRW^Uvf+0DFJD{2|p%i6O})UmDm7HhHgk;U5F zK6E3Wbskxv{|{)gLR(*YkWQv5PxHxxVlv_4b}Tsz_qTYds+`w@FfiEWQf*f|%aQh- zA|I!d1QjiQtwxXx1a3t(vm%GP%x2D!b!bN($S(7Y+ydDEgYh=V9p;RdwlM|Bj1GPz}mIM09t$3 zd4z8+_RNCa_EzZW7UMhBL=O~rsKCR_5TU#LhmwnQ#&f0nc}+(>1$20lXX8ID5rF;| zUyY{4jE|(^<1x=i_lshZadKCrdwlfZ;g_Q?xa6@&rlSeZxrh~zO{Y|ec$X)t5Gj|V zEZrGZD$PIw>#$&0&czxi$DojHXe_5~`_BG_+S}n0+^ZeOedu`YG&<_)c4AIUw`K+7 zneLj7s(A?~E_$%RxKlTMc-3AW&-ew9qyvrR?Drb$IDX2m3)80vXZBU=8ARs-!!zp- z7YKK^LPvK>QK0Klw|D}CP}KIq{w8$A0Mv+!*r3&OPzB9)v+oU9w?KEPLAN@83MGc8 zL3tSjW+S%e5Sq+Ab^sp)Md)p5e~$~97CF`4EKYMgYR*E(VAJk|t8-v=ntfMS5Dgkc z&D1^EiY_Kd7yhvCG`$>ypezNj4xQ)DF$D1e&_~?^7@?kO9m0XqwrXJn2Y9&NZfDnr zehp>SJp`A2fmzy9!5HUzzv9lm8ko1Xd0S`brdv@k-6hShj72bLhUAkm%&d%%L~sc4A}5X3Sa1SE>_(Ai^Wlk2FukG} z7qL{9Sv=>Ar1=>$fBpQMTwN|9deB6iGs5F!N*?q1jlDRlI9N>uN#dOB@KsxTDSuLH zUV@hdM@cM6Op+q2X895x#1MS_3ck%((?S>@$5p0CD#?#O0ZGgCFO#$iWP8fz1l{E< zRq+laiuf#ppwf`Y*(tsMg`>M%#gIjim{p_ab7UxZ9b!~G0ZOt|z7#1Zlzjc|kUXrU zf+HZf5W_|)F(li#_kW~^Y{IvBlf-4ob4CH#W@th}@L93P>w$22rVw%l!D|xPMAK_C z6}EIxjK^7;bBd`}cj7Xo5UB#wkI8WQQdMR}%&06Xk#IUrGcLEn4c(FzgrC5~uYF3> zJXMqmOciw=^6*oQHt3>e(Z^3>F_9*BM!lw58C#f0wTH(Lt<#frNCIreD$4m|QLXdqibDn77}ZOD4ouCtIARoXQ$^?Fu7>4btM>f};Gb_`_QZpIA8 zCD3<3{Re2rR8asH*!x!+U}w~w%F9TJ!AC$?;5%@Roh8Eh?F_~qDusM0(nPLOsyoe~ z_Ti>b(!83Tskh=iu=F834b%a0cyR@4qad{Jg?$VQES!|;F2(q&GL!>Kzpvt~nem%6 zR^KH|$koAzMM_{fH0LOgBV%3no3l=MmMzX3xrEAf9T%CiM)P$x^h3%8*(Edj?b+_=!DA6elkiZGte*ykc4aooi literal 0 HcmV?d00001 diff --git a/lib/utils/config.py b/lib/utils/config.py new file mode 100644 index 0000000..bef0f76 --- /dev/null +++ b/lib/utils/config.py @@ -0,0 +1,96 @@ +import ast + +from argparse import Namespace +from collections import defaultdict +from configparser import ConfigParser +from pathlib import Path + + +def is_jsonable(x): + import json + try: + json.dumps(x) + return True + except TypeError: + return False + + +class Config(ConfigParser): + + # TODO: Do this programmatically; This did not work: + # Initialize Default Sections + # for section in self.default_sections: + # self.__setattr__(section, property(lambda x :x._get_namespace_for_section(section)) + + @property + def main(self): + return self._get_namespace_for_section('main') + + @property + def model(self): + return self._get_namespace_for_section('model') + + @property + def train(self): + return self._get_namespace_for_section('train') + + @property + def data(self): + return self._get_namespace_for_section('data') + + @property + def project(self): + return self._get_namespace_for_section('project') + ################################################### + + @property + def tags(self, ): + return [f'{key}: {val}' for key, val in self.serializable.items()] + + @property + def serializable(self): + return {f'{section}_{key}': val for section, params in self._sections.items() + for key, val in params.items() if is_jsonable(val)} + + @property + def as_dict(self): + return self._sections + + def _get_namespace_for_section(self, item): + return Namespace(**{key: self.get(item, key) for key in self[item]}) + + def __init__(self, **kwargs): + super(Config, self).__init__(**kwargs) + pass + + @classmethod + def read_namespace(cls, namespace: Namespace): + + space_dict = defaultdict(dict) + for key in namespace.__dict__: + section, *attr_name = key.split('_') + attr_name = '_'.join(attr_name) + value = str(namespace.__getattribute__(key)) + + space_dict[section][attr_name] = value + new_config = cls() + new_config.read_dict(space_dict) + return new_config + + def get(self, *args, **kwargs): + item = super(Config, self).get(*args, **kwargs) + try: + return ast.literal_eval(item) + except SyntaxError: + return item + except ValueError: + return item + + def write(self, filepath, **kwargs): + path = Path(filepath, exist_ok=True) + path.parent.mkdir(parents=True, exist_ok=True) + + with path.open('w') as configfile: + super().write(configfile) + + return True diff --git a/lib/utils/logging.py b/lib/utils/logging.py new file mode 100644 index 0000000..5c13116 --- /dev/null +++ b/lib/utils/logging.py @@ -0,0 +1,69 @@ +from pathlib import Path + +from pytorch_lightning.logging.base import LightningLoggerBase +from pytorch_lightning.logging.neptune import NeptuneLogger +from pytorch_lightning.logging.test_tube import TestTubeLogger + +from lib.utils.config import Config + + +class Logger(LightningLoggerBase): + + @property + def experiment(self): + if self.debug: + return self.testtubelogger.experiment + else: + return self.neptunelogger.experiment + + @property + def name(self): + return self.config.model.type + + @property + def project_name(self): + return f"{self.config.project.owner}/{self.config.project.name}" + + @property + def version(self): + return f"version_{self.config.get('main', 'seed')}" + + @property + def outpath(self): + # ToDo: Add further path modification such as dataset config etc. + return Path(self.config.train.outpath) + + def __init__(self, config: Config, debug=False): + """ + params (dict|None): Optional. Parameters of the experiment. After experiment creation params are read-only. + Parameters are displayed in the experiment’s Parameters section and each key-value pair can be + viewed in experiments view as a column. + properties (dict|None): Optional default is {}. Properties of the experiment. + They are editable after experiment is created. Properties are displayed in the experiment’s Details and + each key-value pair can be viewed in experiments view as a column. + tags (list|None): Optional default []. Must be list of str. Tags of the experiment. + They are editable after experiment is created (see: append_tag() and remove_tag()). + Tags are displayed in the experiment’s Details and can be viewed in experiments view as a column. + """ + super(Logger, self).__init__() + + self.debug = debug + self.config = config + self._testtube_kwargs = dict(save_dir=self.outpath, version=self.version, name=self.name) + self._neptune_kwargs = dict(offline_mode=not self.debug, + api_key=self.config.project.neptune_key, + project_name=self.project_name, + name=self.name, + upload_source_files=list()) + self.neptunelogger = NeptuneLogger(**self._neptune_kwargs) + self.testtubelogger = TestTubeLogger(**self._testtube_kwargs) + + def log_hyperparams(self, params): + self.neptunelogger.log_hyperparams(params) + self.testtubelogger.log_hyperparams(params) + pass + + def log_metrics(self, metrics, step_num): + self.neptunelogger.log_metrics(metrics, step_num) + self.testtubelogger.log_metrics(metrics, step_num) + pass diff --git a/lib/utils/model_io.py b/lib/utils/model_io.py new file mode 100644 index 0000000..cc83e59 --- /dev/null +++ b/lib/utils/model_io.py @@ -0,0 +1,76 @@ +from argparse import Namespace +from pathlib import Path +from natsort import natsorted +from torch import nn + + +# Hyperparamter Object +class ModelParameters(Namespace): + + _activations = dict( + leaky_relu=nn.LeakyReLU, + relu=nn.ReLU, + sigmoid=nn.Sigmoid, + tanh=nn.Tanh + ) + + @property + def model_param(self): + return self._model_param + + @property + def train_param(self): + return self._train_param + + @property + def data_param(self): + return self._data_param + + def __init__(self, model_param, train_param, data_param): + self._model_param = model_param + self._train_param = train_param + self._data_param = data_param + kwargs = vars(model_param) + kwargs.update(vars(train_param)) + kwargs.update(vars(data_param)) + super(ModelParameters, self).__init__(**kwargs) + + def __getattribute__(self, item): + if item == 'activation': + try: + return self._activations[item] + except KeyError: + return nn.ReLU + return super(ModelParameters, self).__getattribute__(item) + + +class SavedLightningModels(object): + + @classmethod + def load_checkpoint(cls, models_root_path, model, n=-1, tags_file_path=''): + assert models_root_path.exists(), f'The path {models_root_path.absolute()} does not exist!' + found_checkpoints = list(Path(models_root_path).rglob('*.ckpt')) + + found_checkpoints = natsorted(found_checkpoints, key=lambda y: y.name) + + if not tags_file_path: + tag_files = models_root_path.rglob('meta_tags.csv') + tags_file_path = list(tag_files)[0] + + return cls(weights=found_checkpoints[n], model=model, tags=tags_file_path) + + def __init__(self, **kwargs): + self.weights: str = kwargs.get('weights', '') + self.tags: str = kwargs.get('tags', '') + + self.model = kwargs.get('model', None) + assert self.model is not None + + def restore(self): + pretrained_model = self.model.load_from_metrics( + weights_path=self.weights, + tags_csv=self.tags + ) + pretrained_model.eval() + pretrained_model.freeze() + return pretrained_model \ No newline at end of file diff --git a/lib/utils/transforms.py b/lib/utils/transforms.py new file mode 100644 index 0000000..e373aaf --- /dev/null +++ b/lib/utils/transforms.py @@ -0,0 +1,12 @@ +import numpy as np + + +class AsArray(object): + def __init__(self, width, height): + self.width = width + self.height = height + + def __call__(self, x): + array = np.zeros((self.width, self.height)) + + return array diff --git a/main.py b/main.py new file mode 100644 index 0000000..63da624 --- /dev/null +++ b/main.py @@ -0,0 +1,71 @@ +# Imports +# ============================================================================= +import os +from distutils.util import strtobool +from pathlib import Path +from argparse import ArgumentParser + +import warnings + +from pytorch_lightning import Trainer +from torch.utils.data import DataLoader + +from dataset.dataset import TrajData +from lib.utils.config import Config +from lib.utils.logging import Logger + +warnings.filterwarnings('ignore', category=FutureWarning) +warnings.filterwarnings('ignore', category=UserWarning) + +_ROOT = Path(__file__).parent + +# Paramter Configuration +# ============================================================================= +# Argument Parser +main_arg_parser = ArgumentParser(description="parser for fast-neural-style") + +# Main Parameters +main_arg_parser.add_argument("--main_debug", type=strtobool, default=False, help="") +main_arg_parser.add_argument("--main_eval", type=strtobool, default=False, help="") +main_arg_parser.add_argument("--main_seed", type=int, default=69, help="") + +# Data Parameters +main_arg_parser.add_argument("--data_worker", type=int, default=10, help="") +main_arg_parser.add_argument("--data_batchsize", type=int, default=100, help="") +main_arg_parser.add_argument("--data_root", type=str, default='../data/rpoot', help="") + +# Transformations +main_arg_parser.add_argument("--transformations_to_tensor", type=strtobool, default=False, help="") + +# Transformations +main_arg_parser.add_argument("--train_outpath", type=str, default="output", help="") +main_arg_parser.add_argument("--train_version", type=strtobool, required=False, help="") +main_arg_parser.add_argument("--train_epochs", type=int, default=10, help="") +main_arg_parser.add_argument("--train_batch_size", type=int, default=512, help="") +main_arg_parser.add_argument("--train_lr", type=float, default=0.002, help="") + +# Model +main_arg_parser.add_argument("--model_type", type=str, default="LeNetAE", help="") +main_arg_parser.add_argument("--model_activation", type=str, default="relu", help="") +main_arg_parser.add_argument("--model_filters", type=str, default="[32, 16, 4]", help="") +main_arg_parser.add_argument("--model_use_bias", type=strtobool, default=True, help="") +main_arg_parser.add_argument("--model_use_norm", type=strtobool, default=True, help="") +main_arg_parser.add_argument("--model_dropout", type=float, default=0.00, help="") + +# Project +main_arg_parser.add_argument("--project_name", type=str, default='traj-gen', help="") +main_arg_parser.add_argument("--project_owner", type=str, default='si11ium', help="") +main_arg_parser.add_argument("--project_neptune_key", type=str, default=os.getenv('NEPTUNE_KEY'), help="") + +# Parse it +args = main_arg_parser.parse_args() +config = Config.read_namespace(args) + +# Trainer loading +# ============================================================================= +trainer = Trainer(logger=Logger(config, debug=True)) + + +if __name__ == '__main__': + print(next(iter(train_dataloader))) + pass diff --git a/preprocessing/__pycache__/generator.cpython-37.pyc b/preprocessing/__pycache__/generator.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bcc2ebe6d5d85b892ebab431cf2bd8959845433 GIT binary patch literal 3274 zcma)8NpBp-6|SmYre~+9U2^1!;24;|7%D+F1ccz&QltPfCDSxy4D2+TQ#E7{d%8!} zHIhj5TucFC4n&r#BVc&UAIQ(h$%mj$0dmS;@G0Nxp(xT2kaVM}UhVI_?=AIFvsq*K z{Kt2H|Fbq@|EA9C&jIlvN?DaiPYn;R%SNfHbc9URS z?N>|MOKRhKzh2UQ(ik`UP0sEyS&_jDCWC#^Z%O_c>sJ4RyVzQn+qzW4Xqp%q4-C5Q zqsVNd^*&GIECt~k9i>|el=De68PK-Zp9|tcl>7@+s$?1r^DPc`+tQKl3*L9-lJsQ- zlqb(AUsXnd42I$=lhwa+S(Eh_UcV}rF;i3Za=d}@rfgxnAp@{_-d69aRO!fM+BULO zg_Fn(w&QebV?Z3xN3B5l7$v`ks&EPxvO=KdMihLXgMt!q9$)La*58cNNbg!_98EL< zV5>4RQK+-bVA{=9GSqd9_bXTtW}72jMelD+H_?3e<;r$8Rx7zt!=Xx7?q%6lqB?hU z{8X(x_~i4Ib(O0~54Kmf7Mz3Ga65!1M`{2uVwJBFciT#7?+hkf;nnOCrp zm^(*o=7VwzA8K->bLf02W|dh`xO30=7_Ah6bPt^)&e@X-k6GbDdEVOIF-oVAfv$4vC())#@@~btNt_$)fo)B$VlQrC zKTJ<%<2Q=iwU>`Is=_opdr~x+s8oUnGUTdm&>=jve5^^0{TnAdK>Ojd*leHO&qIBl zF26w4MXCttMb!dj-#c~l$yqpRyf|ke&=suA>1sl3nb-IQeueM#-r)BC971mCXHzqo zniFsdSW-?2?tKsiA29&t0!~Nr9ie*wlh4I5tbFbi1g&@E%qnSMTq*YX*Srw=P9f%A zQ6X513LtgXG#$QoeVj?v8EmV;4lJ_sOr@stOlRp_^eKq5}VHzypK1f5hN*{m1OgXr&yTJjN+QjgjN_&bJlvkTGZzCBZk>q zvlb+2%$r56Xdyu3oT70g^tX%tyow$fO|!vf%}4CXw}=VNKXLsYZm8kz*7w$VCp&yl ztul`M9eW(?`DfwGTe5n@L9Z818Waw6SA8XB4tQvfmXI2d91aC%=Gb_LxcbDO+b>u)-_+p@35?ukCdnMH=A?!K!nUtny}OJk3pKQ*{)A2S)d5 zA9OaSaFQtRSl%1JF3OJ*5m^g`ll*;DTdZJT-I#F$2gUuuI0w!FKftYraNR@kXYqt$ ztLQp!JVE~m8@mGOH`4Gb>+d`Rks~*W@x)e+W3c`)R%uFjSSotA^bd%1%gve`>Xd(# z;fx~LcePcDwS#Otx&2f0b7C0T<>znlinBqsx0+;wD9LYwczwbdu_+IiVp9|?#cs?X z=$sv~g^;D^(pv&Db%nl#WecA9g547)^-nOhin8F2Y0_?2Y^?3My-B>Y0eJ)U9884S zj;%~0jc^6OwE>c<$p%>hWtO454C)lm`}I?iX%WL*%I#pIOgL_ttghG=!dV_~CMqP< zbKCk&o>K_R(lT)AOE^pSP+9NOshT2Ml@BCMNLzP=5UEL_`YQFEM5WfE$jLq@X>(xUaTdM8nK>HhET-M{-ATMTP z-NpWXyBrA9%!I>iiUjMGDcka)t)%K%2-y~(zTN7`;522$4^VPST#Vo3Ekxoa(GWGh zD9cX3TgaCI4@8S!0*&_$evCRbtoHdezQo@_-{qprtMBu@AO25!?eYzTa!LVRvB+`& zo4RGj>o)17MW~0Nt%U^l6sU)`5r$8uQL>oPzoPYcvon2zDl+jBe*J5rN(a0{6eWjJ z(o@;#jX75^OS;I()&k*+nqLd9H8hQP+qOLh4Dm5GNWY64?qX7Zgv!=o(Fxv;u)@4F zFS2>Foy41q*Q;-KC$x>Yv#xm}$K_MXwy~mz*DJQ>J$mcXYqGrbZCu)5b)kgY len(hom_dict[0]): + if counter > len(hom_dict): + counter = len(hom_dict) + if counter in hom_dict: + if len(hom_dict[counter]) == 0: + del hom_dict[counter] + else: + del hom_dict[counter][-1] + counter -= 1 + return hom_dict diff --git a/res/maps/Map.bmp b/res/maps/Map.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cae2af8476091f34605b67c2b6022c205f8a5f2f GIT binary patch literal 21494 zcmeHKJCYJnkTyGhpZlYvEuFsZvRLDXB`` zGtf0S9;>05PvQXjlVSzyTfzK-^w|i5k`b7I1(E0uYtM^E;)1^yC&ue>@O? zsGL@S1`J>U2Y4U=F~2sWVGUpb2Y4U=F~3r)VGUpb2Y4U=F~4pc;+z32-~bN*xQn0ld!RpY3g*m7l+b zV>PIJHod10^DdvW^Ajeo@09TP{hl5_`{FqzTzdOAe7dg5=O=yLAOGC7-{Ub7WYeTchM?lHg|GAMG@{eRK6YVP0`p-Ju|(W<7gwxnbN1phr#pp zN-6j2mF&@9%$sZvujKe=IAAe<=wBrk^H^CP#yO6L;}-LW{#9ZzZ>ENG9IcLfbN$lG z-#LqYOunC<|NS6Oy!4)a1ZJNm&T+I2yY}05%IoQ({{P=Ht!tw23HzngA9B_y zYVtma^o{O0`@Gt%=*AP@)^@IF@o{|Be&48<&rDGsIOcO^t4@n=6LKSaJXsRYs~_S% z&;=>WuFpW#OK;uFp~DM98zqMQ@*KV{TzghTdKZVX-V`OmknJ*yq37j$~KsSlcYzcCWm{qHkIbV z7Iv{i!)p_R-j+xsTeN8`;p((E$Xx8AD9CoDW3Mk};wf(&G-+EE$FvSudq>kOgl_ks$fY zK(vxGWA}iJ8M5TnTO~V^E)>kH;SG;;Fim5sZqA3s*DFe)6aUg z4mp3P6eCoW6s#oBp}B|3f;Mr9U3i?NXd6-bX7eVwOK(}>zMK+H>?hxEeWG#=POi#q z8HLvDVn97`b7mta9c^YUmIGUMmimg;K$;xb{BqujfS@K4o*pfH4woL1*^ipi4 zl1Wx(Dln2ZE{9BL~<#Rg(F`Q=)_E*^3jaFbwb}Na>i%Zd%l86RX+S=K~$d5 zMT*$RJiZTtf14`@PZ+W+{aGss77fh1^orHccPH;jc5ObbSN2N3|GW)TRtX+9bjz~z zhsN&PQ1(iHqPIThWagOSVMAS(r9U)|dZA4n&Fo=Lrr+Do ze=N7iXg2S)#$Cw&xISNe)m5ENU>9C`?|Vmet<~uk+J)DCul?4mlAQb2@$TJ1OZj(W zPwf}?BALd-sjqH?YWn?F4pVX}oXzyLT5Em)>_@Z64SbTn^YXUD#zy^|IFn Ot_@rpxHfS22L1)-?GI=G literal 0 HcmV?d00001 diff --git a/res/maps/doom.bmp b/res/maps/doom.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bfbbb542b43eef445e83d78177d71c3a526ffe2e GIT binary patch literal 198358 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 literal 0 HcmV?d00001 diff --git a/res/maps/home.bmp b/res/maps/home.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b196a05534471d2612831997bbf17d3341bba4d2 GIT binary patch literal 35534 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 UB6m{xhR2k_l++E4fB%;L5BihS9{>OV literal 0 HcmV?d00001 diff --git a/res/maps/maze.bmp b/res/maps/maze.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b6e4fe8c357cf74486deee74b3fd0ec4092ee7bc GIT binary patch literal 40474 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 T6X+A@6X+A@6X+ATn!tYmW#ueM literal 0 HcmV?d00001 diff --git a/res/maps/oet.bmp b/res/maps/oet.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c93c030701955247a24ab5010f127bf52be038e5 GIT binary patch literal 253750 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 qc>?DNoF{OezwC-6Kc@c#j93db)1 literal 0 HcmV?d00001 diff --git a/res/maps/priz.bmp b/res/maps/priz.bmp new file mode 100644 index 0000000000000000000000000000000000000000..99fb8589ea03a1f5fad4599c87f3cc71439621fd GIT binary patch literal 202678 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 F{{i|5nOXn< literal 0 HcmV?d00001 diff --git a/res/maps/tate_sw.bmp b/res/maps/tate_sw.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d55d73c387ff83d21ecb4704b846dff0f25a4a90 GIT binary patch literal 68070 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 am=l;2m=l;2m=l;2m=l;2m=pL?6Zn5o2+^Ma literal 0 HcmV?d00001 diff --git a/res/maps/tum.bmp b/res/maps/tum.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5effbf698e4076fe0eace9c399db6a99f5c35fa9 GIT binary patch literal 132550 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 L+`zd3wSoTzxVA6w literal 0 HcmV?d00001 diff --git a/visualization/bars.py b/visualization/bars.py new file mode 100644 index 0000000..e69de29 diff --git a/visualization/tools.py b/visualization/tools.py new file mode 100644 index 0000000..8ba6651 --- /dev/null +++ b/visualization/tools.py @@ -0,0 +1,26 @@ +from pathlib import Path +import matplotlib.pyplot as plt + + +class Plotter(object): + def __init__(self, root_path=''): + self.root_path = Path(root_path) + + def save_current_figure(self, path, extention='.png'): + fig, _ = plt.gcf(), plt.gca() + # Prepare save location and check img file extention + path = self.root_path / Path(path if str(path).endswith(extention) else f'{str(path)}{extention}') + path.parent.mkdir(exist_ok=True, parents=True) + fig.savefig(path) + fig.clf() + + def show_current_figure(self): + fig, _ = plt.gcf(), plt.gca() + fig.show() + fig.clf() + + +if __name__ == '__main__': + output_root = Path('..') / 'output' + p = Plotter(output_root) + p.save_current_figure('test.png')