Skip to content
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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,36 @@ $ fpie-gui -s test3_src.jpg -t test3_tgt.jpg -o result.jpg -b cuda -n 10000

We provide a simple GUI for real-time seamless cloning. You need to use your mouse to draw a rectangle on top of the source image, and click a point in target image. After that the result will automatically be generated. In the end, you can press ESC to terminate the program.

### Video and streams

```bash
$ fpie-video -s src.png -m mask.png -t input.mp4 -o output.mp4 -h1 100 -w1 100 -n 5000 -g max
```

`fpie-video` applies the same Poisson blending to each target frame. The target can be a video file, stream URL, or camera index supported by OpenCV/FFmpeg. If the source is a video, frames are consumed one by one; add `--loop-source` to repeat it when the target is longer.

The same interface is available from Python:

```python
from fpie.video import BlendOptions, blend_video

blend_video(
"src.png",
"input.mp4",
"output.mp4",
mask="mask.png",
mask_on_tgt=(100, 100),
options=BlendOptions(backend="numpy", iterations=5000),
)
```

### Backend and Solver

We have provided 7 backends. Each backend has two solvers: EquSolver and GridSolver. You can find the difference between these two solvers in the next section.

For different backend usage, please check out the related documentation [here](https://fpie.readthedocs.io/en/main/backend.html).

For other usage, please run `fpie -h` or `fpie-gui -h` to see the hint.
For other usage, please run `fpie -h`, `fpie-gui -h`, or `fpie-video -h` to see the hint.

## Benchmark Result

Expand Down
2 changes: 1 addition & 1 deletion fpie/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Fast Poisson Image Editing package."""

__version__ = "0.3.2"
__version__ = "0.3.3"
36 changes: 32 additions & 4 deletions fpie/process.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Processor abstractions and backend selection for PIE solvers."""

import atexit
import os
from abc import ABC, abstractmethod
from typing import Any
Expand Down Expand Up @@ -30,6 +31,8 @@ def _default_cpu_count() -> int:
DEFAULT_BACKEND = "numpy"
ALL_BACKEND = ["numpy"]
MPI: Any | None = None
_MPI_INITIALIZED_BY_FPIE = False
_MPI_FINALIZER_REGISTERED = False

try:
from fpie import numba_solver
Expand Down Expand Up @@ -64,6 +67,9 @@ def _default_cpu_count() -> int:
core_openmp = None

try:
import mpi4py

mpi4py.rc.initialize = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve MPI finalization when switching to lazy initialization

Disabling auto-init with mpi4py.rc.initialize = False changes mpi4py’s default finalization behavior as well (its default rc.finalize=None follows rc.initialize), so MPI is now manually initialized in _ensure_mpi_initialized() but never finalized anywhere in this code path. For MPI backend runs, this can leave ranks exiting without MPI_Finalize, which commonly produces runtime abort/warning behavior and unstable shutdown on some MPI implementations.

Useful? React with 👍 / 👎.

from mpi4py import MPI as _MPI

from fpie import core_mpi # type: ignore
Expand Down Expand Up @@ -143,6 +149,28 @@ def step(self, iteration: int) -> tuple[np.ndarray, np.ndarray] | None:
pass


def _finalize_mpi() -> None:
"""Finalize MPI when this module initialized it lazily."""
assert MPI is not None
if MPI.Is_initialized() and not MPI.Is_finalized():
MPI.Finalize()


def _ensure_mpi_initialized() -> Any:
"""Initialize MPI lazily when the MPI backend is explicitly selected."""
global _MPI_FINALIZER_REGISTERED # noqa: PLW0603
global _MPI_INITIALIZED_BY_FPIE # noqa: PLW0603

assert MPI is not None
if not MPI.Is_initialized():
MPI.Init_thread()
_MPI_INITIALIZED_BY_FPIE = True
if _MPI_INITIALIZED_BY_FPIE and not _MPI_FINALIZER_REGISTERED:
atexit.register(_finalize_mpi)
_MPI_FINALIZER_REGISTERED = True
return MPI


class EquProcessor(BaseProcessor):
"""PIE Jacobi equation processor."""

Expand All @@ -167,9 +195,9 @@ def __init__(
elif backend == "openmp" and core_openmp is not None:
core = core_openmp.EquSolver(n_cpu)
elif backend == "mpi" and core_mpi is not None:
assert MPI is not None
mpi = _ensure_mpi_initialized()
core = core_mpi.EquSolver(min_interval)
rank = MPI.COMM_WORLD.Get_rank()
rank = mpi.COMM_WORLD.Get_rank()
elif backend == "cuda" and core_cuda is not None:
core = core_cuda.EquSolver(block_size)
elif backend.startswith("taichi") and taichi_solver is not None:
Expand Down Expand Up @@ -306,9 +334,9 @@ def __init__(
elif backend == "openmp" and core_openmp is not None:
core = core_openmp.GridSolver(grid_x, grid_y, n_cpu)
elif backend == "mpi" and core_mpi is not None:
assert MPI is not None
mpi = _ensure_mpi_initialized()
core = core_mpi.GridSolver(min_interval)
rank = MPI.COMM_WORLD.Get_rank()
rank = mpi.COMM_WORLD.Get_rank()
elif backend == "cuda" and core_cuda is not None:
core = core_cuda.GridSolver(grid_x, grid_y)
elif backend.startswith("taichi") and taichi_solver is not None:
Expand Down
Loading
Loading