shinestacker 1.3.1__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- {shinestacker-1.3.1 → shinestacker-1.4.0}/CHANGELOG.md +24 -0
- {shinestacker-1.3.1/src/shinestacker.egg-info → shinestacker-1.4.0}/PKG-INFO +1 -1
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/alignment.md +11 -2
- shinestacker-1.4.0/src/shinestacker/_version.py +1 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/align.py +198 -18
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/align_parallel.py +17 -1
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/balance.py +23 -13
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/noise_detection.py +3 -1
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/utils.py +21 -10
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/vignetting.py +2 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/config/gui_constants.py +2 -2
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/core_utils.py +10 -1
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/action_config.py +172 -7
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/action_config_dialog.py +246 -285
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/gui_run.py +2 -2
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/main_window.py +14 -5
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/menu_manager.py +26 -2
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/project_controller.py +4 -0
- shinestacker-1.4.0/src/shinestacker/gui/recent_file_manager.py +93 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/base_filter.py +5 -5
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/brush_preview.py +3 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/brush_tool.py +11 -11
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/display_manager.py +21 -37
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/image_editor_ui.py +129 -71
- shinestacker-1.4.0/src/shinestacker/retouch/image_view_status.py +61 -0
- shinestacker-1.4.0/src/shinestacker/retouch/image_viewer.py +123 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/io_gui_handler.py +12 -2
- shinestacker-1.4.0/src/shinestacker/retouch/overlaid_view.py +212 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker-1.4.0/src/shinestacker/retouch/sidebyside_view.py +479 -0
- shinestacker-1.4.0/src/shinestacker/retouch/view_strategy.py +466 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0/src/shinestacker.egg-info}/PKG-INFO +1 -1
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker.egg-info/SOURCES.txt +5 -0
- shinestacker-1.3.1/src/shinestacker/_version.py +0 -1
- shinestacker-1.3.1/src/shinestacker/retouch/image_viewer.py +0 -465
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.coveragerc +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.flake8 +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.github/workflows/release.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.gitignore +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.pylintrc +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/LICENSE +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/MANIFEST.in +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/README.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/api.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/balancing.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/conf.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/focus_stacking.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/gui.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/index.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/job.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/main.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/multilayer.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/noise.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/requirements.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/docs/vignetting.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/coffee.gif +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/flies.gif +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/gui-finder.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/index.html +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/pyproject.toml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/requirements.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/scripts/build_release.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/setup.cfg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/main.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.4.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,11 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.4.0] - 2025-09-14
|
|
6
|
+
**GUI improvements**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- added retouch view mode with master and frame side by side and top-bottom
|
|
10
|
+
- implemented "Open Recent" menu entry for both projects and retouch images
|
|
11
|
+
- expert options can be shown with a checkbox in each dialog
|
|
12
|
+
- optional summary plots for alignment transformation parameters
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
- fixed bug in plot generation
|
|
16
|
+
- fixes warning due to missing glyph in PDF generation on macOS
|
|
17
|
+
- safer parallel plot generation using a thread locks
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- code refactoring in various areas
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- code cleanup
|
|
26
|
+
|
|
5
27
|
## [v1.3.1] - 2025-09-08
|
|
28
|
+
**Fixes and optimizations**
|
|
6
29
|
|
|
7
30
|
## Fixed
|
|
8
31
|
- fixed input folder widget in job configuration
|
|
9
32
|
- better management of patological alignments
|
|
33
|
+
- restored alignment match plots
|
|
10
34
|
|
|
11
35
|
### Changed
|
|
12
36
|
- improved automatic parameters for parallel alignment
|
|
@@ -100,12 +100,21 @@ This class has extra parameters, in addition to the above ones:
|
|
|
100
100
|
|
|
101
101
|
* ```max_threads``` (optional, default: ```2```): number of parallel processes allowed. The number of actual threads will not be greater than the number of available CPU cores.
|
|
102
102
|
* ```chunk_submit``` (optional, default: ```True```): submit at most ```max_threads``` parallel processes. If ```chunk_submit``` is greater than ```max_threads``` a moderate performance gain is achieved at the cost of a possibly large memory occupancy.
|
|
103
|
-
* ```bw_matching``` (optional, default: ```False```): perform matches on black and white version of the images in order to save memory. Preliminary tests indicate that the gain with this option is marginal, and this option may be dropped in the future.
|
|
103
|
+
* ```bw_matching``` (optional, default: ```False```): perform matches on black and white version of the images in order to save memory. Preliminary tests indicate that the gain with this option is marginal, and this option may be dropped in the future.
|
|
104
|
+
|
|
105
|
+
## Automatic selection of processing strategy
|
|
106
|
+
|
|
107
|
+
A class ```AlignFramesAuto``` implements alignment with either sequential or parallel processing, and automatically tunes parallel processing parameters.
|
|
108
|
+
This class has extra parameters, in addition to the above ones:
|
|
109
|
+
|
|
110
|
+
* ```mode``` (optional, default: ```auto```): can be ```auto```, ```sequential``` or ```parallel```.
|
|
111
|
+
* ```memory_limit``` (optional, default: 8×1024<sup>3</sup>sup>): memory limit to determine optimal running parameters
|
|
112
|
+
|
|
104
113
|
|
|
105
114
|
## Allowed configurations
|
|
106
115
|
|
|
107
116
|
⚠️ Not all combinations of detector, descriptor and match methods are allowed. Combinations that are not allowed
|
|
108
|
-
give raise to an exception.
|
|
117
|
+
give raise to an exception. This is automatically prevented if one works with the GUI, but may occur when using python scripting. Below the table of the allowed combination with a comparison of CPU performances.
|
|
109
118
|
|
|
110
119
|
## CPU performances
|
|
111
120
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.4.0'
|
|
@@ -5,13 +5,13 @@ import logging
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import cv2
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
|
-
import matplotlib
|
|
9
8
|
from .. config.constants import constants
|
|
10
9
|
from .. core.exceptions import InvalidOptionError
|
|
11
10
|
from .. core.colors import color_str
|
|
11
|
+
from .. core.core_utils import setup_matplotlib_mode
|
|
12
12
|
from .utils import img_8bit, img_bw_8bit, save_plot, img_subsample
|
|
13
13
|
from .stack_framework import SubAction
|
|
14
|
-
|
|
14
|
+
setup_matplotlib_mode()
|
|
15
15
|
|
|
16
16
|
_DEFAULT_FEATURE_CONFIG = {
|
|
17
17
|
'detector': constants.DEFAULT_DETECTOR,
|
|
@@ -135,14 +135,14 @@ def check_homography_distortion(m, img_shape, homography_thresholds=_HOMOGRAPHY_
|
|
|
135
135
|
(area_ratio, aspect_ratio, max_angle_dev)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def check_transform(m,
|
|
138
|
+
def check_transform(m, img_shape, transform_type,
|
|
139
139
|
affine_thresholds, homography_thresholds):
|
|
140
140
|
if transform_type == constants.ALIGN_RIGID:
|
|
141
141
|
return check_affine_matrix(
|
|
142
|
-
m,
|
|
142
|
+
m, img_shape, affine_thresholds)
|
|
143
143
|
if transform_type == constants.ALIGN_HOMOGRAPHY:
|
|
144
144
|
return check_homography_distortion(
|
|
145
|
-
m,
|
|
145
|
+
m, img_shape, homography_thresholds)
|
|
146
146
|
return False, f'invalid transfrom option {transform_type}', None
|
|
147
147
|
|
|
148
148
|
|
|
@@ -251,7 +251,10 @@ def find_transform(src_pts, dst_pts, transform=constants.DEFAULT_TRANSFORM,
|
|
|
251
251
|
confidence=align_confidence / 100.0,
|
|
252
252
|
refineIters=refine_iters)
|
|
253
253
|
else:
|
|
254
|
-
raise InvalidOptionError(
|
|
254
|
+
raise InvalidOptionError(
|
|
255
|
+
'transform', method,
|
|
256
|
+
f". Valid options are: {constants.ALIGN_HOMOGRAPHY}, {constants.ALIGN_RIGID}"
|
|
257
|
+
)
|
|
255
258
|
return result
|
|
256
259
|
|
|
257
260
|
|
|
@@ -349,9 +352,11 @@ def align_images(img_ref, img_0, feature_config=None, matching_config=None, alig
|
|
|
349
352
|
if m is None:
|
|
350
353
|
raise InvalidOptionError("transform", transform)
|
|
351
354
|
transform_type = alignment_config['transform']
|
|
352
|
-
is_valid, reason,
|
|
353
|
-
m, img_0, transform_type,
|
|
355
|
+
is_valid, reason, result = check_transform(
|
|
356
|
+
m, img_0.shape, transform_type,
|
|
354
357
|
affine_thresholds, homography_thresholds)
|
|
358
|
+
if callbacks and 'save_transform_result' in callbacks:
|
|
359
|
+
callbacks['save_transform_result'](result)
|
|
355
360
|
if not is_valid:
|
|
356
361
|
if callbacks and 'warning' in callbacks:
|
|
357
362
|
callbacks['warning'](f"invalid transformation: {reason}")
|
|
@@ -407,6 +412,18 @@ class AlignFramesBase(SubAction):
|
|
|
407
412
|
for k in self.alignment_config:
|
|
408
413
|
if k in kwargs:
|
|
409
414
|
self.alignment_config[k] = kwargs[k]
|
|
415
|
+
self._area_ratio = None
|
|
416
|
+
self._aspect_ratio = None
|
|
417
|
+
self._max_angle_dev = None
|
|
418
|
+
self._scale_x = None
|
|
419
|
+
self._scale_y = None
|
|
420
|
+
self._translation_x = None
|
|
421
|
+
self._translation_y = None
|
|
422
|
+
self._rotation = None
|
|
423
|
+
self._shear = None
|
|
424
|
+
|
|
425
|
+
def relative_transformation(self):
|
|
426
|
+
return None
|
|
410
427
|
|
|
411
428
|
def align_images(self, idx, img_ref, img_0):
|
|
412
429
|
pass
|
|
@@ -417,6 +434,15 @@ class AlignFramesBase(SubAction):
|
|
|
417
434
|
def begin(self, process):
|
|
418
435
|
self.process = process
|
|
419
436
|
self._n_good_matches = np.zeros(process.total_action_counts)
|
|
437
|
+
self._area_ratio = np.ones(process.total_action_counts)
|
|
438
|
+
self._aspect_ratio = np.ones(process.total_action_counts)
|
|
439
|
+
self._max_angle_dev = np.zeros(process.total_action_counts)
|
|
440
|
+
self._scale_x = np.ones(process.total_action_counts)
|
|
441
|
+
self._scale_y = np.ones(process.total_action_counts)
|
|
442
|
+
self._translation_x = np.zeros(process.total_action_counts)
|
|
443
|
+
self._translation_y = np.zeros(process.total_action_counts)
|
|
444
|
+
self._rotation = np.zeros(process.total_action_counts)
|
|
445
|
+
self._shear = np.zeros(process.total_action_counts)
|
|
420
446
|
|
|
421
447
|
def run_frame(self, idx, ref_idx, img_0):
|
|
422
448
|
if idx == self.process.ref_idx:
|
|
@@ -432,24 +458,29 @@ class AlignFramesBase(SubAction):
|
|
|
432
458
|
f"{os.path.basename(self.process.input_filepath(idx))}"
|
|
433
459
|
|
|
434
460
|
def end(self):
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
x = np.arange(1, len(
|
|
461
|
+
|
|
462
|
+
def get_coordinates(items):
|
|
463
|
+
x = np.arange(1, len(items) + 1, dtype=int)
|
|
438
464
|
no_ref = x != self.process.ref_idx + 1
|
|
439
465
|
x = x[no_ref]
|
|
440
|
-
y = np.array(
|
|
466
|
+
y = np.array(items)[no_ref]
|
|
441
467
|
if self.process.ref_idx == 0:
|
|
442
|
-
|
|
468
|
+
y_ref = y[1]
|
|
443
469
|
elif self.process.ref_idx >= len(y):
|
|
444
|
-
|
|
470
|
+
y_ref = y[-1]
|
|
445
471
|
else:
|
|
446
|
-
|
|
472
|
+
y_ref = (y[self.process.ref_idx - 1] + y[self.process.ref_idx]) / 2
|
|
473
|
+
return x, y, y_ref
|
|
447
474
|
|
|
475
|
+
if self.plot_summary:
|
|
476
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
477
|
+
x, y, y_ref = get_coordinates(self._n_good_matches)
|
|
448
478
|
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
449
|
-
[0,
|
|
479
|
+
[0, y_ref], color='cornflowerblue', linestyle='--', label='reference frame')
|
|
450
480
|
plt.plot([x[0], x[-1]], [self.min_matches, self.min_matches], color='lightgray',
|
|
451
481
|
linestyle='--', label='min. matches')
|
|
452
482
|
plt.plot(x, y, color='navy', label='matches')
|
|
483
|
+
plt.title("Number of matches")
|
|
453
484
|
plt.xlabel('frame')
|
|
454
485
|
plt.ylabel('# of matches')
|
|
455
486
|
plt.legend()
|
|
@@ -458,15 +489,160 @@ class AlignFramesBase(SubAction):
|
|
|
458
489
|
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
459
490
|
f"{self.process.name}-matches.pdf"
|
|
460
491
|
save_plot(plot_path)
|
|
461
|
-
plt.close('all')
|
|
462
492
|
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
463
493
|
f"{self.process.name}: matches", plot_path)
|
|
494
|
+
transform = self.alignment_config['transform']
|
|
495
|
+
title = "Transformation parameters rel. to reference frame"
|
|
496
|
+
if transform == constants.ALIGN_RIGID:
|
|
497
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
498
|
+
x, y, y_ref = get_coordinates(self._rotation)
|
|
499
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
500
|
+
[0, y_ref], color='cornflowerblue',
|
|
501
|
+
linestyle='--', label='reference frame')
|
|
502
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
503
|
+
plt.plot(x, y, color='navy', label='rotation (°)')
|
|
504
|
+
y_lim = max(abs(y.min()), abs(y.max())) * 1.1
|
|
505
|
+
plt.ylim(-y_lim, y_lim)
|
|
506
|
+
plt.title(title)
|
|
507
|
+
plt.xlabel('frame')
|
|
508
|
+
plt.ylabel('rotation angle (degrees)')
|
|
509
|
+
plt.legend()
|
|
510
|
+
plt.xlim(x[0], x[-1])
|
|
511
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
512
|
+
f"{self.process.name}-rotation.pdf"
|
|
513
|
+
save_plot(plot_path)
|
|
514
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
515
|
+
f"{self.process.name}: rotation", plot_path)
|
|
516
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
517
|
+
x, y_x, y_x_ref = get_coordinates(self._translation_x)
|
|
518
|
+
x, y_y, y_y_ref = get_coordinates(self._translation_y)
|
|
519
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
520
|
+
[y_x_ref, y_y_ref], color='cornflowerblue',
|
|
521
|
+
linestyle='--', label='reference frame')
|
|
522
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
523
|
+
plt.plot(x, y_x, color='blue', label='translation, x (px)')
|
|
524
|
+
plt.plot(x, y_y, color='red', label='translation, y (px)')
|
|
525
|
+
y_lim = max(abs(y_x.min()), abs(y_x.max()), abs(y_y.min()), abs(y_y.max())) * 1.1
|
|
526
|
+
plt.ylim(-y_lim, y_lim)
|
|
527
|
+
plt.title(title)
|
|
528
|
+
plt.xlabel('frame')
|
|
529
|
+
plt.ylabel('translation (pixels)')
|
|
530
|
+
plt.legend()
|
|
531
|
+
plt.xlim(x[0], x[-1])
|
|
532
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
533
|
+
f"{self.process.name}-translation.pdf"
|
|
534
|
+
save_plot(plot_path)
|
|
535
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
536
|
+
f"{self.process.name}: translation", plot_path)
|
|
537
|
+
|
|
538
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
539
|
+
x, y, y_ref = get_coordinates(self._scale_x)
|
|
540
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
541
|
+
[1, y_ref], color='cornflowerblue',
|
|
542
|
+
linestyle='--', label='reference frame')
|
|
543
|
+
plt.plot([x[0], x[-1]], [1, 1], color='cornflowerblue', linestyle='--')
|
|
544
|
+
plt.plot(x, y, color='blue', label='scale factor')
|
|
545
|
+
d_max = max(abs(y.min() - 1), abs(y.max() - 1)) * 1.1
|
|
546
|
+
plt.ylim(1.0 - d_max, 1.0 + d_max)
|
|
547
|
+
plt.title(title)
|
|
548
|
+
plt.xlabel('frame')
|
|
549
|
+
plt.ylabel('scale factor')
|
|
550
|
+
plt.legend()
|
|
551
|
+
plt.xlim(x[0], x[-1])
|
|
552
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
553
|
+
f"{self.process.name}-scale.pdf"
|
|
554
|
+
save_plot(plot_path)
|
|
555
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
556
|
+
f"{self.process.name}: scale", plot_path)
|
|
557
|
+
elif transform == constants.ALIGN_HOMOGRAPHY:
|
|
558
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
559
|
+
x, y, y_ref = get_coordinates(self._area_ratio)
|
|
560
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
561
|
+
[0, y_ref], color='cornflowerblue',
|
|
562
|
+
linestyle='--', label='reference frame')
|
|
563
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
564
|
+
plt.plot(x, y, color='navy', label='area ratio')
|
|
565
|
+
d_max = max(abs(y.min() - 1), abs(y.max() - 1)) * 1.1
|
|
566
|
+
plt.ylim(1.0 - d_max, 1.0 + d_max)
|
|
567
|
+
plt.title(title)
|
|
568
|
+
plt.xlabel('frame')
|
|
569
|
+
plt.ylabel('warped area ratio')
|
|
570
|
+
plt.legend()
|
|
571
|
+
plt.xlim(x[0], x[-1])
|
|
572
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
573
|
+
f"{self.process.name}-area-ratio.pdf"
|
|
574
|
+
save_plot(plot_path)
|
|
575
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
576
|
+
f"{self.process.name}: area ratio", plot_path)
|
|
577
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
578
|
+
x, y, y_ref = get_coordinates(self._aspect_ratio)
|
|
579
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
580
|
+
[0, y_ref], color='cornflowerblue',
|
|
581
|
+
linestyle='--', label='reference frame')
|
|
582
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
583
|
+
plt.plot(x, y, color='navy', label='aspect ratio')
|
|
584
|
+
y_min, y_max = y.min(), y.max()
|
|
585
|
+
delta = y_max - y_min
|
|
586
|
+
plt.ylim(y_min - 0.05 * delta, y_max + 0.05 * delta)
|
|
587
|
+
plt.title(title)
|
|
588
|
+
plt.xlabel('frame')
|
|
589
|
+
plt.ylabel('aspect ratio')
|
|
590
|
+
plt.legend()
|
|
591
|
+
plt.xlim(x[0], x[-1])
|
|
592
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
593
|
+
f"{self.process.name}-aspect-ratio.pdf"
|
|
594
|
+
save_plot(plot_path)
|
|
595
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
596
|
+
f"{self.process.name}: aspect ratio", plot_path)
|
|
597
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
598
|
+
x, y, y_ref = get_coordinates(self._max_angle_dev)
|
|
599
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
600
|
+
[0, y_ref], color='cornflowerblue',
|
|
601
|
+
linestyle='--', label='reference frame')
|
|
602
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
603
|
+
plt.plot(x, y, color='navy', label='max. dev. ang. (°)')
|
|
604
|
+
y_lim = max(abs(y.min()), abs(y.max())) * 1.1
|
|
605
|
+
plt.ylim(-y_lim, y_lim)
|
|
606
|
+
plt.title(title)
|
|
607
|
+
plt.xlabel('frame')
|
|
608
|
+
plt.ylabel('max deviation angle (degrees)')
|
|
609
|
+
plt.legend()
|
|
610
|
+
plt.xlim(x[0], x[-1])
|
|
611
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
612
|
+
f"{self.process.name}-rotation.pdf"
|
|
613
|
+
save_plot(plot_path)
|
|
614
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
615
|
+
f"{self.process.name}: rotation", plot_path)
|
|
616
|
+
|
|
617
|
+
def save_transform_result(self, idx, result):
|
|
618
|
+
if result is None:
|
|
619
|
+
return
|
|
620
|
+
transform = self.alignment_config['transform']
|
|
621
|
+
if transform == constants.ALIGN_HOMOGRAPHY:
|
|
622
|
+
area_ratio, aspect_ratio, max_angle_dev = result
|
|
623
|
+
self._area_ratio[idx] = area_ratio
|
|
624
|
+
self._aspect_ratio[idx] = aspect_ratio
|
|
625
|
+
self._max_angle_dev[idx] = max_angle_dev
|
|
626
|
+
elif transform == constants.ALIGN_RIGID:
|
|
627
|
+
scale_x, scale_y, translation_x, translation_y, rotation, shear = result
|
|
628
|
+
self._scale_x[idx] = scale_x
|
|
629
|
+
self._scale_y[idx] = scale_y
|
|
630
|
+
self._translation_x[idx] = translation_x
|
|
631
|
+
self._translation_y[idx] = translation_y
|
|
632
|
+
self._rotation[idx] = rotation
|
|
633
|
+
self._shear[idx] = shear
|
|
634
|
+
else:
|
|
635
|
+
raise InvalidOptionError(
|
|
636
|
+
'transform', transform,
|
|
637
|
+
f". Valid options are: {constants.ALIGN_HOMOGRAPHY}, {constants.ALIGN_RIGID}"
|
|
638
|
+
)
|
|
464
639
|
|
|
465
640
|
|
|
466
641
|
class AlignFrames(AlignFramesBase):
|
|
467
642
|
def align_images(self, idx, img_ref, img_0):
|
|
468
643
|
idx_str = f"{idx:04d}"
|
|
469
644
|
idx_tot_str = self.process.idx_tot_str(idx)
|
|
645
|
+
|
|
470
646
|
callbacks = {
|
|
471
647
|
'message': lambda: self.print_message(f'{idx_tot_str}: find matches'),
|
|
472
648
|
'matches_message': lambda n: self.print_message(f'{idx_tot_str}: good matches: {n}'),
|
|
@@ -476,7 +652,8 @@ class AlignFrames(AlignFramesBase):
|
|
|
476
652
|
f': {msg}', constants.LOG_COLOR_WARNING),
|
|
477
653
|
'save_plot': lambda plot_path: self.process.callback(
|
|
478
654
|
constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
479
|
-
f"{self.process.name}: matches\nframe {idx_str}", plot_path)
|
|
655
|
+
f"{self.process.name}: matches\nframe {idx_str}", plot_path),
|
|
656
|
+
'save_transform_result': lambda result: self.save_transform_result(idx, result)
|
|
480
657
|
}
|
|
481
658
|
if self.plot_matches:
|
|
482
659
|
plot_path = os.path.join(
|
|
@@ -504,5 +681,8 @@ class AlignFrames(AlignFramesBase):
|
|
|
504
681
|
return None
|
|
505
682
|
return img
|
|
506
683
|
|
|
684
|
+
def relative_transformation(self):
|
|
685
|
+
return False
|
|
686
|
+
|
|
507
687
|
def sequential_processing(self):
|
|
508
688
|
return True
|
|
@@ -39,6 +39,7 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
39
39
|
self.chunk_submit = kwargs.get('chunk_submit', constants.DEFAULT_ALIGN_CHUNK_SUBMIT)
|
|
40
40
|
self.bw_matching = kwargs.get('bw_matching', constants.DEFAULT_ALIGN_BW_MATCHING)
|
|
41
41
|
self._img_cache = None
|
|
42
|
+
self._img_shapes = None
|
|
42
43
|
self._img_locks = None
|
|
43
44
|
self._cache_locks = None
|
|
44
45
|
self._target_indices = None
|
|
@@ -48,6 +49,9 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
48
49
|
self._kp = None
|
|
49
50
|
self._des = None
|
|
50
51
|
|
|
52
|
+
def relative_transformation(self):
|
|
53
|
+
return True
|
|
54
|
+
|
|
51
55
|
def cache_img(self, idx):
|
|
52
56
|
with self._cache_locks[idx]:
|
|
53
57
|
self._img_locks[idx] += 1
|
|
@@ -56,6 +60,8 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
56
60
|
if self.bw_matching:
|
|
57
61
|
img = img_bw(img)
|
|
58
62
|
self._img_cache[idx] = img
|
|
63
|
+
if img is not None:
|
|
64
|
+
self._img_shapes[idx] = img.shape
|
|
59
65
|
return self._img_cache[idx]
|
|
60
66
|
|
|
61
67
|
def submit_threads(self, idxs, imgs):
|
|
@@ -112,6 +118,7 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
112
118
|
self.process.id, self.process.name, 2 * n_frames)
|
|
113
119
|
input_filepaths = self.process.input_filepaths()
|
|
114
120
|
self._img_cache = [None] * n_frames
|
|
121
|
+
self._img_shapes = [None] * n_frames
|
|
115
122
|
self._img_locks = [0] * n_frames
|
|
116
123
|
self._cache_locks = [threading.Lock() for _ in range(n_frames)]
|
|
117
124
|
self._target_indices = [None] * n_frames
|
|
@@ -168,9 +175,17 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
168
175
|
self._transforms[idx] = None
|
|
169
176
|
gc.collect()
|
|
170
177
|
missing_transforms = 0
|
|
178
|
+
thresholds = self.get_transform_thresholds()
|
|
171
179
|
for i in range(n_frames):
|
|
172
180
|
if self._cumulative_transforms[i] is not None:
|
|
173
181
|
self._cumulative_transforms[i] = self._cumulative_transforms[i].astype(np.float32)
|
|
182
|
+
is_valid, _reason, result = check_transform(
|
|
183
|
+
self._cumulative_transforms[i], self._img_shapes[i],
|
|
184
|
+
transform_type, *thresholds)
|
|
185
|
+
if is_valid:
|
|
186
|
+
self.save_transform_result(i, result)
|
|
187
|
+
else:
|
|
188
|
+
self._cumulative_transforms[i] = None
|
|
174
189
|
else:
|
|
175
190
|
missing_transforms += 1
|
|
176
191
|
msg = "feature extaction completed"
|
|
@@ -277,7 +292,8 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
277
292
|
return self.extract_features(idx, delta + 1)
|
|
278
293
|
transform_type = self.alignment_config['transform']
|
|
279
294
|
thresholds = self.get_transform_thresholds()
|
|
280
|
-
is_valid, _reason, _result = check_transform(m, img_0, transform_type, *thresholds)
|
|
295
|
+
is_valid, _reason, _result = check_transform(m, img_0.shape, transform_type, *thresholds)
|
|
296
|
+
# self.save_transform_result(idx, result)
|
|
281
297
|
if not is_valid:
|
|
282
298
|
msg = f"invalid transformation for {self.image_str(idx)}"
|
|
283
299
|
do_abort = self.alignment_config['abort_abnormal']
|
|
@@ -8,9 +8,11 @@ from scipy.interpolate import interp1d
|
|
|
8
8
|
from .. config.constants import constants
|
|
9
9
|
from .. core.exceptions import InvalidOptionError
|
|
10
10
|
from .. core.colors import color_str
|
|
11
|
+
from .. core.core_utils import setup_matplotlib_mode
|
|
11
12
|
from .utils import (read_img, save_plot, img_subsample, bgr_to_hsv, bgr_to_hls,
|
|
12
13
|
hsv_to_bgr, hls_to_bgr, bgr_to_lab, lab_to_bgr)
|
|
13
14
|
from .stack_framework import SubAction
|
|
15
|
+
setup_matplotlib_mode()
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class BaseHistogrammer:
|
|
@@ -40,12 +42,11 @@ class BaseHistogrammer:
|
|
|
40
42
|
x_values = np.linspace(0, self.max_pixel_value, len(hist))
|
|
41
43
|
ax.plot(x_values, hist, color=color, alpha=alpha)
|
|
42
44
|
|
|
43
|
-
def save_plot(self, idx):
|
|
45
|
+
def save_plot(self, idx, fig=None):
|
|
44
46
|
idx_str = f"{idx:04d}"
|
|
45
47
|
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
46
48
|
f"{self.process.name}-hist-{idx_str}.pdf"
|
|
47
|
-
save_plot(plot_path)
|
|
48
|
-
plt.close('all')
|
|
49
|
+
save_plot(plot_path, fig)
|
|
49
50
|
self.process.callback(
|
|
50
51
|
'save_plot',
|
|
51
52
|
self.process.id, f"{self.process.name}: balance\nframe {idx_str}",
|
|
@@ -56,7 +57,6 @@ class BaseHistogrammer:
|
|
|
56
57
|
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
57
58
|
f"{self.process.name}-{name}.pdf"
|
|
58
59
|
save_plot(plot_path)
|
|
59
|
-
plt.close('all')
|
|
60
60
|
self.process.callback(
|
|
61
61
|
'save_plot', self.process.id,
|
|
62
62
|
f"{self.process.name}: {name}", plot_path
|
|
@@ -69,13 +69,14 @@ class LumiHistogrammer(BaseHistogrammer):
|
|
|
69
69
|
self.colors = ("r", "g", "b")
|
|
70
70
|
|
|
71
71
|
def generate_frame_plot(self, idx, hist, chans, calc_hist_func):
|
|
72
|
-
|
|
72
|
+
fig, axs = plt.subplots(1, 2, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
73
73
|
self.histo_plot(axs[0], hist, "pixel luminosity", 'black')
|
|
74
74
|
for (chan, color) in zip(chans, self.colors):
|
|
75
75
|
hist_col = calc_hist_func(chan)
|
|
76
76
|
self.histo_plot(axs[1], hist_col, "R, G, B intensity", color, alpha=0.5)
|
|
77
|
+
fig.suptitle("Image histograms")
|
|
77
78
|
plt.xlim(0, self.max_pixel_value)
|
|
78
|
-
self.save_plot(idx)
|
|
79
|
+
self.save_plot(idx, fig)
|
|
79
80
|
|
|
80
81
|
def generate_summary_plot(self, ref_idx):
|
|
81
82
|
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
@@ -86,6 +87,7 @@ class LumiHistogrammer(BaseHistogrammer):
|
|
|
86
87
|
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
87
88
|
label='no correction')
|
|
88
89
|
plt.plot(x, y, color='navy', label='luminosity correction')
|
|
90
|
+
plt.title("Image balance correction")
|
|
89
91
|
plt.xlabel('frame')
|
|
90
92
|
plt.ylabel('correction')
|
|
91
93
|
plt.legend()
|
|
@@ -100,11 +102,12 @@ class RGBHistogrammer(BaseHistogrammer):
|
|
|
100
102
|
self.colors = ("r", "g", "b")
|
|
101
103
|
|
|
102
104
|
def generate_frame_plot(self, idx, hists):
|
|
103
|
-
|
|
105
|
+
fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
104
106
|
for c in [2, 1, 0]:
|
|
105
107
|
self.histo_plot(axs[c], hists[c], self.colors[c] + " luminosity", self.colors[c])
|
|
108
|
+
fig.suptitle("Image histograms")
|
|
106
109
|
plt.xlim(0, self.max_pixel_value)
|
|
107
|
-
self.save_plot(idx)
|
|
110
|
+
self.save_plot(idx, fig)
|
|
108
111
|
|
|
109
112
|
def generate_summary_plot(self, ref_idx):
|
|
110
113
|
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
@@ -118,6 +121,7 @@ class RGBHistogrammer(BaseHistogrammer):
|
|
|
118
121
|
plt.plot(x, y[:, 0], color='r', label='R correction')
|
|
119
122
|
plt.plot(x, y[:, 1], color='g', label='G correction')
|
|
120
123
|
plt.plot(x, y[:, 2], color='b', label='B correction')
|
|
124
|
+
plt.title("Image balance correction")
|
|
121
125
|
plt.xlabel('frame')
|
|
122
126
|
plt.ylabel('correction')
|
|
123
127
|
plt.legend()
|
|
@@ -133,10 +137,12 @@ class Ch1Histogrammer(BaseHistogrammer):
|
|
|
133
137
|
self.colors = colors
|
|
134
138
|
|
|
135
139
|
def generate_frame_plot(self, idx, hists):
|
|
136
|
-
|
|
140
|
+
fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
137
141
|
for c in range(3):
|
|
138
142
|
self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
|
|
139
|
-
|
|
143
|
+
fig.suptitle("Image histograms")
|
|
144
|
+
for ax in axs:
|
|
145
|
+
ax.set_xlim(0, self.max_pixel_value)
|
|
140
146
|
self.save_plot(idx)
|
|
141
147
|
|
|
142
148
|
def generate_summary_plot(self, ref_idx):
|
|
@@ -149,6 +155,7 @@ class Ch1Histogrammer(BaseHistogrammer):
|
|
|
149
155
|
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
150
156
|
label='no correction')
|
|
151
157
|
plt.plot(x, y[:, 0], color=self.colors[0], label=self.labels[0] + ' correction')
|
|
158
|
+
plt.title("Image balance correction")
|
|
152
159
|
plt.xlabel('frame')
|
|
153
160
|
plt.ylabel('correction')
|
|
154
161
|
plt.legend()
|
|
@@ -164,10 +171,12 @@ class Ch2Histogrammer(BaseHistogrammer):
|
|
|
164
171
|
self.colors = colors
|
|
165
172
|
|
|
166
173
|
def generate_frame_plot(self, idx, hists):
|
|
167
|
-
|
|
174
|
+
fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
168
175
|
for c in range(3):
|
|
169
176
|
self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
|
|
170
|
-
|
|
177
|
+
fig.suptitle("Image histograms")
|
|
178
|
+
for ax in axs:
|
|
179
|
+
ax.set_xlim(0, self.max_pixel_value)
|
|
171
180
|
self.save_plot(idx)
|
|
172
181
|
|
|
173
182
|
def generate_summary_plot(self, ref_idx):
|
|
@@ -181,6 +190,7 @@ class Ch2Histogrammer(BaseHistogrammer):
|
|
|
181
190
|
label='no correction')
|
|
182
191
|
plt.plot(x, y[:, 0], color=self.colors[1], label=self.labels[1] + ' correction')
|
|
183
192
|
plt.plot(x, y[:, 1], color=self.colors[2], label=self.labels[2] + ' correction')
|
|
193
|
+
plt.title("Image balance correction")
|
|
184
194
|
plt.xlabel('frame')
|
|
185
195
|
plt.ylabel('correction')
|
|
186
196
|
plt.legend()
|
|
@@ -603,7 +613,7 @@ class BalanceFrames(SubAction):
|
|
|
603
613
|
mask_radius = int(min(*shape) * self.mask_size / 2)
|
|
604
614
|
cv2.circle(img, (shape[1] // 2, shape[0] // 2), mask_radius, 255, -1)
|
|
605
615
|
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
606
|
-
plt.title('
|
|
616
|
+
plt.title('Image balance mask')
|
|
607
617
|
plt.imshow(img, 'gray')
|
|
608
618
|
self.correction.histogrammer.save_summary_plot("mask")
|
|
609
619
|
|
|
@@ -10,10 +10,12 @@ from .. config.constants import constants
|
|
|
10
10
|
from .. core.colors import color_str
|
|
11
11
|
from .. core.exceptions import ImageLoadError
|
|
12
12
|
from .. core.framework import TaskBase
|
|
13
|
-
from .. core.core_utils import make_tqdm_bar
|
|
13
|
+
from .. core.core_utils import make_tqdm_bar, setup_matplotlib_mode
|
|
14
14
|
from .. core.exceptions import RunStopException, ShapeError
|
|
15
15
|
from .stack_framework import ImageSequenceManager, SubAction
|
|
16
16
|
from .utils import read_img, save_plot, get_img_metadata, validate_image
|
|
17
|
+
setup_matplotlib_mode()
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
MAX_NOISY_PIXELS = 1000
|
|
19
21
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0116, E1101, R0914
|
|
2
2
|
import os
|
|
3
|
+
import gc
|
|
3
4
|
import logging
|
|
5
|
+
import threading
|
|
4
6
|
import numpy as np
|
|
5
7
|
import cv2
|
|
6
8
|
import matplotlib.pyplot as plt
|
|
@@ -50,6 +52,10 @@ def extension_jpg_png(path):
|
|
|
50
52
|
return extension_in(path, EXTENSIONS_JPG + EXTENSIONS_PNG)
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
def extension_jpg_tif_png(path):
|
|
56
|
+
return extension_in(path, EXTENSIONS_JPG + EXTENSIONS_TIF + EXTENSIONS_PNG)
|
|
57
|
+
|
|
58
|
+
|
|
53
59
|
def read_img(file_path):
|
|
54
60
|
if not os.path.isfile(file_path):
|
|
55
61
|
raise RuntimeError("File does not exist: " + file_path)
|
|
@@ -124,17 +130,22 @@ def read_and_validate_img(filename, expected_shape=None, expected_dtype=None):
|
|
|
124
130
|
return validate_image(read_img(filename), expected_shape, expected_dtype)
|
|
125
131
|
|
|
126
132
|
|
|
127
|
-
def save_plot(filename):
|
|
133
|
+
def save_plot(filename, fig=None):
|
|
128
134
|
logging.getLogger(__name__).debug(msg=f"save plot file: {filename}")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
dir_path =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
save_lock = threading.Lock()
|
|
136
|
+
with save_lock:
|
|
137
|
+
dir_path = os.path.dirname(filename)
|
|
138
|
+
if not dir_path:
|
|
139
|
+
dir_path = '.'
|
|
140
|
+
if not os.path.isdir(dir_path):
|
|
141
|
+
os.makedirs(dir_path)
|
|
142
|
+
if fig is None:
|
|
143
|
+
fig = plt.gcf()
|
|
144
|
+
fig.savefig(filename, dpi=150)
|
|
145
|
+
if config.JUPYTER_NOTEBOOK:
|
|
146
|
+
plt.show()
|
|
147
|
+
plt.close(fig)
|
|
148
|
+
gc.collect()
|
|
138
149
|
|
|
139
150
|
|
|
140
151
|
def img_subsample(img, subsample, fast=True):
|
|
@@ -7,9 +7,11 @@ import matplotlib.pyplot as plt
|
|
|
7
7
|
from scipy.optimize import curve_fit, fsolve
|
|
8
8
|
import cv2
|
|
9
9
|
from .. core.colors import color_str
|
|
10
|
+
from .. core.core_utils import setup_matplotlib_mode
|
|
10
11
|
from .. config.constants import constants
|
|
11
12
|
from .utils import img_8bit, save_plot, img_subsample
|
|
12
13
|
from .stack_framework import SubAction
|
|
14
|
+
setup_matplotlib_mode()
|
|
13
15
|
|
|
14
16
|
CLIP_EXP = 10
|
|
15
17
|
|