diff --git a/Dockerfile b/Dockerfile
index 72e148d5..4ee3ae2e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,37 +1,62 @@
-# tagged aloception-oss:cuda-11.3.1-pytorch1.10.1-lightning1.4.1
-
FROM nvidia/cuda:11.3.1-cudnn8-devel-ubuntu20.04
-#FROM nvidia/cuda:11.6.0-cudnn8-devel-ubuntu20.04
+#ARG py=3.9
+#ARG pytorch=2.1.0.dev20230313+cu117
+#ARG torchvision=0.15.0.dev20230313+cu117
+#ARG torchaudio=2.0.0.dev20230313+cu117
+#ARG pytorch_lightning=1.9.3
+#ARG pycyda=11.7
ARG py=3.9
ARG pytorch=1.13.1
ARG torchvision=0.14.1
ARG torchaudio=0.13.1
-ARG pytorch_lightning=1.9.0
+ARG pytorch_lightning=1.9.3
ARG pycyda=11.7
+
+ARG HOME=/home/aloception
+
ENV TZ=Europe/Paris
ENV DEBIAN_FRONTEND=noninteractive
-RUN apt-get update
+RUN apt-get -y update; apt-get -y install sudo
+
RUN apt-get install -y build-essential nano git wget libgl1-mesa-glx
-# Usefull for scipy
-RUN apt-get install -y gfortran
-# required for aloscene
-RUN apt-get install -y libglib2.0-0
+# Usefull for scipy / required for aloscene
+RUN apt-get install -y gfortran libglib2.0-0
+
+# Create aloception user
+RUN useradd --create-home --uid 1000 --shell /bin/bash aloception && usermod -aG sudo aloception && echo "aloception ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
+ENV HOME /home/aloception
+WORKDIR /home/aloception
-RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
-RUN bash Miniconda3-latest-Linux-x86_64.sh -b -p /miniconda
-ENV PATH=$PATH:/miniconda/condabin:/miniconda/bin
+
+RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && \
+ /bin/bash /tmp/miniconda.sh -b -p /opt/miniconda && \
+ rm /tmp/miniconda.sh
+ENV CONDA_HOME /opt/miniconda
+ENV PATH ${CONDA_HOME}/condabin:${CONDA_HOME}/bin:${PATH}
RUN /bin/bash -c "source activate base"
-ENV HOME /workspace
-WORKDIR /workspace
+
+# The following so that any user can install packages inside this Image
+RUN chmod -R o+w /opt/miniconda && chmod -R o+w /home/aloception
+
+USER aloception
# Pytorch & pytorch litning
+#RUN conda install py pytorch-cuda=${pycuda} -c pytorch -c nvidia
+#RUN pip install --pre torch==${pytorch} torchvision==${torchvision} torchaudio==${torchaudio} --index-url https://download.pytorch.org/whl/nightly/cu117
+#RUN pip install pytorch_lightning==${pytorch_lightning}
RUN conda install pytorch==${pytorch} torchvision==${torchvision} torchaudio==${torchaudio} pytorch-cuda=${pycuda} -c pytorch -c nvidia
RUN pip install pytorch_lightning==${pytorch_lightning}
-COPY requirements-torch1.13.1.txt /install/requirements-torch1.13.1.txt
-RUN pip install -r /install/requirements-torch1.13.1.txt
+
+COPY --chown=aloception:aloception requirements/requirements-torch1.13.1.txt /home/aloception/install/requirements-torch1.13.1.txt
+RUN pip install -r /home/aloception/install/requirements-torch1.13.1.txt
+COPY --chown=aloception:aloception ./aloscene/utils /home/aloception/install/utils
+
+USER root
+COPY entrypoint.sh /entrypoint.sh
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/README.md b/README.md
index 6e295777..1ccc158d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Documentation
-[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-0.5.1-green.svg)](https://conventionalcommits.org)
+[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-0.6.0-green.svg)](https://conventionalcommits.org)
# Aloception open source software
@@ -78,17 +78,17 @@ training pipelines with **augmented tensors**.
### Docker install
```
-docker build -t aloception-oss:cuda-11.3.1-pytorch1.13.1-lightning1.9.0 .
+docker build -t aloception-oss:cuda-11.3-pytorch1.13.1-lightning1.9.3 .
```
```
-docker run --gpus all -it -v /YOUR/WORKSPACE/:/workspace --privileged -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix aloception-oss:cuda-11.3.1-pytorch1.13.1-lightning1.9.0
+docker run -e LOCAL_USER_ID=$(id -u) --gpus all -it -v /YOUR/WORKSPACE/:/workspace --privileged -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix aloception-oss:cuda-11.3-pytorch1.13.1-lightning1.9.3
```
Or without building the image
```
-docker run --gpus all -it -v /YOUR/WORKSPACE/:/workspace --privileged -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix visualbehaviorofficial/aloception-oss:cuda-11.3.1-pytorch1.13.1-lightning1.9.0
+docker run -e LOCAL_USER_ID=$(id -u) --gpus all -it -v /YOUR/WORKSPACE/:/workspace --privileged -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix visualbehaviorofficial/aloception-oss:cuda-11.3-pytorch1.13.1-lightning1.9.3
```
diff --git a/alodataset/__init__.py b/alodataset/__init__.py
index f3d81dc4..697de3dc 100644
--- a/alodataset/__init__.py
+++ b/alodataset/__init__.py
@@ -1,3 +1,5 @@
+from . import utils
+from . import prepare
from .base_dataset import BaseDataset, Split
from .sequence_mixin import SequenceMixin
from .split_mixin import SplitMixin
@@ -17,3 +19,6 @@
from .from_directory_dataset import FromDirectoryDataset
from .woodScape_dataset import WooodScapeDataset
from .woodScape_split_dataset import WoodScapeSplitDataset
+
+import pkg_resources
+__version__ = pkg_resources.get_distribution("aloception").version
diff --git a/alodataset/io/__init__.py b/alodataset/io/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/alodataset/prepare/__init__.py b/alodataset/prepare/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/alodataset/utils/__init__.py b/alodataset/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/alonet/__init__.py b/alonet/__init__.py
index d0275b60..13be1cc5 100644
--- a/alonet/__init__.py
+++ b/alonet/__init__.py
@@ -10,3 +10,6 @@
from . import detr_panoptic
from . import deformable_detr_panoptic
+
+import pkg_resources
+__version__ = pkg_resources.get_distribution("aloception").version
diff --git a/alonet/common/base_datamodule.py b/alonet/common/base_datamodule.py
new file mode 100644
index 00000000..60780276
--- /dev/null
+++ b/alonet/common/base_datamodule.py
@@ -0,0 +1,177 @@
+import pytorch_lightning as pl
+import alonet
+import torch
+from torch.utils.data.sampler import RandomSampler, SequentialSampler
+
+
+class BaseDataModule(pl.LightningDataModule):
+ """
+ Base class for all data modules.
+ """
+
+ def __init__(
+ self, args, **kwargs,
+ ):
+ super().__init__()
+ alonet.common.pl_helpers.params_update(self, args, kwargs)
+
+ @staticmethod
+ def add_argparse_args(parent_parser):
+ parser = parent_parser.add_argument_group("BaseDataModule")
+ parser.add_argument("--batch_size", type=int, default=5, help="Batch size (Default: %(default)s)")
+ parser.add_argument(
+ "--num_workers", type=int, default=8, help="num_workers to use on the dataset (Default: %(default)s)"
+ )
+ parser.add_argument("--sequential_sampler", action="store_true", help="sample data sequentially (no shuffle)")
+ parser.add_argument(
+ "--sample", action="store_true", help="Download a sample for train/val process (Default: %(default)s)"
+ )
+ parser.add_argument("--train_on_val", action="store_true", help="Train on validation set (Default: %(default)s)")
+
+ parser.add_argument("--no_aug", action="store_true", help="Disable data augmentation (Default: %(default)s)")
+ return parent_parser
+
+ @property
+ def train_dataset(self):
+ if not hasattr(self, "_train_dataset"):
+ self.setup()
+ return self._train_dataset
+
+ @train_dataset.setter
+ def train_dataset(self, new_dataset):
+ self._train_dataset = new_dataset
+
+ @property
+ def val_dataset(self):
+ if not hasattr(self, "_val_dataset"):
+ self.setup()
+ return self._val_dataset
+
+ @val_dataset.setter
+ def val_dataset(self, new_dataset):
+ self._val_dataset = new_dataset
+
+ @property
+ def test_dataset(self):
+ if not hasattr(self, "_test_dataset"):
+ self.setup()
+ return self._test_dataset
+
+ @test_dataset.setter
+ def test_dataset(self, new_dataset):
+ self._test_dataset = new_dataset
+
+ def train_transform(self, frames, **kwargs):
+ """
+ A structure to select the train transform function.
+ Parameters
+ ----------
+ frames : aloscene.Frame
+ Input frames
+ Returns
+ -------
+ aloscene.Frame
+ """
+ if self.no_aug:
+ return self._train_transform_no_aug(frames)
+ else:
+ return self._train_transform_aug(frames, **kwargs)
+
+ def _train_transform_no_aug(self, frames):
+ """
+ Train_transform with no data augmentation.
+ Parameters
+ ----------
+ frames : aloscene.Frame
+ Input frames
+ Returns
+ -------
+ aloscene.Frame
+ """
+
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def _train_transform_aug(self, frames):
+ """
+ Train_transform with data augmentation.
+ Parameters
+ ----------
+ frames : aloscene.Frame
+ Input frames
+ Returns
+ -------
+ aloscene.Frame
+ """
+
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def val_transform(self, frames, **kwargs):
+ """
+ Val transform.
+ Parameters
+ ----------
+ frames : aloscene.Frame
+ Input frames
+ Returns
+ -------
+ aloscene.Frame
+ """
+
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def setup(self, stage=None):
+ """:attr:`train_dataset`, :attr:`val_dataset`, attr:`test_dataset` datasets setup
+ Parameters
+ ----------
+ stage : str, optional
+ Stage either `fit`, `validate`, `test` or `predict`, by default None"""
+
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def train_dataloader(self, sampler: torch.utils.data = None):
+ """Get train dataloader
+ Parameters
+ ----------
+ sampler : torch.utils.data, optional
+ Sampler to load batches, by default None
+ Returns
+ -------
+ torch.utils.data.DataLoader
+ Dataloader for training process
+ """
+ if sampler is None:
+ sampler = RandomSampler if not self.sequential_sampler else SequentialSampler
+
+ return self.train_dataset.train_loader(batch_size=self.batch_size, num_workers=self.num_workers, sampler=sampler)
+
+ def val_dataloader(self, sampler: torch.utils.data = None):
+ """Get val dataloader
+ Parameters
+ ----------
+ sampler : torch.utils.data, optional
+ Sampler to load batches, by default None
+ Returns
+ -------
+ torch.utils.data.DataLoader
+ Dataloader for validation process
+ """
+ if sampler is None:
+ sampler = SequentialSampler
+
+ return self.val_dataset.train_loader(batch_size=self.batch_size, num_workers=self.num_workers, sampler=sampler)
+
+ def test_dataloader(self, sampler: torch.utils.data = None):
+ """Get test dataloader
+ Parameters
+ ----------
+ sampler : torch.utils.data, optional
+ Sampler to load batches, by default None
+ Returns
+ -------
+ torch.utils.data.DataLoader
+ Dataloader for inference process
+ """
+ if sampler is None:
+ sampler = SequentialSampler
+
+ return self.test_dataset.train_loader(batch_size=self.batch_size, num_workers=self.num_workers, sampler=sampler)
diff --git a/alonet/common/base_lightningmodule.py b/alonet/common/base_lightningmodule.py
new file mode 100644
index 00000000..756a5070
--- /dev/null
+++ b/alonet/common/base_lightningmodule.py
@@ -0,0 +1,261 @@
+import pytorch_lightning as pl
+from argparse import Namespace, ArgumentParser
+import torch
+import alonet
+from typing import Union
+import aloscene
+
+
+class BaseLightningModule(pl.LightningModule):
+ """
+ Parameters
+ ----------
+ args : Namespace, optional
+ Attributes stored in specific Namespace, by default None
+ weights : str, optional
+ Weights name to load, by default None
+ gradient_clip_val : float, optional
+ pytorch_lightning.trainer.trainer parameter. 0 means don’t clip, by default 0.1
+ accumulate_grad_batches : int, optional
+ Accumulates grads every k batches or as set up in the dict, by default 4
+ model_name : str, optional
+ Name use to define the model, by default "detr-r50"
+ model : torch.nn, optional
+ Custom model to train
+ Notes
+ -----
+ Arguments entered by the user (kwargs) will replace those stored in args attribute
+ """
+
+ def __init__(self, args: Namespace = None, model: torch.nn = None, **kwargs):
+ super().__init__()
+ # Update class attributes with args and kwargs inputs
+ alonet.common.pl_helpers.params_update(self, args, kwargs)
+ self._init_kwargs_config.update({"model": model})
+
+ self.model = self.build_model(weights=self.weights)
+ self.criterion = self.build_criterion()
+
+ @staticmethod
+ def add_argparse_args(parent_parser, parser=None):
+ """Add arguments to parent parser with default values
+ Parameters
+ ----------
+ parent_parser : ArgumentParser
+ Object to append new arguments
+ parser : ArgumentParser.argument_group, optional
+ Argument group to append the parameters, by default None
+ Returns
+ -------
+ ArgumentParser
+ Object with new arguments concatenated
+ """
+ parser = parent_parser.add_argument_group("BaseLightningModule") if parser is None else parser
+ parser.add_argument("--weights", type=str, default=None, help="One of (detr-r50). (Default: %(default)s)")
+
+ return parent_parser
+
+ def configure_optimizers(self):
+ """AdamW default optimizer configuration, using different learning rates for backbone and others parameters
+ Returns
+ -------
+ torch.optim
+ `AdamW `_ optimizer to update weights
+ """
+ param_dicts = [
+ {"params": [p for n, p in self.model.named_parameters() if p.requires_grad]},
+ ]
+ optimizer = torch.optim.AdamW(param_dicts, lr=1e-4, weight_decay=1e-4)
+ return optimizer
+
+ def build_model(self, weights):
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def build_criterion(self):
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def forward(self, frames: Union[list, aloscene.Frame], **kwargs):
+ """Run a forward pass through the model.
+ Parameters
+ ----------
+ frames : Union[list, :mod:`Frames `]
+ List of :mod:`~aloscene.frame` without batch dimension or a Frame with the batch dimension
+ Returns
+ -------
+ m_outputs: dict
+ A dict with the forward outputs.
+ """
+ # Batch list of frame if needed
+ if isinstance(frames, list):
+ frames = aloscene.Frame.batch_list(frames)
+
+ # Assert inputs content
+ self.assert_input(frames, inference=True)
+
+ m_outputs = self.model(frames)
+
+ return m_outputs
+
+ def inference(self, m_outputs: dict, frames: aloscene.Frame, **kwargs):
+ """Given the model forward outputs, run inference.
+ Parameters
+ ----------
+ m_outputs: dict
+ Dict with the model forward outptus
+ """
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def training_step(self, frames: Union[list, aloscene.Frame], batch_idx: int):
+ """Train the model for one step
+ Parameters
+ ----------
+ frames : Union[list, :mod:`Frames `]
+ List of :mod:`~aloscene.frame` without batch dimension or a Frame with the batch dimension
+ batch_idx : int
+ Batch id given by Lightning
+ Returns
+ -------
+ dict
+ Dictionary with the :attr:`loss` to optimize, :attr:`m_outputs` forward outputs and :attr:`metrics` to log.
+ """
+ if isinstance(frames, list):
+ frames = aloscene.Frame.batch_list(frames)
+
+ # Assert inputs content
+ self.assert_input(frames)
+
+ m_outputs = self.model(frames)
+
+ loss, metrics = self.criterion(frames, m_outputs)
+
+ outputs = {"loss": loss}
+ outputs.update({"m_outputs": m_outputs})
+ outputs.update({"metrics": metrics})
+
+ return outputs
+
+ @torch.no_grad()
+ def validation_step(self, frames: Union[list, aloscene.Frame], batch_idx: int):
+ """Run one step of validation
+ Parameters
+ ----------
+ frames : Union[list, :mod:`Frames `]
+ List of :mod:`~aloscene.frame` without batch dimension or a Frame with the batch dimension
+ batch_idx : int
+ Batch id given by Lightning
+ Returns
+ -------
+ dict
+ Dictionary with the :attr:`loss` to optimize, and :attr:`metrics` to log.
+ """
+ # Batch list of frame if needed
+ with torch.no_grad():
+ if isinstance(frames, list):
+ frames = aloscene.Frame.batch_list(frames)
+
+ # Assert inputs content
+ self.assert_input(frames)
+
+ m_outputs = self.model(frames)
+
+ loss, metrics = self.criterion(frames, m_outputs)
+
+ outputs = {"loss": loss}
+ outputs.update({"metrics": metrics})
+
+ return outputs
+
+ def assert_input(self, frames: aloscene.Frame, inference=False):
+ """Check if input-frames have the correct format
+ Parameters
+ ----------
+ frames : :mod:`Frames `
+ Input frames
+ inference : bool, optional
+ Check input from inference procedure, by default False
+ """
+ raise NotImplementedError("Should be implemented in child class.")
+
+ def callbacks(self,):
+ """Given a data loader, this method will return the default callbacks of the training loop.
+ Returns
+ -------
+ List[:doc:`alonet.callbacks`]
+ Callbacks use in train process
+ """
+
+ metrics_callback = alonet.callbacks.MetricsCallback()
+ ap_metrics_callback = alonet.callbacks.ApMetricsCallback()
+ return [metrics_callback, ap_metrics_callback]
+
+ def run_train(
+ self,
+ data_loader: torch.utils.data.DataLoader,
+ args: Namespace = None,
+ project: str = None,
+ expe_name: str = None,
+ callbacks: list = None,
+ ):
+ """Train the model using pytorch lightning
+ Parameters
+ ----------
+ data_loader : torch.utils.data.DataLoader
+ Dataloader use in :func:`callbacks` function
+ project : str, optional
+ Project name using to save checkpoints, by default None
+ expe_name : str, optional
+ Specific experiment name to save checkpoints, by default None
+ callbacks : list, optional
+ List of callbacks to use, by default :func:`callbacks` output
+ args : Namespace, optional
+ Additional arguments use in training process, by default None
+ """
+ # Set the default callbacks if not provide.
+ callbacks = callbacks if callbacks is not None else self.callbacks(data_loader)
+
+ alonet.common.pl_helpers.run_pl_training(
+ # Trainer, data & callbacks
+ lit_model=self,
+ data_loader=data_loader,
+ callbacks=callbacks,
+ # Project info
+ args=args,
+ project=project,
+ expe_name=expe_name,
+ )
+
+ def run_validation(
+ self,
+ data_loader: torch.utils.data.DataLoader,
+ args: Namespace = None,
+ project: str = None,
+ expe_name: str = None,
+ callbacks: list = None,
+ ):
+ """
+ Validate the model using pytorch lightning
+ Parameters
+ ----------
+ data_loader : torch.utils.data.DataLoader
+ Dataloader use in :func:`callbacks` function
+ project : str, optional
+ Project name using to save checkpoints, by default None
+ expe_name : str, optional
+ Specific experiment name to save checkpoints, by default None
+ callbacks : list, optional
+ List of callbacks to use, by default :func:`callbacks` output
+ args : Namespace, optional
+ Additional arguments use in training process, by default None
+ """
+ # Set the default callbacks if not provide.
+ callbacks = callbacks if callbacks is not None else self.callbacks(data_loader)
+
+ alonet.common.pl_helpers.run_pl_validate(
+ # Trainer, data & callbacks
+ lit_model=self,
+ data_loader=data_loader,
+ callbacks=callbacks,
+ # Project info
+ args=args,
+ project=project,
+ )
diff --git a/alonet/common/pl_helpers.py b/alonet/common/pl_helpers.py
index 2a48888d..75533f26 100644
--- a/alonet/common/pl_helpers.py
+++ b/alonet/common/pl_helpers.py
@@ -4,9 +4,12 @@
from pytorch_lightning.loggers import TensorBoardLogger, WandbLogger
import datetime
import os
+import json
import re
import torch
+from alodataset.base_dataset import _user_prompt
+
parser = ArgumentParser()
@@ -15,6 +18,7 @@ def vb_folder(create_if_not_found=False):
alofolder = os.path.join(home, ".aloception")
if not os.path.exists(alofolder):
if create_if_not_found:
+ print(f"{alofolder} cannot be found so will be created.")
os.mkdir(alofolder)
else:
raise Exception(
@@ -92,6 +96,20 @@ def add_argparse_args(parent_parser, add_pl_args=True, mode="training"):
const="wandb",
help="Log results, can specify logger, by default %(default)s. If set but value not provided:wandb",
)
+ parser.add_argument(
+ "--log_save_dir",
+ type=str,
+ default=None,
+ nargs="?",
+ help="Path to save training log, by default %(default)s. If not set, use the default value in alonet_config.json",
+ )
+ parser.add_argument(
+ "--cp_save_dir",
+ type=str,
+ default=None,
+ nargs="?",
+ help="Path to save training checkpoint, by default %(default)s. If not set, use the default value in alonet_config.json",
+ )
parser.add_argument("--cpu", action="store_true", help="Use the CPU instead of scaling on the vaiable GPUs")
parser.add_argument("--run_id", type=str, help="Load the weights from this saved experiment")
parser.add_argument(
@@ -201,6 +219,66 @@ def load_training(
return lit_model
+def set_save_dir_config(key):
+ """
+ Create /home/USER/.aloception/alonet_config.json with path to log/weights save dir.
+ """
+ streaming_config = os.path.join(vb_folder(create_if_not_found=True), "alonet_config.json")
+ if not os.path.exists(streaming_config):
+ with open(streaming_config, "w") as f: # Json init as empty config
+ json.dump(dict(), f, indent=4)
+ with open(streaming_config) as f:
+ content = json.loads(f.read())
+ key = f"{key}_save_dir"
+ default_dir = vb_folder()
+ default_dir_message = f"Do you want to use the default dir {default_dir} ? (Y)es or Please write a new directory for {key}: "
+ if key not in content:
+ save_dir = _user_prompt(
+ f"{key} is not set in config file. " + default_dir_message
+ )
+ if save_dir.lower() in ["y", "yes"]:
+ save_dir = default_dir
+ content[key] = save_dir
+ with open(streaming_config, "w") as f: # Save new directory
+ json.dump(content, f, indent=4)
+ return content[key]
+
+
+def get_dir_from_config(args, key):
+ """
+ Get the directory to save log/checkpoint from the config in
+ /home/USER/.aloception/alonet_config.json or from args if the dir is set.
+ This file will be created if not exist.
+
+ Parameters
+ ----------
+ args: Namespace
+ key: str
+ log or checkpoint
+
+ Return
+ ------
+ The directory to save log/checkpoint
+ """
+ assert key in ["log", "checkpoint"], "Value of key must be log or checkpoint"
+ save_dir = args.log_save_dir if key == "log" else args.cp_save_dir
+
+ # get dir from config.json file if save_dir is not Set. Create the file if not exist
+ if save_dir is None:
+ streaming_config = os.path.join(vb_folder(create_if_not_found=True), "alonet_config.json")
+ if not os.path.exists(streaming_config):
+ save_dir = set_save_dir_config(key)
+ with open(streaming_config) as f:
+ content = json.loads(f.read())
+ key_dir = f"{key}_save_dir"
+ if key_dir not in content:
+ return set_save_dir_config(key)
+ else:
+ return content[key_dir]
+ else:
+ return save_dir
+
+
def get_expe_infos(project, expe_name, args=None):
"""
Get the directories for the project and the experimentation
@@ -209,7 +287,7 @@ def get_expe_infos(project, expe_name, args=None):
expe_name = expe_name or args.expe_name
if args is not None and not args.no_suffix:
expe_name = "{}_{:%B-%d-%Y-%Hh-%M}".format(expe_name, datetime.datetime.now())
- project_dir = os.path.join(vb_folder(), f"project_{project}")
+ project_dir = os.path.join(get_dir_from_config(args, "checkpoint"), f"project_{project}")
expe_dir = os.path.join(project_dir, expe_name)
return project_dir, expe_dir, expe_name
@@ -228,6 +306,8 @@ def run_pl_training(
args.logger = "wandb"
args.save = False
args.cpu = False
+ args.log_save_dir = None
+ args.cp_save_dir = None
# Set the experiment name ID
project_dir, expe_dir, expe_name = get_expe_infos(project, expe_name, args)
@@ -251,12 +331,19 @@ def run_pl_training(
expe_dir = os.path.join(run_id_project_dir, args.run_id)
if args.log is not None:
+ # get dir to save log
+ save_dir = os.path.join(get_dir_from_config(args, "log"), f"project_{project}", expe_name)
+
+ # create path
if args.log == "wandb":
- logger = WandbLogger(name=expe_name, project=project, id=expe_name)
+ save_dir = os.path.join(save_dir, "wandb")
+ os.makedirs(save_dir, exist_ok=True)
+ logger = WandbLogger(name=expe_name, save_dir=save_dir, project=project, id=expe_name)
logger.log_hyperparams(args)
-
elif args.log == "tensorboard":
- logger = TensorBoardLogger(save_dir="tensorboard/", name=expe_name, sub_dir=expe_name)
+ save_dir = os.path.join(save_dir, "tensorboard")
+ os.makedirs(save_dir, exist_ok=True)
+ logger = TensorBoardLogger(save_dir=save_dir, name=expe_name, sub_dir=expe_name)
else:
raise ValueError("Unknown or not implemented logger")
else:
diff --git a/alonet/deformable_detr/ops/__init__.py b/alonet/deformable_detr/ops/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/alonet/deformable_detr/ops/src/__init__.py b/alonet/deformable_detr/ops/src/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/alonet/metrics/compute_pq.py b/alonet/metrics/compute_pq.py
index 57379e80..db6ae104 100644
--- a/alonet/metrics/compute_pq.py
+++ b/alonet/metrics/compute_pq.py
@@ -29,11 +29,12 @@ def __iadd__(self, pq_stat_cat):
class PQMetrics(object):
- def __init__(self):
+ def __init__(self, iou_threshold=0.5):
self.pq_per_cat = defaultdict(PQStatCat)
self.class_names = None
self.isfull = False
self.categories = dict()
+ self.iou_threshold = iou_threshold
def __getitem__(self, label_id: int):
return self.pq_per_cat[label_id]
@@ -75,7 +76,7 @@ def update_data_objects(self, cat_labels: aloscene.Labels, isthing_labels: alosc
)
self.isfull = False
- def pq_average(self, isthing: bool = None, print_result: bool = False, **kwargs):
+ def pq_average(self, isthing: bool = None, print_result: bool = False, recall_precision: bool = False, **kwargs):
"""Calculate SQ, RQ and PQ metrics from the categories, and thing/stuff/all if desired
Parameters
@@ -85,6 +86,8 @@ def pq_average(self, isthing: bool = None, print_result: bool = False, **kwargs)
By default the metric is executed over both
print_result : bool
Print result in console, by default False
+ recall_precision : bool
+ Include precision and reacall.
Returns
-------
@@ -92,6 +95,8 @@ def pq_average(self, isthing: bool = None, print_result: bool = False, **kwargs)
Dictionaries with general PQ average metrics and for each class, respectively
"""
pq, sq, rq, n = 0, 0, 0, 0
+ t_fp, t_fn, t_tp = 0, 0, 0
+
per_class_results = {}
for label, label_info in self.categories.items():
if isthing is not None:
@@ -110,11 +115,23 @@ def pq_average(self, isthing: bool = None, print_result: bool = False, **kwargs)
sq_class = iou / tp if tp != 0 else 0
rq_class = tp / (tp + 0.5 * fp + 0.5 * fn)
per_class_results[label_info["category"]] = {"pq": pq_class, "sq": sq_class, "rq": rq_class}
+
pq += pq_class
sq += sq_class
rq += rq_class
+ t_fp += fp
+ t_tp += tp
+ t_fn += fn
+
+ if n == 0:
+ n = 1
+
result = {"pq": pq / n, "sq": sq / n, "rq": rq / n, "n": n}
+ if recall_precision:
+ recall = t_tp / (t_tp + t_fn) if t_tp + t_fn > 0 else 0
+ precision = t_tp / (t_tp + t_fp) if t_tp + t_fn > 0 else 0
+ result.update({"precision": precision, "recall": recall})
if print_result:
suffix = ""
if isthing is not None and isthing:
@@ -222,7 +239,7 @@ def add_sample(
- gt_pred_map.get((VOID, pred_label), 0)
)
iou = intersection / union
- if iou > 0.5: # Add matches from this IoU (take from original paper)
+ if iou > self.iou_threshold: # Add matches from this IoU (take from original paper)
self.pq_per_cat[gt_segms[gt_label]["cat_id"]].tp += 1
self.pq_per_cat[gt_segms[gt_label]["cat_id"]].iou += iou
gt_matched.add(gt_label)
@@ -245,13 +262,15 @@ def add_sample(
continue
self.pq_per_cat[pred_info["cat_id"]].fp += 1
- def calc_map(self, print_result: bool = False):
+ def calc_map(self, print_result: bool = False, recall_precision: bool = False):
"""Calcule PQ-RQ-SQ maps
Parameters
----------
print_result : bool, optional
Print results, by default False
+ recall_precision : bool.
+ Include precision and recall in returned results
Returns
-------
@@ -267,13 +286,15 @@ def calc_map(self, print_result: bool = False):
keys, cats = ["all"], [None]
for key, cat in zip(keys, cats):
if cat is not None or not self.isfull:
- all_maps[key], all_maps_per_class[key] = self.pq_average(cat,
- print_result,
- clm_size=9,
- head_elm=["PQ", "SQ", "RQ"],
- )
+ all_maps[key], all_maps_per_class[key] = self.pq_average(
+ cat,
+ print_result,
+ clm_size=9,
+ head_elm=["PQ", "SQ", "RQ"],
+ recall_precision=recall_precision,
+ )
else:
- all_maps[key], all_maps_per_class[key] = self.pq_average(cat)
+ all_maps[key], all_maps_per_class[key] = self.pq_average(cat, recall_precision=recall_precision)
if print_result and self.isfull:
_print_head(head_elm=["PQ", "SQ", "RQ"], clm_size=9)
diff --git a/alonet/torch2trt/base_exporter.py b/alonet/torch2trt/base_exporter.py
index 50032fc1..4b04c046 100644
--- a/alonet/torch2trt/base_exporter.py
+++ b/alonet/torch2trt/base_exporter.py
@@ -308,7 +308,6 @@ def _torch2onnx(self):
self.onnx_path, # where to save the model
export_params=True, # store the trained parameter weights inside the model file
output_names=onames,
- enable_onnx_checker=True,
input_names=self.input_names, # the model's input names
dynamic_axes=self.dynamic_axes,
custom_opsets=self.custom_opset,
diff --git a/aloscene/__init__.py b/aloscene/__init__.py
index 7340ab5d..da3ae2bd 100644
--- a/aloscene/__init__.py
+++ b/aloscene/__init__.py
@@ -1,26 +1,27 @@
ALOSCENE_ROOT = "/".join(__file__.split("/")[:-1])
-from . import tensors
-from .labels import Labels
-from .camera_calib import CameraExtrinsic, CameraIntrinsic
-from .mask import Mask
-from .flow import Flow
-from .depth import Depth
-from .points_2d import Points2D
-from .points_3d import Points3D
-from .disparity import Disparity
-from .pose import Pose
-from .bounding_boxes_2d import BoundingBoxes2D
-from .bounding_boxes_3d import BoundingBoxes3D
-from .oriented_boxes_2d import OrientedBoxes2D
-from .scene_flow import SceneFlow
-from .frame import Frame
-from .tensors.spatial_augmented_tensor import SpatialAugmentedTensor
-
-from .renderer import Renderer
+from . tensors import AugmentedTensor, SpatialAugmentedTensor
+from . labels import Labels
+from . camera_calib import CameraExtrinsic, CameraIntrinsic
+from . mask import Mask
+from . flow import Flow
+from . depth import Depth
+from . points_2d import Points2D
+from . points_3d import Points3D
+from . disparity import Disparity
+from . pose import Pose
+from . bounding_boxes_2d import BoundingBoxes2D
+from . bounding_boxes_3d import BoundingBoxes3D
+from . oriented_boxes_2d import OrientedBoxes2D
+from . scene_flow import SceneFlow
+from . frame import Frame
+from . renderer import Renderer
from typing import Union
+import pkg_resources
+__version__ = pkg_resources.get_distribution("aloception").version
+
def batch_list(tensors, intersection=False):
return SpatialAugmentedTensor.batch_list(tensors, intersection=intersection)
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100755
index 00000000..98fa5cb6
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+set -e
+
+if [ -v LOCAL_USER_ID ]; then
+ if id -u ${LOCAL_USER_ID} >/dev/null 2>&1; then
+ BASE_USER=aloception
+ else
+ # Create a new user with the specified UID and GI
+ useradd --home /home/aloception --uid $LOCAL_USER_ID --shell /bin/bash dynamic && usermod -aG sudo dynamic && echo "dynamic ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
+ BASE_USER=dynamic
+ fi
+else
+ BASE_USER=aloception
+fi
+
+echo "Starting with UID : $LOCAL_USER_ID, base user: $BASE_USER"
+
+export CONDA_HOME=/opt/miniconda;
+export PATH=${CONDA_HOME}/condabin:${CONDA_HOME}/bin:${PATH};
+source activate base;
+
+if [ "$#" -ne 0 ]; then
+ su -s /bin/bash $BASE_USER -c "export CONDA_HOME=/opt/miniconda; export PATH=${CONDA_HOME}/condabin:${CONDA_HOME}/bin:${PATH}; source activate base; $@"
+else
+ su -s /bin/bash $BASE_USER -c "export CONDA_HOME=/opt/miniconda; export PATH=${CONDA_HOME}/condabin:${CONDA_HOME}/bin:${PATH}; source activate base; script -q /dev/null -c 'bash -i'"
+fi
+
diff --git a/requirements-torch1.13.1.txt b/requirements/requirements-torch1.13.1.txt
similarity index 99%
rename from requirements-torch1.13.1.txt
rename to requirements/requirements-torch1.13.1.txt
index e6a22f91..95cc045a 100644
--- a/requirements-torch1.13.1.txt
+++ b/requirements/requirements-torch1.13.1.txt
@@ -19,3 +19,4 @@ tqdm==4.62.3
captum==0.4.0
setuptools==59.5.0
+
diff --git a/setup.py b/setup.py
index 551f474c..2d400fdc 100644
--- a/setup.py
+++ b/setup.py
@@ -3,32 +3,27 @@
setup(
name='aloception',
author='Visual Behavior',
- version='0.3.0',
+ version='0.6.0beta',
description='Aloception is a set of package for computer vision: aloscene, alodataset, alonet.',
- packages=find_packages(include=['aloscene', 'alodataset', 'alonet']),
+ packages=find_packages(include=["aloscene", "aloscene.*", "alodataset", "alodataset.*", "alonet", "alonet.*"]),
url='https://visualbehavior.ai/',
download_url='https://github.com/Visual-Behavior/aloception-oss',
install_requires=[
- 'matplotlib==3.5.3',
- 'more-itertools==8.8.0', # required for alodataset waymo
- 'onnx==1.12.0',
- 'onnx_graphsurgeon==0.0.1.dev5',
- 'onnxsim==0.4.8',
- 'opencv-python==4.5.3.56'
- 'Pillow==9.2.0',
- 'pycocotools==2.0.2', # required for alodataset coco
- 'pytorch_lightning==1.4.1',
- 'pytorch_quantization==0.0.1.dev5',
- 'Requests==2.28.1',
- 'scipy==1.4.1', # required for alonet/detr/matcher
- 'setuptools==63.4.1',
- 'tensorflow==2.10.0', # required for alodataset/prepare/waymo_converter
- 'tensorrt==0.0.1.dev5',
- 'torchvision==0.13.1',
+ 'PyYAML==5.4.1',
+ 'chardet==4.0.0',
+ 'idna==2.10',
+ 'scipy==1.10.0',
+ 'more_itertools==8.8.0',
+ 'requests==2.25.1',
+ 'opencv-python==4.7.0.68',
+ 'python-dateutil==2.8.2',
+ 'urllib3==1.26.6',
+ 'protobuf==4.21.12',
+ 'wandb==0.13.9',
'tqdm==4.62.3',
- 'ts==0.5.1',
- 'wandb==0.12.2',
- 'waymo_open_dataset==1.0.1'],
+ 'captum==0.4.0',
+ 'setuptools==59.5.0',
+ ],
setup_requires=['numpy', 'torch', 'nvidia-pyindex', 'pycuda'],
license_files=['LICENSE'],
keywords=['artificial intelligence', 'computer vision'],