Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrypuzyrev committed Oct 4, 2024
2 parents f06fa4b + 95dfbfa commit 5412b25
Show file tree
Hide file tree
Showing 60 changed files with 4,640 additions and 10,311 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code_style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
python_version: ['3.11']
python_version: ['3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python_version }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish_particledetection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python_version: ['3.11']
python_version: ['3.12']
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish_rodtracker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python_version: ['3.11']
python_version: ['3.12']
steps:
- uses: actions/checkout@v4
with:
Expand Down
15 changes: 14 additions & 1 deletion ParticleDetection/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
## [Unreleased]

## [v0.4.3]
### Fixed
- tracking now works as intended ([#96](https://github.com/ANP-Granular/ParticleTracking/issues/96))
- tracking/matching tests were updated according to the fixed tracking issue

## [v0.4.2]
### Added
- support for Python 3.12

### Fixed
- error mitigation for failed projection error binning
- numpy version mismatch ([#90](https://github.com/ANP-Granular/ParticleTracking/issues/90))
- minimum test coverage

## [v0.4.1]
### Added
- support for python3.11
Expand Down Expand Up @@ -51,7 +63,8 @@
### Added
- start versioning

[Unreleased]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.4.1+ParticleTracking...HEAD
[Unreleased]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.4.2+ParticleTracking...HEAD
[v0.4.2]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.4.1+ParticleTracking...v0.4.2+ParticleTracking
[v0.4.1]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.4.0+ParticleTracking...v0.4.1+ParticleTracking
[v0.4.0]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.3.1+ParticleTracking...v0.4.0+ParticleTracking
[v0.3.1]: https://github.com/ANP-Granular/ParticleTracking/compare/v0.3.1+ParticleTracking
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"rotation": [
[
1.0425226613259928,
-0.0004143987226463844,
-0.006185995129884015
],
[
-0.010958551087494509,
-0.9981086439453789,
-0.009663203036154984
],
[
-0.0001673295899603522,
0.0055522462000225925,
-1.0051994470916754
]
],
"translation": [
4.032122379281649,
-1.8661071654922172,
283.13110014701454
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ def assign(
# Matching with a previous frame (+data) available
tmp_df, tmp_costs, tmp_lengths = match_frame(
data,
tmp_df,
cam1_name,
cam2_name,
frame,
Expand Down Expand Up @@ -370,6 +371,7 @@ def assign(

def match_frame(
data: pd.DataFrame,
data_last_frame: pd.DataFrame,
cam1_name: str,
cam2_name: str,
frame: int,
Expand All @@ -391,6 +393,8 @@ def match_frame(
----------
data : DataFrame
Dataset of rod positions.
data_last_frame : DataFrame
Dataset with updated rods data for the last frame.
cam1_name : str
First camera's identifier in the given dataset, e.g. ``"gp1"``.
cam2_name : str
Expand Down Expand Up @@ -548,8 +552,9 @@ def match_frame(

# TODO: check for NaN, so that an error is thrown, if NaNs encountered in
# 3D coordinates
last_points = data.loc[
data.frame == frame - 1, ["x1", "y1", "z1", "x2", "y2", "z2"]
last_points = data_last_frame.loc[
data_last_frame.frame == frame - 1,
["x1", "y1", "z1", "x2", "y2", "z2"],
]
last_points = last_points.to_numpy()
last_points = last_points.reshape((-1, 2, 3))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ def reprojection_errors_hist(reprojection_errors: np.ndarray) -> Figure:
alpha=0.8,
bins=np.arange(0, reprojection_errors.max(), 0.25),
)
except ValueError as e:
if "Maximum allowed size exceeded" in str(e):
except Exception as e:
if ("Maximum allowed size exceeded" in str(e)) or (
"Unable to allocate" in str(e)
):
_logger.warning(f"{e}\nUsing a different binning strategy.")
plt.hist(
reprojection_errors,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def csv_split_by_frames(input_file: str, cut_frames: List[int]) -> List[str]:
if len(next_slice) == 0:
continue
next_slice.reset_index(drop=True, inplace=True)
new_path = base_path + f"_{next_min}_{next_max-1}.csv"
new_path = base_path + f"_{next_min}_{next_max - 1}.csv"
next_slice.to_csv(new_path, sep=",")
written.append(new_path)
return written
Expand Down
6 changes: 3 additions & 3 deletions ParticleDetection/src/ParticleDetection/utils/helper_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,10 @@ def find_world_transform(

transformations = {}

transformations["rot_comb"] = rot_comb.tolist()
transformations["trans_vec"] = trans_vec.tolist()
transformations["rotation"] = rot_comb.tolist()
transformations["translation"] = trans_vec.tolist()

with open(out_json, "w") as fp:
json.dump(transformations, fp)
json.dump(transformations, fp, indent=2)

return rot_comb, trans_vec
3 changes: 2 additions & 1 deletion ParticleDetection/tests/test_reconstruct_3D/test_match_nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def test_assign(tmp_path: Path):


def test_match_frame_nd(example_data: pd.DataFrame):
frame = 508
frame = 507
color = "black"
calibration = dl.load_camera_calibration(EXAMPLES / "gp34.json")
transformation = dl.load_world_transformation(
Expand All @@ -117,6 +117,7 @@ def test_match_frame_nd(example_data: pd.DataFrame):
trans = transformation["translation"]

result = mnd.match_frame(
example_data,
example_data,
"gp3",
"gp4",
Expand Down
18 changes: 17 additions & 1 deletion RodTracker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
## [Unreleased]
## [v0.6.4]

### Added
- missing sections in documentation were added (i.e. world coordinate transformation)
- placeholders (TBDs) were replaced for API documentation

### Changed
- default rod detection models (CPU and CUDA) are now published and downloaded via PyTorch Hub ([#102](https://github.com/ANP-Granular/ParticleTracking/issues/102))
- default number of detected particles changed to 25 for each color (corresponding to default sample data)
- ParticleDetection dependency updated (ParticleDetection v0.4.3 now required)
- reverted to original dialogue window for stereo calibration/world transformation (for consistency with image loading dialogue)

### Fixed
- tracking now works as intended, previously it required to press the "solve" button frame by frame ([#96](https://github.com/ANP-Granular/ParticleTracking/issues/96))
- solved issue when changes in data were not saved and reverted after pressing "solve" button
- tracking/matching tests were updated according to the fixed tracking issue


## [v0.6.3]
### Added
Expand Down
4 changes: 2 additions & 2 deletions RodTracker/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pandas= [
{version = ">=2.1.1", python = ">=3.9"},
{version = ">=1.2.5,<2.1", python = "<3.9"}
]
ParticleDetection = ">=0.4.2.dev21"
ParticleDetection = ">=0.4.3"
platformdirs = ">=3.2.0"
PyQt3D = ">=5.15.5"
PyQt5 = ">=5.15.4"
Expand Down Expand Up @@ -108,7 +108,7 @@ files = [

[tool.black]
line-length = 79
target-version = ['py38', 'py39', 'py310', 'py311']
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
extend-exclude = '''
(
.*mainwindow_layout..*
Expand Down
7 changes: 6 additions & 1 deletion RodTracker/src/RodTracker/backend/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.

"""**TBD**"""
"""
Includes objects and methods used for detection of particles by RodTracker.
**Author:** Adrian Niemann (adrian.niemann@ovgu.de)\n
**Date:** 2022-2024
"""

import logging
from pathlib import Path
Expand Down
8 changes: 7 additions & 1 deletion RodTracker/src/RodTracker/backend/file_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.

"""**TBD**"""
"""
Auxillary methods for getting paths to application icon/logo and opening
documentation.
**Author:** Adrian Niemann (adrian.niemann@ovgu.de)\n
**Date:** 2022-2024
"""

import logging
import os
Expand Down
83 changes: 48 additions & 35 deletions RodTracker/src/RodTracker/backend/img_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.

"""**TBD**"""
"""
Class and methods called in RodTracked GUI application for loading and
selection of images.
**Author:** Adrian Niemann (adrian.niemann@ovgu.de)\n
**Date:** 2022-2024
"""

import logging
from pathlib import Path
from typing import List, Tuple

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtCore, QtGui

import RodTracker.backend.logger as lg
import RodTracker.ui.dialogs as dialogs
Expand Down Expand Up @@ -124,25 +130,20 @@ def select_images(self, pre_selection: str = ""):
if chosen_folder is None:
# File selection was aborted
return
# Find and hand over the first file in the chosen directory
for file in chosen_folder.iterdir():
if file.is_dir():
continue
self.open_image_folder(file)
return
self.open_image_folder(chosen_folder)

def open_image_folder(self, chosen_file: Path):
"""Tries to open an image folder to show the given image.
def open_image_folder(self, chosen_folder: Path):
"""Attempts to open an image folder and show the first image.
All images of the folder from the chosen file's folder are marked for
later display. The selected image is opened immediately. It tries to
extract a camera id from the selected folder and logs the
opening action.
later display. The first image is opened immediately. It tries to
extract a camera id from the selected folder and logs the opening
action.
Parameters
----------
chosen_file: Path
Path to image file chosen for immediate display.
chosen_folder: Path
Path to chosen folder.
Returns
-------
Expand All @@ -157,27 +158,34 @@ def open_image_folder(self, chosen_file: Path):
- :attr:`next_img` [int, int]
- :attr:`data_loaded`
"""
if not chosen_file:
if not chosen_folder:
return
chosen_folder = chosen_folder.resolve()
files, frames = get_images(chosen_folder)
if len(files) == 0:
_logger.warning(
"No valid frames found in the selected folder: "
f"{chosen_folder}"
)
dialogs.show_warning(
"No valid frames found in the selected folder:\n"
f"{chosen_folder}"
)
return
chosen_file = chosen_file.resolve()
frame = int(chosen_file.stem.split("_")[-1])
# Sort according to name / ascending order
files.sort()
frames.sort()

# Open file
loaded_image = QtGui.QImage(str(chosen_file))
loaded_image = QtGui.QImage(str(files[0]))
if loaded_image.isNull():
QtWidgets.QMessageBox.information(
None, "Image Viewer", f"Cannot load {chosen_file}"
)
dialogs.show_warning(f"Cannot load {files[0]}")
return
# Directory
self.folder = chosen_file.parent
self.files, self.frames = get_images(self.folder)
self.frame_idx = self.frames.index(frame)

# Sort according to name / ascending order
desired_file = self.files[self.frame_idx]
self.files.sort()
self.frame_idx = self.files.index(desired_file)
self.frames.sort()
self.folder = chosen_folder
self.files = files
self.frames = frames
self.frame_idx = 0

# Get camera id for data display
self.data_id = self.folder.name
Expand Down Expand Up @@ -303,9 +311,14 @@ def get_images(read_dir: Path) -> Tuple[List[Path], List[int]]:
file_ids = []
for f in read_dir.iterdir():
if f.is_file() and f.suffix in [".png", ".jpg", ".jpeg"]:
# Add all image files to a list
try:
# Split any non-frame describing part of the filename and
# convert to integer (frame number)
tmp_id = int(f.stem.split("_")[-1])
except ValueError:
_logger.debug(f"Invalid file naming: {f}")
continue
# Add all image files and frame IDs to a output
file_ids.append(tmp_id)
files.append(f)
# Split any non-frame describing part of the filename
tmp_id = f.stem.split("_")[-1]
file_ids.append(int(tmp_id))
return files, file_ids
8 changes: 7 additions & 1 deletion RodTracker/src/RodTracker/backend/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.

"""**TBD**"""
"""
Class and methods used in RodTracked GUI for manipulation with particles
coordinate data and logging the resulting actions.
**Author:** Adrian Niemann (adrian.niemann@ovgu.de)\n
**Date:** 2022-2024
"""

import logging
import pathlib
Expand Down
9 changes: 8 additions & 1 deletion RodTracker/src/RodTracker/backend/parallelism.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.

"""**TBD**"""
"""
Classes used in RodTracked GUI for parallel thread work
(some lengthy computations/data loading is performed outside
of the main thread).
**Author:** Adrian Niemann (adrian.niemann@ovgu.de)\n
**Date:** 2022-2024
"""

import sys
from functools import wraps
Expand Down
Loading

0 comments on commit 5412b25

Please sign in to comment.