Skip to content

Commit

Permalink
created external_sequence for Run30 LV project and updated Readme
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlib committed Jan 1, 2025
1 parent 45f19e3 commit 84d75b7
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 59 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ Ask for help on our mailing list:



## Working with plugins

Plugins is a system of extensions to PyPTV without the need to change the GUI

1. copy the `external_sequence_list.txt` and `external_tracker_list.txt` to the working folder
2. copy the `plugins/` directory to the working folder
3. modify the code so it performs instead of the default sequence or default tracker
4. Open the GUI and Plugins -> Choose , then run the rest: Init -> Sequence
4 changes: 3 additions & 1 deletion bump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def increment_version(version, part='minor'):
elif part == 'minor':
minor += 1
patch = 0
elif part == 'patch':
patch += 1
else:
raise ValueError("Invalid part specified. Use 'major' or 'minor'.")

Expand All @@ -56,7 +58,7 @@ def increment_version(version, part='minor'):
print(f"Current version is {current_version}")

# Example usage
new_version = increment_version(current_version, 'minor')
new_version = increment_version(current_version, 'patch')
print(f"New version is {new_version}")

# Update the version in pyproject.toml
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pyptv"
version = "0.3.1"
version = "0.3.2"
description = "Python GUI for the OpenPTV library `liboptv`"
authors = [
{ name = "Alex Liberzon", email = "[email protected]" }
Expand Down
2 changes: 1 addition & 1 deletion pyptv/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.3.1"
__version__ = "0.3.2"
3 changes: 2 additions & 1 deletion pyptv/external_sequence_list.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ext_sequence_denis
plugins/ext_sequence_denis
plugins/ext_sequence_rembg

2 changes: 1 addition & 1 deletion pyptv/external_tracker_list.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ext_tracker_denis
plugins/ext_tracker_denis

File renamed without changes.
302 changes: 302 additions & 0 deletions pyptv/plugins/ext_sequence_rembg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import random

import numpy as np
from imageio.v3 import imread, imwrite
from pathlib import Path

from skimage import img_as_ubyte
from skimage import filters, measure, morphology
from skimage.color import rgb2gray, label2rgb
from skimage.segmentation import clear_border
from skimage.morphology import binary_erosion, binary_dilation, disk
from skimage.util import img_as_ubyte

from optv.correspondences import correspondences, MatchedCoords
from optv.tracker import default_naming
from optv.orientation import point_positions

import matplotlib.pyplot as plt


def mask_image(imname : Path, display: bool = False) -> np.ndarray:
"""Mask the image using a simple high pass filter.
Parameters
----------
img : np.ndarray
The image to be masked.
Returns
-------
np.ndarray
The masked image.
"""

img = imread(imname)
if img.ndim > 2:
img = rgb2gray(img)

if img.dtype != np.uint8:
img = img_as_ubyte(img)

# Apply Gaussian filter to smooth the image
smoothed_frame = filters.gaussian(img, sigma=5)

if display:
plt.figure()
plt.imshow(smoothed_frame)
plt.show()

# Apply Otsu's thresholding method to segment the object
thresh = filters.threshold_otsu(smoothed_frame)
# print('Threshold:', thresh)
binary_frame = smoothed_frame > 1.1*thresh

if display:
plt.figure()
plt.imshow(binary_frame)
plt.show()


# binary_frame_cleared = clear_border(binary_frame, buffer_size=20)
binary_frame_cleared = binary_frame.copy()

# plt.figure()
# plt.imshow(binary_frame_cleared)
# plt.show()

# Remove small bright objects
cleaned_frame = morphology.remove_small_objects(binary_frame_cleared, min_size=100000)

# %%
# Apply morphological closing to close the boundary
closed_cleaned_frame = binary_dilation(cleaned_frame, disk(21))
closed_cleaned_frame = binary_erosion(closed_cleaned_frame, disk(21))

if display:
# Display the result
plt.figure()
plt.imshow(closed_cleaned_frame, cmap='gray')
plt.title('Closed Boundary of Cleaned Frame')
plt.show()


# check the size of the second largest black hole
# labeled_frame = measure.label(~closed_cleaned_frame)
# regions = measure.regionprops(labeled_frame)
# areas = np.array([r.area for r in regions])
# area_to_remove = np.sort(areas)[-2] # 2nd largest, 1st is the surrounding

# %%
# Fill holes inside the binary frame to remove large black objects
filled_frame = morphology.remove_small_holes(closed_cleaned_frame, area_threshold=2e6)

if display:
# # Display the result
plt.figure()
plt.imshow(filled_frame, cmap='gray')
plt.title('Binary Frame with Large Black Objects Removed')
plt.show()

# %%

# # Remove small objects and clear the border
# cleaned_frame = morphology.remove_small_objects(binary_frame, min_size=100000)
# # Fill holes inside the binary frame to remove dark islands
# filled_frame = morphology.remove_small_holes(cleaned_frame, area_threshold=100000)

# filled_frame = clear_border(filled_frame)

# Label the segmented regions
labeled_frame = measure.label(filled_frame)

if display:
# Show the labeled filled frame as a color labeled image
plt.figure()
plt.imshow(label2rgb(labeled_frame, image=img, bg_label=0))
plt.title('Color Labeled Frame with Filled Holes')
plt.show()

# %%

# Find region properties
regions = measure.regionprops(labeled_frame)

# Assuming the largest region is the object of interest
largest_region = max(regions, key=lambda r: r.area)


# Find the smooth contour that surrounds the largest region
smooth_contour = morphology.convex_hull_image(largest_region.image)

# Create an empty image to draw the smooth contour
smooth_contour_image = np.zeros_like(labeled_frame, dtype=bool)

# Place the smooth contour in the correct location
minr, minc, maxr, maxc = largest_region.bbox
smooth_contour_image[minr:maxr, minc:maxc] = smooth_contour

if display:
# Display the smooth contour on the labeled image
plt.figure()
plt.imshow(labeled_frame, cmap='jet')
plt.contour(smooth_contour_image, colors='red', linewidths=2)
plt.title(f'Segmented Object with Smooth Contour')
plt.show()


# Convert the largest region to a black and white image
bw_image = np.zeros_like(labeled_frame, dtype=bool)
bw_image[largest_region.coords[:, 0], largest_region.coords[:, 1]] = True

# plt.figure(), plt.imshow(bw_image, cmap='gray')

# Apply morphological closing to remove sharp spikes
closed_image = binary_dilation(bw_image, disk(21))
closed_image = binary_erosion(closed_image, disk(21))

if display:
# Display the result
plt.figure()
plt.imshow(closed_image, cmap='gray')
plt.title('Smooth Boundary without Sharp Spikes')
plt.show()


# Apply morphological operations to get the external contour
eroded_image = binary_erosion(closed_image, disk(1))
external_contour = closed_image & ~eroded_image

imwrite(imname.with_suffix('.jpg'), img_as_ubyte(external_contour))

# Dilate the external contour for better visibility
dilated_external_contour = binary_dilation(external_contour, disk(3))

# Create a masked image of the same size as the input image
masked_image = np.zeros_like(img, dtype=np.uint8)
# Mask out (black) everything outside of closed_image
masked_image[closed_image] = img[closed_image]

if display:
plt.figure()
plt.imshow(masked_image)
plt.show()

return masked_image

class Sequence:
"""Sequence class defines external tracking addon for pyptv
User needs to implement the following functions:
do_sequence(self)
Connection to C ptv module is given via self.ptv and provided by pyptv software
Connection to active parameters is given via self.exp1 and provided by pyptv software.
User responsibility is to read necessary files, make the calculations and write the files back.
"""

def __init__(self, ptv=None, exp=None):
self.ptv = ptv
self.exp = exp

def do_sequence(self):
""" Copy of the sequence loop with one change we call everything as
self.ptv instead of ptv.
"""
# Sequence parameters

n_cams, cpar, spar, vpar, tpar, cals = (
self.exp.n_cams,
self.exp.cpar,
self.exp.spar,
self.exp.vpar,
self.exp.tpar,
self.exp.cals,
)

# # Sequence parameters
# spar = SequenceParams(num_cams=n_cams)
# spar.read_sequence_par(b"parameters/sequence.par", n_cams)


# sequence loop for all frames
first_frame = spar.get_first()
last_frame = spar.get_last()
print(f" From {first_frame = } to {last_frame = }")

for frame in range(first_frame, last_frame + 1):
# print(f"processing {frame = }")

detections = []
corrected = []
for i_cam in range(n_cams):
base_image_name = spar.get_img_base_name(i_cam).decode()
imname = Path(base_image_name % frame) # works with jumps from 1 to 10
masked_image = mask_image(imname)

# img = imread(imname)
# if img.ndim > 2:
# img = rgb2gray(img)

# if img.dtype != np.uint8:
# img = img_as_ubyte(img)



high_pass = self.ptv.simple_highpass(masked_image, cpar)
targs = self.ptv.target_recognition(high_pass, tpar, i_cam, cpar)

targs.sort_y()
detections.append(targs)
masked_coords = MatchedCoords(targs, cpar, cals[i_cam])
pos, _ = masked_coords.as_arrays()
corrected.append(masked_coords)

# if any([len(det) == 0 for det in detections]):
# return False

# Corresp. + positions.
sorted_pos, sorted_corresp, _ = correspondences(
detections, corrected, cals, vpar, cpar)

# Save targets only after they've been modified:
# this is a workaround of the proper way to construct _targets name
for i_cam in range(n_cams):
base_name = spar.get_img_base_name(i_cam).decode()
# base_name = replace_format_specifiers(base_name) # %d to %04d
self.ptv.write_targets(detections[i_cam], base_name, frame)

print("Frame " + str(frame) + " had " +
repr([s.shape[1] for s in sorted_pos]) + " correspondences.")

# Distinction between quad/trip irrelevant here.
sorted_pos = np.concatenate(sorted_pos, axis=1)
sorted_corresp = np.concatenate(sorted_corresp, axis=1)

flat = np.array([
corrected[i].get_by_pnrs(sorted_corresp[i])
for i in range(len(cals))
])
pos, _ = point_positions(flat.transpose(1, 0, 2), cpar, cals, vpar)

# if len(cals) == 1: # single camera case
# sorted_corresp = np.tile(sorted_corresp,(4,1))
# sorted_corresp[1:,:] = -1

if len(cals) < 4:
print_corresp = -1 * np.ones((4, sorted_corresp.shape[1]))
print_corresp[:len(cals), :] = sorted_corresp
else:
print_corresp = sorted_corresp

# Save rt_is
rt_is_filename = default_naming["corres"].decode()
rt_is_filename = rt_is_filename + f'.{frame}'
with open(rt_is_filename, "w", encoding="utf8") as rt_is:
rt_is.write(str(pos.shape[0]) + "\n")
for pix, pt in enumerate(pos):
pt_args = (pix + 1, ) + tuple(pt) + tuple(print_corresp[:, pix])
rt_is.write("%4d %9.3f %9.3f %9.3f %4d %4d %4d %4d\n" % pt_args)


File renamed without changes.
Loading

0 comments on commit 84d75b7

Please sign in to comment.