Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add main feat #16

Merged
merged 23 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .copier-answers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ email: 34j.95a2p@simplelogin.com
full_name: 34j
github_username: 34j
has_cli: true
initial_commit: true
initial_commit: false
open_source_license: MIT
open_with_vscode: true
open_with_vscode: false
package_name: vr180_convert
project_name: VR180 image converter
project_short_description: Simple VR180 image converter
project_slug: vr180-convert
run_poetry_install: true
setup_github: true
setup_pre_commit: true
setup_venv: true
run_poetry_install: false
setup_github: false
setup_pre_commit: false
setup_venv: false
venv_version: '3.11'

61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,70 @@

---

Simple VR180 image converter
Simple VR180 image converter on top of OpenCV and NumPy.

## Installation

Install this via pip (or your favourite package manager):

`pip install vr180-convert`
```shell
pipx install vr180-convert
```

| Left | Right | Output |
| ------------------------------ | ------------------------------- | ---------------------------------------------------- |
| ![left](docs/_static/test.jpg) | ![right](docs/_static/test.jpg) | ![output](docs/_static/test.lr.PolynomialScaler.jpg) |

## Usage

Simply run the following command to convert 2 fisheye images to a SBS equirectangular VR180 image:

```shell
v1c lr left.jpg right.jpg
```

You can also specify the conversion model by adding Python code directly to the `--transformer` option:

```shell
v1c lr left.jpg right.jpg ---transformer "EquirectangularEncoder() * Euclidean3DRotator(from_rotation_vector([0, np.pi / 4, 0])) * FisheyeDecoder("equidistant")"
```

Please refer to the [API documentation](https://vr180-convert.readthedocs.io/) for the available transformers and their parameters.
For `from_rotation_vector`, please refer to the [numpy-quaternion documentation](https://quaternion.readthedocs.io/en/latest/Package%20API%3A/quaternion/#from_rotation_vector).

The radius of the non-black area of the input image is assumed by counting black pixels by default, but it would be better to specify it manually to get stable results:

```shell
v1c lr left.jpg right.jpg --radius 1000
v1c lr left.jpg right.jpg --radius max # min(width, height) / 2
```

To convert a single image, use `v1c s` instead.

For more information, please refer to the help or API documentation:

```shell
v1c --help
```

## Usage as a library

For more complex transformations, it is recommended to create your own `Transformer`.

Note that the transformation is applied in inverse order (new[(x, y)] = old[transform(x, y)], e.g. to decode [orthographic](https://en.wikipedia.org/wiki/Fisheye_lens#Mapping_function) fisheye images, `transform_polar` should be `arcsin(theta)`, not `sin(theta)`.)

```python
from vr180_convert import PolarRollTransformer, apply_lr

class MyTransformer(PolarRollTransformer):
def transform_polar(
self, theta: NDArray, roll: NDArray, **kwargs: Any
) -> tuple[NDArray, NDArray]:
return theta**0.98 + theta**1.01, roll

transformer = EquirectangularEncoder() * MyTransformer() * FisheyeDecoder("equidistant")
apply_lr(transformer, left_path="left.jpg", right_path="right.jpg", out_path="output.jpg")
```

## Contributors ✨

Expand Down
Binary file added docs/_static/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/test.lr.PolynomialScaler.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
322 changes: 298 additions & 24 deletions poetry.lock

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ packages = [

[tool.poetry.scripts]
vr180-convert = "vr180_convert.cli:app"
v1c = "vr180_convert.cli:app"

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"
rich = ">=10"
typer = {extras = ["all"], version = "^0.12.0"}
typer = {extras = ["all"], version = "^0.9.0"}
opencv-python = "^4.9.0.80"
attrs = "^23.2.0"
scikit-learn = "^1.4.2"
numpy-quaternion = "^2023.0.3"
strenum = "^0.4.15"

[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
Expand Down Expand Up @@ -86,13 +92,16 @@ exclude_lines = [
[tool.ruff]
target-version = "py38"
line-length = 88
unsafe-fixes = true
ignore = [
"D203", # 1 blank line required before class docstring
"D212", # Multi-line docstring summary should start at the first line
"D100", # Missing docstring in public module
"D104", # Missing docstring in public package
"D107", # Missing docstring in `__init__`
"D401", # First line of docstring should be in imperative mood
"D101",
"D102",
]
select = [
"B", # flake8-bugbear
Expand Down
33 changes: 32 additions & 1 deletion src/vr180_convert/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
__version__ = "0.0.1"
__version__ = "0.0.0"
from .remapper import apply, apply_lr, get_map
from .transformer import (
DenormalizeTransformer,
EquirectangularEncoder,
Euclidean3DRotator,
Euclidean3DTransformer,
FisheyeDecoder,
FisheyeEncoder,
MultiTransformer,
NormalizeTransformer,
PolarRollTransformer,
TransformerBase,
ZoomTransformer,
)

__all__ = [
"TransformerBase",
"ZoomTransformer",
"MultiTransformer",
"NormalizeTransformer",
"PolarRollTransformer",
"DenormalizeTransformer",
"FisheyeDecoder",
"FisheyeEncoder",
"EquirectangularEncoder",
"Euclidean3DRotator",
"Euclidean3DTransformer",
"apply",
"apply_lr",
"get_map",
]
146 changes: 141 additions & 5 deletions src/vr180_convert/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,148 @@
from enum import auto
from pathlib import Path

import cv2 as cv
import typer
from rich import print
from quaternion import * # noqa
from strenum import StrEnum
from typing_extensions import Annotated

from vr180_convert.transformer import * # noqa
from vr180_convert.transformer import EquirectangularEncoder, FisheyeDecoder

from .main import add
from .remapper import apply, apply_lr

DEFAULT_EXTENSION = ".png"

app = typer.Typer()


class _InterpolationFlags(StrEnum):
"""Interpolation flags enum for typer."""

INTER_NEAREST = auto()
INTER_LINEAR = auto()
INTER_CUBIC = auto()
INTER_AREA = auto()
INTER_LANCZOS4 = auto()
INTER_MAX = auto()
WARP_FILL_OUTLIERS = auto()
WARP_INVERSE_MAP = auto()


class _BorderTypes(StrEnum):
"""Border types enum for typer."""

BORDER_CONSTANT = auto()
BORDER_REPLICATE = auto()
BORDER_REFLECT = auto()
BORDER_WRAP = auto()
BORDER_REFLECT_101 = auto()
BORDER_TRANSPARENT = auto()
BORDER_ISOLATED = auto()


@app.command()
def main(n1: int, n2: int) -> None:
"""Add the arguments and print the result."""
print(add(n1, n2))
def lr(
left_path: Annotated[Path, typer.Argument(help="Left image path")],
right_path: Annotated[Path, typer.Argument(help="Right image path")],
transformer: Annotated[
str, typer.Option(help="Transformer Python code (to be `eval()`ed)")
] = "",
out_path: Annotated[
Path,
typer.Option(
help="Output image path, defaults to left_path.with_suffix('.out.jpg')"
),
] = Path(""),
size: Annotated[
str, typer.Option(help="Output image size, defaults to 2048x2048")
] = "2048x2048",
interpolation: Annotated[
_InterpolationFlags,
typer.Option(help="Interpolation method, defaults to lanczos4"),
] = _InterpolationFlags.INTER_LANCZOS4, # type: ignore
boarder_mode: Annotated[
_BorderTypes, typer.Option(help="Border mode, defaults to constant")
] = _BorderTypes.BORDER_CONSTANT, # type: ignore
boarder_value: int = 0,
radius: Annotated[
str, typer.Option(help="Radius of the fisheye image, defaults to 'auto'")
] = "auto",
) -> None:
"""Remap a pair of fisheye images to a pair of SBS equirectangular images."""
if transformer == "":
transformer_ = EquirectangularEncoder() * FisheyeDecoder("equidistant")
else:
transformer_ = eval(transformer) # noqa
apply_lr(
transformer=transformer_,
left_path=left_path,
right_path=right_path,
out_path=(
Path(left_path).with_suffix(f".out.{DEFAULT_EXTENSION}")
if out_path == Path("")
else out_path
),
radius=float(radius) if radius not in ["auto", "max"] else radius, # type: ignore
size_output=tuple(map(int, size.split("x"))), # type: ignore
interpolation=getattr(cv, interpolation.upper()),
boarder_mode=getattr(cv, boarder_mode.upper()),
boarder_value=boarder_value,
)


@app.command()
def s(
in_paths: Annotated[list[Path], typer.Argument(help="Image paths")],
transformer: Annotated[
str, typer.Option(help="Transformer Python code (to be `eval()`ed)")
] = "",
out_path: Annotated[
Path,
typer.Option(
help="Output image path, defaults to left_path.with_suffix('.out.jpg')"
),
] = Path(""),
size: Annotated[
str, typer.Option(help="Output image size, defaults to 2048x2048")
] = "2048x2048",
interpolation: Annotated[
_InterpolationFlags,
typer.Option(help="Interpolation method, defaults to lanczos4"),
] = _InterpolationFlags.INTER_LANCZOS4, # type: ignore
boarder_mode: Annotated[
_BorderTypes, typer.Option(help="Border mode, defaults to constant")
] = _BorderTypes.BORDER_CONSTANT, # type: ignore
boarder_value: int = 0,
radius: Annotated[
str, typer.Option(help="Radius of the fisheye image, defaults to 'auto'")
] = "auto",
) -> None:
"""Remap fisheye images to SBS equirectangular images."""
if transformer == "":
transformer_ = EquirectangularEncoder() * FisheyeDecoder("equidistant")
else:
transformer_ = eval(transformer) # noqa

if out_path == Path(""):
out_paths = [p.with_suffix(f".out.{DEFAULT_EXTENSION}") for p in in_paths]
elif out_path.is_dir():
out_paths = [out_path / p.name for p in in_paths]
else:
if len(in_paths) > 1:
raise ValueError(
"Output path must be a directory when multiple input paths are provided"
)
out_paths = [out_path for p in in_paths]

apply(
transformer=transformer_,
in_paths=in_paths,
out_paths=out_paths,
radius=float(radius) if radius not in ["auto", "max"] else radius, # type: ignore
size_output=tuple(map(int, size.split("x"))), # type: ignore
interpolation=getattr(cv, interpolation.upper()),
boarder_mode=getattr(cv, boarder_mode.upper()),
boarder_value=boarder_value,
)
3 changes: 0 additions & 3 deletions src/vr180_convert/main.py

This file was deleted.

Loading
Loading