ChessAnalysisPipeline 0.0.11__py3-none-any.whl → 0.0.13__py3-none-any.whl
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 ChessAnalysisPipeline might be problematic. Click here for more details.
- CHAP/__init__.py +2 -0
- CHAP/common/__init__.py +6 -2
- CHAP/common/models/map.py +217 -70
- CHAP/common/processor.py +249 -155
- CHAP/common/reader.py +175 -130
- CHAP/common/writer.py +150 -94
- CHAP/edd/models.py +458 -262
- CHAP/edd/processor.py +614 -354
- CHAP/edd/utils.py +746 -235
- CHAP/tomo/models.py +22 -18
- CHAP/tomo/processor.py +1215 -892
- CHAP/utils/fit.py +211 -127
- CHAP/utils/general.py +789 -610
- CHAP/utils/parfile.py +1 -9
- CHAP/utils/scanparsers.py +101 -52
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/RECORD +21 -21
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/WHEEL +1 -1
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/top_level.txt +0 -0
CHAP/utils/general.py
CHANGED
|
@@ -26,8 +26,8 @@ from sys import float_info
|
|
|
26
26
|
import numpy as np
|
|
27
27
|
try:
|
|
28
28
|
import matplotlib.pyplot as plt
|
|
29
|
-
|
|
30
|
-
from matplotlib
|
|
29
|
+
from matplotlib.widgets import AxesWidget, RadioButtons
|
|
30
|
+
from matplotlib import cbook
|
|
31
31
|
except ImportError:
|
|
32
32
|
pass
|
|
33
33
|
|
|
@@ -437,7 +437,7 @@ def index_nearest(a, value):
|
|
|
437
437
|
return (int)(np.argmin(np.abs(a-value)))
|
|
438
438
|
|
|
439
439
|
|
|
440
|
-
def
|
|
440
|
+
def index_nearest_down(a, value):
|
|
441
441
|
"""Return index of nearest array value, rounded down"""
|
|
442
442
|
a = np.asarray(a)
|
|
443
443
|
if a.ndim > 1:
|
|
@@ -461,6 +461,23 @@ def index_nearest_upp(a, value):
|
|
|
461
461
|
return index
|
|
462
462
|
|
|
463
463
|
|
|
464
|
+
def get_consecutive_int_range(a):
|
|
465
|
+
"""Return a list of pairs of integers marking consecutive ranges
|
|
466
|
+
of integers."""
|
|
467
|
+
a.sort()
|
|
468
|
+
i = 0
|
|
469
|
+
int_ranges = []
|
|
470
|
+
while i < len(a):
|
|
471
|
+
j = i
|
|
472
|
+
while j < len(a)-1:
|
|
473
|
+
if a[j+1] > 1 + a[j]:
|
|
474
|
+
break
|
|
475
|
+
j += 1
|
|
476
|
+
int_ranges.append([a[i], a[j]])
|
|
477
|
+
i = j+1
|
|
478
|
+
return int_ranges
|
|
479
|
+
|
|
480
|
+
|
|
464
481
|
def round_to_n(x, n=1):
|
|
465
482
|
"""Round to a specific number of decimals."""
|
|
466
483
|
if x == 0.0:
|
|
@@ -880,641 +897,818 @@ def file_exists_and_readable(f):
|
|
|
880
897
|
return f
|
|
881
898
|
|
|
882
899
|
|
|
883
|
-
def
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
:type
|
|
893
|
-
:param
|
|
894
|
-
None
|
|
895
|
-
:type
|
|
896
|
-
:param label:
|
|
897
|
-
None
|
|
900
|
+
def select_mask_1d(
|
|
901
|
+
y, x=None, label=None, ref_data=[], preselected_index_ranges=None,
|
|
902
|
+
preselected_mask=None, title=None, xlabel=None, ylabel=None,
|
|
903
|
+
min_num_index_ranges=None, max_num_index_ranges=None,
|
|
904
|
+
interactive=True):
|
|
905
|
+
"""Display a lineplot and have the user select a mask.
|
|
906
|
+
|
|
907
|
+
:param y: One-dimensional data array for which a mask will be
|
|
908
|
+
constructed.
|
|
909
|
+
:type y: numpy.ndarray
|
|
910
|
+
:param x: x-coordinates of the reference data,
|
|
911
|
+
defaults to `None`.
|
|
912
|
+
:type x: numpy.ndarray, optional
|
|
913
|
+
:param label: Legend label for the reference data,
|
|
914
|
+
defaults to `None`.
|
|
898
915
|
:type label: str, optional
|
|
899
|
-
:param ref_data:
|
|
916
|
+
:param ref_data: A list of additional reference data to
|
|
900
917
|
plot. Items in the list should be tuples of positional
|
|
901
918
|
arguments and keyword arguments to unpack and pass directly to
|
|
902
|
-
`matplotlib.axes.Axes.plot`, defaults to []
|
|
903
|
-
:type ref_data: list[tuple
|
|
904
|
-
:param
|
|
905
|
-
mask, defaults to None
|
|
906
|
-
:type
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
:
|
|
911
|
-
|
|
912
|
-
will be excluded. Defaults to True.
|
|
913
|
-
:type select_mask: bool, optional
|
|
914
|
-
:param title: title for the displayed figure, defaults to None
|
|
919
|
+
`matplotlib.axes.Axes.plot`, defaults to `[]`.
|
|
920
|
+
:type ref_data: list[tuple(tuple, dict)], optional
|
|
921
|
+
:param preselected_index_ranges: List of preselected index ranges
|
|
922
|
+
to mask, defaults to `None`.
|
|
923
|
+
:type preselected_index_ranges: Union(list[tuple(int, int)],
|
|
924
|
+
list[list[int]]), optional
|
|
925
|
+
:param preselected_mask: Preselected boolean mask array,
|
|
926
|
+
defaults to `None`.
|
|
927
|
+
:type preselected_mask: numpy.ndarray, optional
|
|
928
|
+
:param title: Title for the displayed figure, defaults to `None`.
|
|
915
929
|
:type title: str, optional
|
|
916
|
-
:param xlabel:
|
|
917
|
-
defaults to None
|
|
930
|
+
:param xlabel: Label for the x-axis of the displayed figure,
|
|
931
|
+
defaults to `None`.
|
|
918
932
|
:type xlabel: str, optional
|
|
919
|
-
:param ylabel:
|
|
920
|
-
defaults to None
|
|
933
|
+
:param ylabel: Label for the y-axis of the displayed figure,
|
|
934
|
+
defaults to `None`.
|
|
921
935
|
:type ylabel: str, optional
|
|
922
|
-
:param
|
|
923
|
-
|
|
924
|
-
:type
|
|
925
|
-
:param
|
|
926
|
-
|
|
927
|
-
:type
|
|
928
|
-
:
|
|
929
|
-
|
|
930
|
-
:
|
|
936
|
+
:param min_num_index_ranges: The minimum number of selected index
|
|
937
|
+
ranges, defaults to `None`.
|
|
938
|
+
:type min_num_index_ranges: int, optional
|
|
939
|
+
:param max_num_index_ranges: The maximum number of selected index
|
|
940
|
+
ranges, defaults to `None`.
|
|
941
|
+
:type max_num_index_ranges: int, optional
|
|
942
|
+
:param interactive: Show the plot and allow user interactions with
|
|
943
|
+
the matplotlib figure, defults to `True`.
|
|
944
|
+
:type interactive: bool, optional
|
|
945
|
+
:return: A Matplotlib figure, a boolean mask array and the list of
|
|
946
|
+
selected index ranges.
|
|
947
|
+
:rtype: matplotlib.figure.Figure, numpy.ndarray,
|
|
948
|
+
list[tuple(int, int)]
|
|
931
949
|
"""
|
|
932
|
-
#
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
(selected_index_ranges[-1], event.xdata)
|
|
975
|
-
else:
|
|
976
|
-
selected_index_ranges[-1] = \
|
|
977
|
-
(event.xdata, selected_index_ranges[-1])
|
|
978
|
-
draw_selections(
|
|
979
|
-
event.inaxes, current_include, current_exclude,
|
|
980
|
-
selected_index_ranges)
|
|
981
|
-
else:
|
|
982
|
-
selected_index_ranges.pop(-1)
|
|
983
|
-
|
|
984
|
-
def confirm_selection(event):
|
|
985
|
-
"""Action taken on hitting the confirm button."""
|
|
986
|
-
plt.close()
|
|
987
|
-
|
|
988
|
-
def clear_last_selection(event):
|
|
989
|
-
"""Action taken on hitting the clear button."""
|
|
990
|
-
if selected_index_ranges:
|
|
991
|
-
selected_index_ranges.pop(-1)
|
|
950
|
+
# Third party modules
|
|
951
|
+
from matplotlib.patches import Patch
|
|
952
|
+
from matplotlib.widgets import Button, SpanSelector
|
|
953
|
+
|
|
954
|
+
# local modules
|
|
955
|
+
from CHAP.utils.general import index_nearest
|
|
956
|
+
|
|
957
|
+
def change_fig_title(title):
|
|
958
|
+
if fig_title:
|
|
959
|
+
fig_title[0].remove()
|
|
960
|
+
fig_title.pop()
|
|
961
|
+
fig_title.append(plt.figtext(*title_pos, title, **title_props))
|
|
962
|
+
|
|
963
|
+
def change_error_text(error):
|
|
964
|
+
if error_texts:
|
|
965
|
+
error_texts[0].remove()
|
|
966
|
+
error_texts.pop()
|
|
967
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
968
|
+
|
|
969
|
+
def get_selected_index_ranges(change_fnc=None):
|
|
970
|
+
selected_index_ranges = sorted(
|
|
971
|
+
[[index_nearest(x, span.extents[0]),
|
|
972
|
+
index_nearest(x, span.extents[1])+1]
|
|
973
|
+
for span in spans])
|
|
974
|
+
if change_fnc is not None:
|
|
975
|
+
if len(selected_index_ranges) > 1:
|
|
976
|
+
change_fnc(
|
|
977
|
+
f'Selected index ranges: {selected_index_ranges}')
|
|
978
|
+
elif selected_index_ranges:
|
|
979
|
+
change_fnc(
|
|
980
|
+
f'Selected ROI: {tuple(selected_index_ranges[0])}')
|
|
981
|
+
else:
|
|
982
|
+
change_fnc(f'Selected ROI: None')
|
|
983
|
+
return selected_index_ranges
|
|
984
|
+
|
|
985
|
+
def add_span(event, xrange_init=None):
|
|
986
|
+
"""Callback function for the "Add span" button."""
|
|
987
|
+
if (max_num_index_ranges is not None
|
|
988
|
+
and len(spans) >= max_num_index_ranges):
|
|
989
|
+
change_error_text(
|
|
990
|
+
f'Exceeding max number of ranges, adjust an existing '
|
|
991
|
+
'range or click "Reset"/"Confirm"')
|
|
992
992
|
else:
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
993
|
+
spans.append(
|
|
994
|
+
SpanSelector(
|
|
995
|
+
ax, select_span, 'horizontal', props=included_props,
|
|
996
|
+
useblit=True, interactive=interactive,
|
|
997
|
+
drag_from_anywhere=True, ignore_event_outside=True,
|
|
998
|
+
grab_range=5))
|
|
999
|
+
if xrange_init is None:
|
|
1000
|
+
xmin_init, xmax_init = min(x), 0.05*(max(x)-min(x))
|
|
1001
|
+
else:
|
|
1002
|
+
xmin_init, xmax_init = xrange_init
|
|
1003
|
+
spans[-1]._selection_completed = True
|
|
1004
|
+
spans[-1].extents = (xmin_init, xmax_init)
|
|
1005
|
+
spans[-1].onselect(xmin_init, xmax_init)
|
|
1006
|
+
plt.draw()
|
|
1007
|
+
|
|
1008
|
+
def select_span(xmin, xmax):
|
|
1009
|
+
"""Callback function for the SpanSelector widget."""
|
|
1010
|
+
combined_spans = True
|
|
1011
|
+
while combined_spans:
|
|
1012
|
+
combined_spans = False
|
|
1013
|
+
for i, span1 in enumerate(spans):
|
|
1014
|
+
for span2 in spans[i+1:]:
|
|
1015
|
+
if (span1.extents[1] >= span2.extents[0]
|
|
1016
|
+
and span1.extents[0] <= span2.extents[1]):
|
|
1017
|
+
change_error_text(
|
|
1018
|
+
'Combined overlapping spans in currently '
|
|
1019
|
+
'selected mask')
|
|
1020
|
+
span2.extents = (
|
|
1021
|
+
min(span1.extents[0], span2.extents[0]),
|
|
1022
|
+
max(span1.extents[1], span2.extents[1]))
|
|
1023
|
+
span1.set_visible(False)
|
|
1024
|
+
spans.remove(span1)
|
|
1025
|
+
combined_spans = True
|
|
1026
|
+
break
|
|
1027
|
+
if combined_spans:
|
|
1028
|
+
break
|
|
1029
|
+
get_selected_index_ranges(change_error_text)
|
|
1030
|
+
plt.draw()
|
|
1031
|
+
|
|
1032
|
+
def reset(event):
|
|
1033
|
+
"""Callback function for the "Reset" button."""
|
|
1034
|
+
if error_texts:
|
|
1035
|
+
error_texts[0].remove()
|
|
1036
|
+
error_texts.pop()
|
|
1037
|
+
for span in reversed(spans):
|
|
1038
|
+
span.set_visible(False)
|
|
1039
|
+
spans.remove(span)
|
|
1040
|
+
get_selected_index_ranges(change_error_text)
|
|
1041
|
+
plt.draw()
|
|
1042
|
+
|
|
1043
|
+
def confirm(event):
|
|
1044
|
+
"""Callback function for the "Confirm" button."""
|
|
1045
|
+
if (min_num_index_ranges is not None
|
|
1046
|
+
and len(spans) < min_num_index_ranges):
|
|
1047
|
+
change_error_text(
|
|
1048
|
+
f'Select at least {min_num_index_ranges} unique index ranges')
|
|
1049
|
+
plt.draw()
|
|
1050
|
+
else:
|
|
1051
|
+
if error_texts:
|
|
1052
|
+
error_texts[0].remove()
|
|
1053
|
+
error_texts.pop()
|
|
1054
|
+
get_selected_index_ranges(change_fig_title)
|
|
1055
|
+
plt.close()
|
|
1056
|
+
|
|
1057
|
+
def update_mask(mask, selected_index_ranges):
|
|
1058
|
+
"""Update the mask with the selected index ranges."""
|
|
1059
|
+
for min_, max_ in selected_index_ranges:
|
|
1060
|
+
mask = np.logical_or(
|
|
1061
|
+
mask,
|
|
1062
|
+
np.logical_and(x >= x[min_], x <= x[min(max_, num_data-1)]))
|
|
1011
1063
|
return mask
|
|
1012
1064
|
|
|
1013
1065
|
def update_index_ranges(mask):
|
|
1014
1066
|
"""
|
|
1015
|
-
Update the
|
|
1067
|
+
Update the selected index ranges (where mask = True).
|
|
1016
1068
|
"""
|
|
1017
|
-
|
|
1069
|
+
selected_index_ranges = []
|
|
1018
1070
|
for i, m in enumerate(mask):
|
|
1019
1071
|
if m:
|
|
1020
|
-
if (not
|
|
1021
|
-
or isinstance(
|
|
1022
|
-
|
|
1072
|
+
if (not selected_index_ranges
|
|
1073
|
+
or isinstance(selected_index_ranges[-1], tuple)):
|
|
1074
|
+
selected_index_ranges.append(i)
|
|
1023
1075
|
else:
|
|
1024
|
-
if
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1076
|
+
if (selected_index_ranges
|
|
1077
|
+
and isinstance(selected_index_ranges[-1], int)):
|
|
1078
|
+
selected_index_ranges[-1] = \
|
|
1079
|
+
(selected_index_ranges[-1], i-1)
|
|
1080
|
+
if (selected_index_ranges
|
|
1081
|
+
and isinstance(selected_index_ranges[-1], int)):
|
|
1082
|
+
selected_index_ranges[-1] = (selected_index_ranges[-1], num_data-1)
|
|
1083
|
+
return selected_index_ranges
|
|
1029
1084
|
|
|
1030
1085
|
# Check inputs
|
|
1031
|
-
|
|
1032
|
-
if
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
xdata = np.arange(num_data)
|
|
1086
|
+
y = np.asarray(y)
|
|
1087
|
+
if y.ndim > 1:
|
|
1088
|
+
raise ValueError(f'Invalid y dimension ({y.ndim})')
|
|
1089
|
+
num_data = y.size
|
|
1090
|
+
if x is None:
|
|
1091
|
+
x = np.arange(num_data)+0.5
|
|
1038
1092
|
else:
|
|
1039
|
-
|
|
1040
|
-
if
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
logger.warning('Invalid xdata: must be monotonically increasing')
|
|
1045
|
-
return None, None
|
|
1046
|
-
if current_index_ranges is not None:
|
|
1047
|
-
if not isinstance(current_index_ranges, (tuple, list)):
|
|
1048
|
-
logger.warning(
|
|
1049
|
-
'Invalid current_index_ranges parameter '
|
|
1050
|
-
f'({current_index_ranges}, {type(current_index_ranges)})')
|
|
1051
|
-
return None, None
|
|
1052
|
-
if not isinstance(select_mask, bool):
|
|
1053
|
-
logger.warning(
|
|
1054
|
-
f'Invalid select_mask parameter ({select_mask}, '
|
|
1055
|
-
f'{type(select_mask)})')
|
|
1056
|
-
return None, None
|
|
1057
|
-
if num_index_ranges_max is not None:
|
|
1058
|
-
logger.warning(
|
|
1059
|
-
'num_index_ranges_max input not yet implemented in draw_mask_1d')
|
|
1093
|
+
x = np.asarray(x, dtype=np.float64)
|
|
1094
|
+
if x.ndim > 1 or x.size != num_data:
|
|
1095
|
+
raise ValueError(f'Invalid x shape ({x.shape})')
|
|
1096
|
+
if not np.all(x[:-1] < x[1:]):
|
|
1097
|
+
raise ValueError('Invalid x: must be monotonically increasing')
|
|
1060
1098
|
if title is None:
|
|
1061
|
-
title = 'select ranges
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
title = ''
|
|
1065
|
-
|
|
1066
|
-
if select_mask:
|
|
1067
|
-
selection_color = 'green'
|
|
1099
|
+
title = 'Click and drag to select ranges to include in mask'
|
|
1100
|
+
if preselected_index_ranges is None:
|
|
1101
|
+
preselected_index_ranges = []
|
|
1068
1102
|
else:
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1103
|
+
if (not isinstance(preselected_index_ranges, list)
|
|
1104
|
+
or any(not is_int_pair(v, ge=0, le=num_data)
|
|
1105
|
+
for v in preselected_index_ranges)):
|
|
1106
|
+
raise ValueError('Invalid parameter preselected_index_ranges '
|
|
1107
|
+
f'({preselected_index_ranges})')
|
|
1108
|
+
if (min_num_index_ranges is not None
|
|
1109
|
+
and len(preselected_index_ranges) < min_num_index_ranges):
|
|
1110
|
+
raise ValueError(
|
|
1111
|
+
'Invalid parameter preselected_index_ranges '
|
|
1112
|
+
f'({preselected_index_ranges}), number of selected index '
|
|
1113
|
+
f'ranges must be >= {min_num_index_ranges}')
|
|
1114
|
+
if (max_num_index_ranges is not None
|
|
1115
|
+
and len(preselected_index_ranges) > max_num_index_ranges):
|
|
1116
|
+
raise ValueError(
|
|
1117
|
+
'Invalid parameter preselected_index_ranges '
|
|
1118
|
+
f'({preselected_index_ranges}), number of selected index '
|
|
1119
|
+
f'ranges must be <= {max_num_index_ranges}')
|
|
1120
|
+
|
|
1121
|
+
spans = []
|
|
1122
|
+
fig_title = []
|
|
1123
|
+
error_texts = []
|
|
1124
|
+
|
|
1125
|
+
title_pos = (0.5, 0.95)
|
|
1126
|
+
title_props = {'fontsize': 'xx-large', 'horizontalalignment': 'center',
|
|
1127
|
+
'verticalalignment': 'bottom'}
|
|
1128
|
+
error_pos = (0.5, 0.90)
|
|
1129
|
+
error_props = {'fontsize': 'x-large', 'horizontalalignment': 'center',
|
|
1130
|
+
'verticalalignment': 'bottom'}
|
|
1131
|
+
excluded_props = {
|
|
1132
|
+
'facecolor': 'white', 'edgecolor': 'gray', 'linestyle': ':'}
|
|
1133
|
+
included_props = {
|
|
1134
|
+
'alpha': 0.5, 'facecolor': 'tab:blue', 'edgecolor': 'blue'}
|
|
1135
|
+
|
|
1136
|
+
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
1137
|
+
handles = ax.plot(x, y, color='k', label='Reference Data')
|
|
1138
|
+
handles.append(Patch(
|
|
1139
|
+
label='Excluded / unselected ranges', **excluded_props))
|
|
1140
|
+
handles.append(Patch(
|
|
1141
|
+
label='Included / selected ranges', **included_props))
|
|
1142
|
+
ax.legend(handles=handles)
|
|
1143
|
+
ax.set_xlabel(xlabel, fontsize='x-large')
|
|
1144
|
+
ax.set_ylabel(ylabel, fontsize='x-large')
|
|
1145
|
+
ax.set_xlim(x[0], x[-1])
|
|
1146
|
+
fig.subplots_adjust(bottom=0.0, top=0.85)
|
|
1147
|
+
|
|
1148
|
+
# Setup the preselected mask and index ranges if provided
|
|
1149
|
+
if preselected_mask is not None:
|
|
1150
|
+
preselected_index_ranges = update_index_ranges(
|
|
1151
|
+
update_mask(
|
|
1152
|
+
np.copy(np.asarray(preselected_mask, dtype=bool)),
|
|
1153
|
+
preselected_index_ranges))
|
|
1154
|
+
for min_, max_ in preselected_index_ranges:
|
|
1155
|
+
add_span(None, xrange_init=(x[min_], x[min(max_, num_data-1)]))
|
|
1156
|
+
|
|
1157
|
+
if not interactive:
|
|
1158
|
+
|
|
1159
|
+
get_selected_index_ranges(change_fig_title)
|
|
1160
|
+
|
|
1107
1161
|
else:
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
#
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
# Update the mask with the currently selected/unselected x-ranges
|
|
1155
|
-
selected_mask = update_mask(
|
|
1156
|
-
selected_mask, selected_index_ranges, unselected_index_ranges)
|
|
1157
|
-
|
|
1158
|
-
# Update the currently included index ranges (where mask is True)
|
|
1159
|
-
current_include = update_index_ranges(selected_mask)
|
|
1160
|
-
|
|
1161
|
-
if return_figure:
|
|
1162
|
-
return selected_mask, current_include, fig
|
|
1163
|
-
return selected_mask, current_include
|
|
1164
|
-
|
|
1165
|
-
def select_peaks(
|
|
1166
|
-
ydata, xdata, peak_locations,
|
|
1167
|
-
peak_labels=None,
|
|
1168
|
-
mask=None,
|
|
1169
|
-
pre_selected_peak_indices=[],
|
|
1170
|
-
return_sorted=True,
|
|
1171
|
-
title='', xlabel='', ylabel='',
|
|
1162
|
+
|
|
1163
|
+
change_fig_title(title)
|
|
1164
|
+
get_selected_index_ranges(change_error_text)
|
|
1165
|
+
fig.subplots_adjust(bottom=0.2)
|
|
1166
|
+
|
|
1167
|
+
# Setup "Add span" button
|
|
1168
|
+
add_span_btn = Button(plt.axes([0.15, 0.05, 0.15, 0.075]), 'Add span')
|
|
1169
|
+
add_span_cid = add_span_btn.on_clicked(add_span)
|
|
1170
|
+
|
|
1171
|
+
# Setup "Reset" button
|
|
1172
|
+
reset_btn = Button(plt.axes([0.45, 0.05, 0.15, 0.075]), 'Reset')
|
|
1173
|
+
reset_cid = reset_btn.on_clicked(reset)
|
|
1174
|
+
|
|
1175
|
+
# Setup "Confirm" button
|
|
1176
|
+
confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
1177
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
1178
|
+
|
|
1179
|
+
# Show figure for user interaction
|
|
1180
|
+
plt.show()
|
|
1181
|
+
|
|
1182
|
+
# Disconnect all widget callbacks when figure is closed
|
|
1183
|
+
add_span_btn.disconnect(add_span_cid)
|
|
1184
|
+
reset_btn.disconnect(reset_cid)
|
|
1185
|
+
confirm_btn.disconnect(confirm_cid)
|
|
1186
|
+
|
|
1187
|
+
# ...and remove the buttons before returning the figure
|
|
1188
|
+
add_span_btn.ax.remove()
|
|
1189
|
+
reset_btn.ax.remove()
|
|
1190
|
+
confirm_btn.ax.remove()
|
|
1191
|
+
plt.subplots_adjust(bottom=0.0)
|
|
1192
|
+
|
|
1193
|
+
selected_index_ranges = get_selected_index_ranges()
|
|
1194
|
+
if not selected_index_ranges:
|
|
1195
|
+
selected_index_ranges = None
|
|
1196
|
+
|
|
1197
|
+
# Update the mask with the currently selected index ranges
|
|
1198
|
+
selected_mask = update_mask(len(x)*[False], selected_index_ranges)
|
|
1199
|
+
|
|
1200
|
+
fig_title[0].set_in_layout(True)
|
|
1201
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
1202
|
+
|
|
1203
|
+
return fig, selected_mask, selected_index_ranges
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
def select_roi_1d(
|
|
1207
|
+
y, x=None, preselected_roi=None, title=None, xlabel=None, ylabel=None,
|
|
1172
1208
|
interactive=True):
|
|
1173
|
-
"""
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
:
|
|
1179
|
-
:
|
|
1180
|
-
:
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
:
|
|
1184
|
-
|
|
1185
|
-
:type peak_locations: list
|
|
1186
|
-
:param peak_labels: list of annotations for each peak, defaults to
|
|
1187
|
-
None
|
|
1188
|
-
:type peak_labels: list[str], optional
|
|
1189
|
-
:param mask: boolean array representing a mask that will be
|
|
1190
|
-
applied to the data at some later point, defaults to None
|
|
1191
|
-
:type mask: np.ndarray, optional
|
|
1192
|
-
:param pre_selected_peak_indices: indices of markers that should
|
|
1193
|
-
already be selected when the figure shows up, defaults to []
|
|
1194
|
-
:type pre_selected_peak_indices: list[int], optional
|
|
1195
|
-
:param return_sorted: sort the indices of selected markers before
|
|
1196
|
-
returning (otherwise: return them in the same order that the
|
|
1197
|
-
user selected them), defaults to True
|
|
1198
|
-
:type return_sorted: bool, optional
|
|
1199
|
-
:param title: title for the plot, defaults to ''
|
|
1209
|
+
"""Display a 2D plot and have the user select a single region
|
|
1210
|
+
of interest.
|
|
1211
|
+
|
|
1212
|
+
:param y: One-dimensional data array for which a for which a region of
|
|
1213
|
+
interest will be selected.
|
|
1214
|
+
:type y: numpy.ndarray
|
|
1215
|
+
:param x: x-coordinates of the data, defaults to `None`.
|
|
1216
|
+
:type x: numpy.ndarray, optional
|
|
1217
|
+
:param preselected_roi: Preselected region of interest,
|
|
1218
|
+
defaults to `None`.
|
|
1219
|
+
:type preselected_roi: tuple(int, int), optional
|
|
1220
|
+
:param title: Title for the displayed figure, defaults to `None`.
|
|
1200
1221
|
:type title: str, optional
|
|
1201
|
-
:param xlabel: x-axis
|
|
1222
|
+
:param xlabel: Label for the x-axis of the displayed figure,
|
|
1223
|
+
defaults to `None`.
|
|
1202
1224
|
:type xlabel: str, optional
|
|
1203
|
-
:param ylabel: y-axis
|
|
1225
|
+
:param ylabel: Label for the y-axis of the displayed figure,
|
|
1226
|
+
defaults to `None`.
|
|
1204
1227
|
:type ylabel: str, optional
|
|
1205
|
-
:param interactive:
|
|
1206
|
-
the matplotlib figure, defults to True
|
|
1228
|
+
:param interactive: Show the plot and allow user interactions with
|
|
1229
|
+
the matplotlib figure, defults to `True`.
|
|
1207
1230
|
:type interactive: bool, optional
|
|
1208
|
-
:return:
|
|
1209
|
-
|
|
1231
|
+
:return: The selected region of interest as array indices and a
|
|
1232
|
+
matplotlib figure.
|
|
1233
|
+
:rtype: matplotlib.figure.Figure, tuple(int, int)
|
|
1210
1234
|
"""
|
|
1235
|
+
# Check inputs
|
|
1236
|
+
y = np.asarray(y)
|
|
1237
|
+
if y.ndim != 1:
|
|
1238
|
+
raise ValueError(f'Invalid image dimension ({y.ndim})')
|
|
1239
|
+
if preselected_roi is not None:
|
|
1240
|
+
if not is_int_pair(preselected_roi, ge=0, le=y.size, log=False):
|
|
1241
|
+
raise ValueError('Invalid parameter preselected_roi '
|
|
1242
|
+
f'({preselected_roi})')
|
|
1243
|
+
preselected_roi = [preselected_roi]
|
|
1244
|
+
|
|
1245
|
+
fig, mask, roi = select_mask_1d(
|
|
1246
|
+
y, x=x, preselected_index_ranges=preselected_roi, title=title,
|
|
1247
|
+
xlabel=xlabel, ylabel=ylabel, min_num_index_ranges=1,
|
|
1248
|
+
max_num_index_ranges=1, interactive=interactive)
|
|
1249
|
+
|
|
1250
|
+
return fig, tuple(roi[0])
|
|
1251
|
+
|
|
1252
|
+
def select_roi_2d(
|
|
1253
|
+
a, preselected_roi=None, title=None, title_a=None,
|
|
1254
|
+
row_label='row index', column_label='column index', interactive=True):
|
|
1255
|
+
"""Display a 2D image and have the user select a single rectangular
|
|
1256
|
+
region of interest.
|
|
1257
|
+
|
|
1258
|
+
:param a: Two-dimensional image data array for which a region of
|
|
1259
|
+
interest will be selected.
|
|
1260
|
+
:type a: numpy.ndarray
|
|
1261
|
+
:param preselected_roi: Preselected region of interest,
|
|
1262
|
+
defaults to `None`.
|
|
1263
|
+
:type preselected_roi: tuple(int, int, int, int), optional
|
|
1264
|
+
:param title: Title for the displayed figure, defaults to `None`.
|
|
1265
|
+
:type title: str, optional
|
|
1266
|
+
:param title_a: Title for the image of a, defaults to `None`.
|
|
1267
|
+
:type title_a: str, optional
|
|
1268
|
+
:param row_label: Label for the y-axis of the displayed figure,
|
|
1269
|
+
defaults to `row index`.
|
|
1270
|
+
:type row_label: str, optional
|
|
1271
|
+
:param column_label: Label for the x-axis of the displayed figure,
|
|
1272
|
+
defaults to `column index`.
|
|
1273
|
+
:type column_label: str, optional
|
|
1274
|
+
:param interactive: Show the plot and allow user interactions with
|
|
1275
|
+
the matplotlib figure, defaults to `True`.
|
|
1276
|
+
:type interactive: bool, optional
|
|
1277
|
+
:return: The selected region of interest as array indices and a
|
|
1278
|
+
matplotlib figure.
|
|
1279
|
+
:rtype: matplotlib.figure.Figure, tuple(int, int, int, int)
|
|
1280
|
+
"""
|
|
1281
|
+
# Third party modules
|
|
1282
|
+
from matplotlib.widgets import Button, RectangleSelector
|
|
1283
|
+
|
|
1284
|
+
# Local modules
|
|
1285
|
+
from CHAP.utils.general import index_nearest
|
|
1286
|
+
|
|
1287
|
+
def change_fig_title(title):
|
|
1288
|
+
if fig_title:
|
|
1289
|
+
fig_title[0].remove()
|
|
1290
|
+
fig_title.pop()
|
|
1291
|
+
fig_title.append(plt.figtext(*title_pos, title, **title_props))
|
|
1292
|
+
|
|
1293
|
+
def change_error_text(error):
|
|
1294
|
+
if error_texts:
|
|
1295
|
+
error_texts[0].remove()
|
|
1296
|
+
error_texts.pop()
|
|
1297
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
1298
|
+
|
|
1299
|
+
def on_rect_select(eclick, erelease):
|
|
1300
|
+
"""Callback function for the RectangleSelector widget."""
|
|
1301
|
+
change_error_text(
|
|
1302
|
+
f'Selected ROI: {tuple(int(v) for v in rects[0].extents)}')
|
|
1303
|
+
plt.draw()
|
|
1304
|
+
|
|
1305
|
+
def reset(event):
|
|
1306
|
+
"""Callback function for the "Reset" button."""
|
|
1307
|
+
if error_texts:
|
|
1308
|
+
error_texts[0].remove()
|
|
1309
|
+
error_texts.pop()
|
|
1310
|
+
rects[0].set_visible(False)
|
|
1311
|
+
rects.pop()
|
|
1312
|
+
rects.append(
|
|
1313
|
+
RectangleSelector(
|
|
1314
|
+
ax, on_rect_select, props=rect_props, useblit=True,
|
|
1315
|
+
interactive=interactive, drag_from_anywhere=True,
|
|
1316
|
+
ignore_event_outside=False))
|
|
1317
|
+
plt.draw()
|
|
1318
|
+
|
|
1319
|
+
def confirm(event):
|
|
1320
|
+
"""Callback function for the "Confirm" button."""
|
|
1321
|
+
if error_texts:
|
|
1322
|
+
error_texts[0].remove()
|
|
1323
|
+
error_texts.pop()
|
|
1324
|
+
roi = tuple(int(v) for v in rects[0].extents)
|
|
1325
|
+
if roi[1]-roi[0] < 1 or roi[3]-roi[2] < 1:
|
|
1326
|
+
roi = None
|
|
1327
|
+
change_fig_title(f'Selected ROI: {roi}')
|
|
1328
|
+
plt.close()
|
|
1211
1329
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if mask is not None and mask.size != ydata.size:
|
|
1215
|
-
raise ValueError('mask must have the same size as data')
|
|
1330
|
+
fig_title = []
|
|
1331
|
+
error_texts = []
|
|
1216
1332
|
|
|
1333
|
+
# Check inputs
|
|
1334
|
+
a = np.asarray(a)
|
|
1335
|
+
if a.ndim != 2:
|
|
1336
|
+
raise ValueError(f'Invalid image dimension ({a.ndim})')
|
|
1337
|
+
if preselected_roi is not None:
|
|
1338
|
+
if (not is_int_series(preselected_roi, ge=0, log=False)
|
|
1339
|
+
or len(preselected_roi) != 4):
|
|
1340
|
+
raise ValueError('Invalid parameter preselected_roi '
|
|
1341
|
+
f'({preselected_roi})')
|
|
1342
|
+
if title is None:
|
|
1343
|
+
title = 'Click and drag to select or adjust a region of interest (ROI)'
|
|
1217
1344
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
'
|
|
1345
|
+
title_pos = (0.5, 0.95)
|
|
1346
|
+
title_props = {'fontsize': 'xx-large', 'horizontalalignment': 'center',
|
|
1347
|
+
'verticalalignment': 'bottom'}
|
|
1348
|
+
error_pos = (0.5, 0.90)
|
|
1349
|
+
error_props = {'fontsize': 'xx-large', 'horizontalalignment': 'center',
|
|
1350
|
+
'verticalalignment': 'bottom'}
|
|
1351
|
+
rect_props = {
|
|
1352
|
+
'alpha': 0.5, 'facecolor': 'tab:blue', 'edgecolor': 'blue'}
|
|
1226
1353
|
|
|
1227
|
-
# Setup reference data & plot
|
|
1228
|
-
if mask is None:
|
|
1229
|
-
mask = np.full(ydata.shape, True, dtype=bool)
|
|
1230
1354
|
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
transform=ax.get_xaxis_transform())
|
|
1264
|
-
peak_vlines.append(peak_vline)
|
|
1265
|
-
|
|
1266
|
-
# Indicate masked regions by gray-ing out the axes facecolor
|
|
1267
|
-
exclude_bounds = []
|
|
1268
|
-
for i, m in enumerate(mask):
|
|
1269
|
-
if not m:
|
|
1270
|
-
if (not exclude_bounds) or isinstance(exclude_bounds[-1], tuple):
|
|
1271
|
-
exclude_bounds.append(i)
|
|
1272
|
-
else:
|
|
1273
|
-
if exclude_bounds and isinstance(exclude_bounds[-1], int):
|
|
1274
|
-
exclude_bounds[-1] = (exclude_bounds[-1], i-1)
|
|
1275
|
-
if exclude_bounds and isinstance(exclude_bounds[-1], int):
|
|
1276
|
-
exclude_bounds[-1] = (exclude_bounds[-1], mask.size-1)
|
|
1277
|
-
for (low, upp) in exclude_bounds:
|
|
1278
|
-
xlow = xdata[low]
|
|
1279
|
-
xupp = xdata[upp]
|
|
1280
|
-
ax.axvspan(xlow, xupp, facecolor='gray', alpha=0.5)
|
|
1281
|
-
|
|
1282
|
-
selected_peak_indices = pre_selected_peak_indices
|
|
1283
|
-
if interactive:
|
|
1284
|
-
# Setup interative peak selection
|
|
1285
|
-
def onpick(event):
|
|
1286
|
-
try:
|
|
1287
|
-
peak_index = peak_vlines.index(event.artist)
|
|
1288
|
-
except:
|
|
1289
|
-
pass
|
|
1290
|
-
else:
|
|
1291
|
-
peak_vline = event.artist
|
|
1292
|
-
if peak_index in selected_peak_indices:
|
|
1293
|
-
peak_vline.set(**excluded_peak_props)
|
|
1294
|
-
selected_peak_indices.remove(peak_index)
|
|
1295
|
-
else:
|
|
1296
|
-
peak_vline.set(**included_peak_props)
|
|
1297
|
-
selected_peak_indices.append(peak_index)
|
|
1298
|
-
plt.draw()
|
|
1299
|
-
cid_pick_peak = fig.canvas.mpl_connect('pick_event', onpick)
|
|
1355
|
+
ax.imshow(a)
|
|
1356
|
+
ax.set_title(title_a, fontsize='xx-large')
|
|
1357
|
+
ax.set_xlabel(column_label, fontsize='x-large')
|
|
1358
|
+
ax.set_ylabel(row_label, fontsize='x-large')
|
|
1359
|
+
ax.set_xlim(0, a.shape[1])
|
|
1360
|
+
ax.set_ylim(a.shape[0], 0)
|
|
1361
|
+
fig.subplots_adjust(bottom=0.0, top=0.85)
|
|
1362
|
+
|
|
1363
|
+
# Setup the preselected range of interest if provided
|
|
1364
|
+
rects = [RectangleSelector(
|
|
1365
|
+
ax, on_rect_select, props=rect_props, useblit=True,
|
|
1366
|
+
interactive=interactive, drag_from_anywhere=True,
|
|
1367
|
+
ignore_event_outside=True)]
|
|
1368
|
+
if preselected_roi is not None:
|
|
1369
|
+
rects[0].extents = preselected_roi
|
|
1370
|
+
|
|
1371
|
+
if not interactive:
|
|
1372
|
+
|
|
1373
|
+
change_fig_title(
|
|
1374
|
+
f'Selected ROI: {tuple(int(v) for v in preselected_roi)}')
|
|
1375
|
+
|
|
1376
|
+
else:
|
|
1377
|
+
|
|
1378
|
+
change_fig_title(title)
|
|
1379
|
+
if preselected_roi is not None:
|
|
1380
|
+
change_error_text(
|
|
1381
|
+
f'Preselected ROI: {tuple(int(v) for v in preselected_roi)}')
|
|
1382
|
+
fig.subplots_adjust(bottom=0.2)
|
|
1383
|
+
|
|
1384
|
+
# Setup "Reset" button
|
|
1385
|
+
reset_btn = Button(plt.axes([0.125, 0.05, 0.15, 0.075]), 'Reset')
|
|
1386
|
+
reset_cid = reset_btn.on_clicked(reset)
|
|
1300
1387
|
|
|
1301
1388
|
# Setup "Confirm" button
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
plt.subplots_adjust(bottom=0.2)
|
|
1305
|
-
confirm_b = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
1306
|
-
cid_confirm = confirm_b.on_clicked(confirm_selection)
|
|
1389
|
+
confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
1390
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
1307
1391
|
|
|
1308
1392
|
# Show figure for user interaction
|
|
1309
1393
|
plt.show()
|
|
1310
1394
|
|
|
1311
1395
|
# Disconnect all widget callbacks when figure is closed
|
|
1312
|
-
|
|
1313
|
-
|
|
1396
|
+
reset_btn.disconnect(reset_cid)
|
|
1397
|
+
confirm_btn.disconnect(confirm_cid)
|
|
1314
1398
|
|
|
1315
|
-
# ...and remove the
|
|
1316
|
-
|
|
1317
|
-
|
|
1399
|
+
# ... and remove the buttons before returning the figure
|
|
1400
|
+
reset_btn.ax.remove()
|
|
1401
|
+
confirm_btn.ax.remove()
|
|
1318
1402
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
selected_peaks.sort()
|
|
1322
|
-
return selected_peaks, fig
|
|
1403
|
+
fig_title[0].set_in_layout(True)
|
|
1404
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
1323
1405
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1406
|
+
# Remove the handles before returning the figure
|
|
1407
|
+
if interactive:
|
|
1408
|
+
rects[0]._center_handle.set_visible(False)
|
|
1409
|
+
rects[0]._corner_handles.set_visible(False)
|
|
1410
|
+
rects[0]._edge_handles.set_visible(False)
|
|
1411
|
+
|
|
1412
|
+
roi = tuple(int(v) for v in rects[0].extents)
|
|
1413
|
+
if roi[1]-roi[0] < 1 or roi[3]-roi[2] < 1:
|
|
1414
|
+
roi = None
|
|
1415
|
+
|
|
1416
|
+
return fig, roi
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
def select_image_indices(
|
|
1420
|
+
a, axis, b=None, preselected_indices=None, axis_index_offset=0,
|
|
1421
|
+
min_range=None, min_num_indices=2, max_num_indices=2, title=None,
|
|
1422
|
+
title_a=None, title_b=None, row_label='row index',
|
|
1423
|
+
column_label='column index', interactive=True):
|
|
1424
|
+
"""Display a 2D image and have the user select a set of image
|
|
1425
|
+
indices in either row or column direction.
|
|
1426
|
+
|
|
1427
|
+
:param a: Two-dimensional image data array for which a region of
|
|
1428
|
+
interest will be selected.
|
|
1429
|
+
:type a: numpy.ndarray
|
|
1430
|
+
:param axis: The selection direction (0: row, 1: column)
|
|
1431
|
+
:type axis: int
|
|
1432
|
+
:param b: A secondary two-dimensional image data array for which
|
|
1433
|
+
a shared region of interest will be selected,
|
|
1434
|
+
defaults to `None`.
|
|
1435
|
+
:type b: numpy.ndarray, optional
|
|
1436
|
+
:param preselected_indices: Preselected image indices,
|
|
1437
|
+
defaults to `None`.
|
|
1438
|
+
:type preselected_indices: tuple(int), list(int), optional
|
|
1439
|
+
:param axis_index_offset: Offset in axes index range and
|
|
1440
|
+
preselected indices, defaults to `0`.
|
|
1441
|
+
:type axis_index_offset: int, optional
|
|
1442
|
+
:param min_range: The minimal range spanned by the selected
|
|
1443
|
+
indices, defaults to `None`
|
|
1444
|
+
:type min_range: int, optional
|
|
1445
|
+
:param min_num_indices: The minimum number of selected indices,
|
|
1446
|
+
defaults to `None`.
|
|
1447
|
+
:type min_num_indices: int, optional
|
|
1448
|
+
:param max_num_indices: The maximum number of selected indices,
|
|
1449
|
+
defaults to `None`.
|
|
1450
|
+
:type max_num_indices: int, optional
|
|
1451
|
+
:param title: Title for the displayed figure, defaults to `None`.
|
|
1452
|
+
:type title: str, optional
|
|
1453
|
+
:param title_a: Title for the image of a, defaults to `None`.
|
|
1454
|
+
:type title_a: str, optional
|
|
1455
|
+
:param title_b: Title for the image of b, defaults to `None`.
|
|
1456
|
+
:type title_b: str, optional
|
|
1457
|
+
:param row_label: Label for the y-axis of the displayed figure,
|
|
1458
|
+
defaults to `row index`.
|
|
1459
|
+
:type row_label: str, optional
|
|
1460
|
+
:param column_label: Label for the x-axis of the displayed figure,
|
|
1461
|
+
defaults to `column index`.
|
|
1462
|
+
:type column_label: str, optional
|
|
1463
|
+
:param interactive: Show the plot and allow user interactions with
|
|
1464
|
+
the matplotlib figure, defaults to `True`.
|
|
1465
|
+
:type interactive: bool, optional
|
|
1466
|
+
:return: The selected region of interest as array indices and a
|
|
1467
|
+
matplotlib figure.
|
|
1468
|
+
:rtype: matplotlib.figure.Figure, tuple(int, int, int, int)
|
|
1331
1469
|
"""
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
if
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
'
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
min_ = 0
|
|
1356
|
-
max_ = a.shape[axis]
|
|
1357
|
-
low_max = a.shape[axis]-num_min
|
|
1358
|
-
while True:
|
|
1359
|
-
if axis:
|
|
1360
|
-
quick_imshow(
|
|
1361
|
-
a[:,min_:max_], title=title, aspect='auto',
|
|
1362
|
-
extent=[min_,max_,a.shape[0],0])
|
|
1470
|
+
# Third party modules
|
|
1471
|
+
from matplotlib.widgets import TextBox, Button
|
|
1472
|
+
|
|
1473
|
+
def change_fig_title(title):
|
|
1474
|
+
if fig_title:
|
|
1475
|
+
fig_title[0].remove()
|
|
1476
|
+
fig_title.pop()
|
|
1477
|
+
fig_title.append(plt.figtext(*title_pos, title, **title_props))
|
|
1478
|
+
|
|
1479
|
+
def change_error_text(error):
|
|
1480
|
+
if error_texts:
|
|
1481
|
+
error_texts[0].remove()
|
|
1482
|
+
error_texts.pop()
|
|
1483
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
1484
|
+
|
|
1485
|
+
def get_selected_indices(change_fnc=None):
|
|
1486
|
+
selected_indices = tuple(sorted(indices))
|
|
1487
|
+
if change_fnc is not None:
|
|
1488
|
+
num_indices = len(indices)
|
|
1489
|
+
if len(selected_indices) > 1:
|
|
1490
|
+
text = f'Selected {row_column} indices: {selected_indices}'
|
|
1491
|
+
elif selected_indices:
|
|
1492
|
+
text = f'Selected {row_column} index: {selected_indices[0]}'
|
|
1363
1493
|
else:
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
if
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1494
|
+
text = f'Selected {row_column} indices: None'
|
|
1495
|
+
if min_num_indices is not None and num_indices < min_num_indices:
|
|
1496
|
+
if min_num_indices == max_num_indices:
|
|
1497
|
+
text += \
|
|
1498
|
+
f', select another {max_num_indices-num_indices}'
|
|
1499
|
+
else:
|
|
1500
|
+
text += \
|
|
1501
|
+
f', select at least {max_num_indices-num_indices} more'
|
|
1502
|
+
change_fnc(text)
|
|
1503
|
+
return selected_indices
|
|
1504
|
+
|
|
1505
|
+
def add_index(index):
|
|
1506
|
+
if index in indices:
|
|
1507
|
+
raise ValueError(f'Ignoring duplicate of selected {row_column}s')
|
|
1508
|
+
elif max_num_indices is not None and len(indices) >= max_num_indices:
|
|
1509
|
+
raise ValueError(
|
|
1510
|
+
f'Exceeding maximum number of selected {row_column}s, click '
|
|
1511
|
+
'either "Reset" or "Confirm"')
|
|
1512
|
+
elif (len(indices) and min_range is not None
|
|
1513
|
+
and abs(max(index, max(indices)) - min(index, min(indices)))
|
|
1514
|
+
< min_range):
|
|
1515
|
+
raise ValueError(
|
|
1516
|
+
f'Selected {row_column} range is smaller than required '
|
|
1517
|
+
'minimal range of {min_range}: ignoring last selection')
|
|
1518
|
+
else:
|
|
1519
|
+
indices.append(index)
|
|
1520
|
+
if not axis:
|
|
1521
|
+
for ax in axs:
|
|
1522
|
+
lines.append(ax.axhline(indices[-1], c='r', lw=2))
|
|
1390
1523
|
else:
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1524
|
+
for ax in axs:
|
|
1525
|
+
lines.append(ax.axvline(indices[-1], c='r', lw=2))
|
|
1526
|
+
|
|
1527
|
+
def select_index(expression):
|
|
1528
|
+
"""Callback function for the "Select row/column index" TextBox.
|
|
1529
|
+
"""
|
|
1530
|
+
if not len(expression):
|
|
1531
|
+
return
|
|
1532
|
+
if error_texts:
|
|
1533
|
+
error_texts[0].remove()
|
|
1534
|
+
error_texts.pop()
|
|
1535
|
+
try:
|
|
1536
|
+
index = int(expression)
|
|
1537
|
+
if (index < axis_index_offset
|
|
1538
|
+
or index >= axis_index_offset+a.shape[axis]):
|
|
1539
|
+
raise ValueError
|
|
1540
|
+
except ValueError:
|
|
1541
|
+
change_error_text(
|
|
1542
|
+
f'Invalid {row_column} index ({expression}), enter an integer '
|
|
1543
|
+
f'between {axis_index_offset} and '
|
|
1544
|
+
f'{axis_index_offset+a.shape[axis]-1}')
|
|
1545
|
+
else:
|
|
1546
|
+
try:
|
|
1547
|
+
add_index(index)
|
|
1548
|
+
get_selected_indices(change_error_text)
|
|
1549
|
+
except ValueError as e:
|
|
1550
|
+
change_error_text(e)
|
|
1551
|
+
index_input.set_val('')
|
|
1552
|
+
for ax in axs:
|
|
1553
|
+
ax.get_figure().canvas.draw()
|
|
1554
|
+
|
|
1555
|
+
def reset(event):
|
|
1556
|
+
"""Callback function for the "Reset" button."""
|
|
1557
|
+
if error_texts:
|
|
1558
|
+
error_texts[0].remove()
|
|
1559
|
+
error_texts.pop()
|
|
1560
|
+
for line in reversed(lines):
|
|
1561
|
+
line.remove()
|
|
1562
|
+
indices.clear()
|
|
1563
|
+
lines.clear()
|
|
1564
|
+
get_selected_indices(change_error_text)
|
|
1565
|
+
for ax in axs:
|
|
1566
|
+
ax.get_figure().canvas.draw()
|
|
1567
|
+
|
|
1568
|
+
def confirm(event):
|
|
1569
|
+
"""Callback function for the "Confirm" button."""
|
|
1570
|
+
if len(indices) < min_num_indices:
|
|
1571
|
+
change_error_text(
|
|
1572
|
+
f'Select at least {min_num_indices} unique {row_column}s')
|
|
1573
|
+
for ax in axs:
|
|
1574
|
+
ax.get_figure().canvas.draw()
|
|
1575
|
+
else:
|
|
1576
|
+
# Remove error texts and add selected indices if set
|
|
1577
|
+
if error_texts:
|
|
1578
|
+
error_texts[0].remove()
|
|
1579
|
+
error_texts.pop()
|
|
1580
|
+
get_selected_indices(change_fig_title)
|
|
1581
|
+
plt.close()
|
|
1582
|
+
|
|
1583
|
+
# Check inputs
|
|
1436
1584
|
a = np.asarray(a)
|
|
1437
1585
|
if a.ndim != 2:
|
|
1438
|
-
|
|
1439
|
-
a.ndim, 'array dimension', location='select_one_image_bound',
|
|
1440
|
-
raise_error=raise_error)
|
|
1441
|
-
return None
|
|
1586
|
+
raise ValueError(f'Invalid image dimension ({a.ndim})')
|
|
1442
1587
|
if axis < 0 or axis >= a.ndim:
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1588
|
+
raise ValueError(f'Invalid parameter axis ({axis})')
|
|
1589
|
+
if not axis:
|
|
1590
|
+
row_column = 'row'
|
|
1591
|
+
else:
|
|
1592
|
+
row_column = 'column'
|
|
1593
|
+
if not is_int(axis_index_offset, ge=0, log=False):
|
|
1594
|
+
raise ValueError(
|
|
1595
|
+
'Invalid parameter axis_index_offset ({axis_index_offset})')
|
|
1596
|
+
if preselected_indices is not None:
|
|
1597
|
+
if not is_int_series(
|
|
1598
|
+
preselected_indices, ge=axis_index_offset,
|
|
1599
|
+
le=axis_index_offset+a.shape[axis], log=False):
|
|
1600
|
+
if interactive:
|
|
1601
|
+
logger.warning(
|
|
1602
|
+
'Invalid parameter preselected_indices '
|
|
1603
|
+
f'({preselected_indices}), ignoring preselected_indices')
|
|
1604
|
+
preselected_indices = None
|
|
1458
1605
|
else:
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1606
|
+
raise ValueError('Invalid parameter preselected_indices '
|
|
1607
|
+
f'({preselected_indices})')
|
|
1608
|
+
if min_range is not None and not 2 <= min_range <= a.shape[axis]:
|
|
1609
|
+
raise ValueError('Invalid parameter min_range ({min_range})')
|
|
1610
|
+
if title is None:
|
|
1611
|
+
title = f'Select or adjust image {row_column} indices'
|
|
1612
|
+
if b is not None:
|
|
1613
|
+
b = np.asarray(b)
|
|
1614
|
+
if b.ndim != 2:
|
|
1615
|
+
raise ValueError(f'Invalid image dimension ({b.ndim})')
|
|
1616
|
+
if a.shape[0] != b.shape[0]:
|
|
1617
|
+
raise ValueError(f'Inconsistent image shapes({a.shape} vs '
|
|
1618
|
+
f'{b.shape})')
|
|
1619
|
+
|
|
1620
|
+
indices = []
|
|
1621
|
+
lines = []
|
|
1622
|
+
fig_title = []
|
|
1623
|
+
error_texts = []
|
|
1624
|
+
|
|
1625
|
+
title_pos = (0.5, 0.95)
|
|
1626
|
+
title_props = {'fontsize': 'xx-large', 'horizontalalignment': 'center',
|
|
1627
|
+
'verticalalignment': 'bottom'}
|
|
1628
|
+
error_pos = (0.5, 0.90)
|
|
1629
|
+
error_props = {'fontsize': 'x-large', 'horizontalalignment': 'center',
|
|
1630
|
+
'verticalalignment': 'bottom'}
|
|
1631
|
+
if b is None:
|
|
1632
|
+
fig, axs = plt.subplots(figsize=(11, 8.5))
|
|
1633
|
+
axs = [axs]
|
|
1477
1634
|
else:
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1635
|
+
if a.shape[0]+b.shape[0] > max(a.shape[1], b.shape[1]):
|
|
1636
|
+
fig, axs = plt.subplots(1, 2, figsize=(11, 8.5))
|
|
1637
|
+
else:
|
|
1638
|
+
fig, axs = plt.subplots(2, 1, figsize=(11, 8.5))
|
|
1639
|
+
extent = (0, a.shape[1], axis_index_offset+a.shape[0], axis_index_offset)
|
|
1640
|
+
axs[0].imshow(a, extent=extent)
|
|
1641
|
+
axs[0].set_title(title_a, fontsize='xx-large')
|
|
1642
|
+
if b is not None:
|
|
1643
|
+
axs[1].imshow(b, extent=extent)
|
|
1644
|
+
axs[1].set_title(title_b, fontsize='xx-large')
|
|
1645
|
+
if a.shape[0]+b.shape[0] > max(a.shape[1], b.shape[1]):
|
|
1646
|
+
axs[0].set_xlabel(column_label, fontsize='x-large')
|
|
1647
|
+
axs[0].set_ylabel(row_label, fontsize='x-large')
|
|
1648
|
+
axs[1].set_xlabel(column_label, fontsize='x-large')
|
|
1649
|
+
else:
|
|
1650
|
+
axs[0].set_ylabel(row_label, fontsize='x-large')
|
|
1651
|
+
axs[1].set_xlabel(column_label, fontsize='x-large')
|
|
1652
|
+
axs[1].set_ylabel(row_label, fontsize='x-large')
|
|
1653
|
+
for ax in axs:
|
|
1654
|
+
ax.set_xlim(extent[0], extent[1])
|
|
1655
|
+
ax.set_ylim(extent[2], extent[3])
|
|
1656
|
+
fig.subplots_adjust(bottom=0.0, top=0.85)
|
|
1657
|
+
|
|
1658
|
+
# Setup the preselected indices if provided
|
|
1659
|
+
if preselected_indices is not None:
|
|
1660
|
+
preselected_indices = sorted(list(preselected_indices))
|
|
1661
|
+
for index in preselected_indices:
|
|
1662
|
+
add_index(index)
|
|
1663
|
+
|
|
1664
|
+
if not interactive:
|
|
1665
|
+
|
|
1666
|
+
get_selected_indices(change_fig_title)
|
|
1667
|
+
|
|
1483
1668
|
else:
|
|
1484
|
-
a_tmp[bound,:] = a_tmp_max
|
|
1485
|
-
quick_imshow(a_tmp, title=title, aspect='auto')
|
|
1486
|
-
del a_tmp
|
|
1487
|
-
if not input_yesno(f'Accept this {bound_name} (y/n)?', default):
|
|
1488
|
-
bound = select_one_image_bound(
|
|
1489
|
-
a, axis, bound_name=bound_name, title=title)
|
|
1490
|
-
clear_imshow(title)
|
|
1491
|
-
return bound
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
def clear_imshow(title=None):
|
|
1495
|
-
"""Clear an image opened by quick_imshow()."""
|
|
1496
|
-
plt.ioff()
|
|
1497
|
-
if title is None:
|
|
1498
|
-
title = 'quick imshow'
|
|
1499
|
-
elif not isinstance(title, str):
|
|
1500
|
-
raise ValueError(f'Invalid parameter title ({title})')
|
|
1501
|
-
plt.close(fig=title)
|
|
1502
1669
|
|
|
1670
|
+
change_fig_title(title)
|
|
1671
|
+
get_selected_indices(change_error_text)
|
|
1672
|
+
fig.subplots_adjust(bottom=0.2)
|
|
1503
1673
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1674
|
+
# Setup TextBox
|
|
1675
|
+
index_input = TextBox(
|
|
1676
|
+
plt.axes([0.25, 0.05, 0.15, 0.075]), f'Select {row_column} index ')
|
|
1677
|
+
indices_cid = index_input.on_submit(select_index)
|
|
1678
|
+
|
|
1679
|
+
# Setup "Reset" button
|
|
1680
|
+
reset_btn = Button(plt.axes([0.5, 0.05, 0.15, 0.075]), 'Reset')
|
|
1681
|
+
reset_cid = reset_btn.on_clicked(reset)
|
|
1682
|
+
|
|
1683
|
+
# Setup "Confirm" button
|
|
1684
|
+
confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
1685
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
1686
|
+
|
|
1687
|
+
plt.show()
|
|
1688
|
+
|
|
1689
|
+
# Disconnect all widget callbacks when figure is closed
|
|
1690
|
+
index_input.disconnect(indices_cid)
|
|
1691
|
+
reset_btn.disconnect(reset_cid)
|
|
1692
|
+
confirm_btn.disconnect(confirm_cid)
|
|
1693
|
+
|
|
1694
|
+
# ... and remove the buttons before returning the figure
|
|
1695
|
+
index_input.ax.remove()
|
|
1696
|
+
reset_btn.ax.remove()
|
|
1697
|
+
confirm_btn.ax.remove()
|
|
1698
|
+
|
|
1699
|
+
fig_title[0].set_in_layout(True)
|
|
1700
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
1701
|
+
|
|
1702
|
+
if indices:
|
|
1703
|
+
return fig, tuple(sorted(indices))
|
|
1704
|
+
return fig, None
|
|
1512
1705
|
|
|
1513
1706
|
|
|
1514
1707
|
def quick_imshow(
|
|
1515
|
-
a, title=None,
|
|
1516
|
-
|
|
1517
|
-
|
|
1708
|
+
a, title=None, row_label='row index', column_label='column index',
|
|
1709
|
+
path=None, name=None, save_fig=False, save_only=False,
|
|
1710
|
+
extent=None, show_grid=False, grid_color='w', grid_linewidth=1,
|
|
1711
|
+
block=False, **kwargs):
|
|
1518
1712
|
"""Display a 2D image."""
|
|
1519
1713
|
if title is not None and not isinstance(title, str):
|
|
1520
1714
|
raise ValueError(f'Invalid parameter title ({title})')
|
|
@@ -1524,8 +1718,6 @@ def quick_imshow(
|
|
|
1524
1718
|
raise ValueError(f'Invalid parameter save_fig ({save_fig})')
|
|
1525
1719
|
if not isinstance(save_only, bool):
|
|
1526
1720
|
raise ValueError(f'Invalid parameter save_only ({save_only})')
|
|
1527
|
-
if not isinstance(clear, bool):
|
|
1528
|
-
raise ValueError(f'Invalid parameter clear ({clear})')
|
|
1529
1721
|
if not isinstance(block, bool):
|
|
1530
1722
|
raise ValueError(f'Invalid parameter block ({block})')
|
|
1531
1723
|
if not title:
|
|
@@ -1533,9 +1725,9 @@ def quick_imshow(
|
|
|
1533
1725
|
if name is None:
|
|
1534
1726
|
ttitle = re_sub(r'\s+', '_', title)
|
|
1535
1727
|
if path is None:
|
|
1536
|
-
path =
|
|
1728
|
+
path = ttitle
|
|
1537
1729
|
else:
|
|
1538
|
-
path = f'{path}/{ttitle}
|
|
1730
|
+
path = f'{path}/{ttitle}'
|
|
1539
1731
|
else:
|
|
1540
1732
|
if path is None:
|
|
1541
1733
|
path = name
|
|
@@ -1558,26 +1750,25 @@ def quick_imshow(
|
|
|
1558
1750
|
kwargs.pop('cmap')
|
|
1559
1751
|
if extent is None:
|
|
1560
1752
|
extent = (0, a.shape[1], a.shape[0], 0)
|
|
1561
|
-
if clear:
|
|
1562
|
-
try:
|
|
1563
|
-
plt.close(fig=title)
|
|
1564
|
-
except:
|
|
1565
|
-
pass
|
|
1566
1753
|
if not save_only:
|
|
1567
1754
|
if block:
|
|
1568
1755
|
plt.ioff()
|
|
1569
1756
|
else:
|
|
1570
1757
|
plt.ion()
|
|
1571
|
-
plt.figure(
|
|
1758
|
+
plt.figure(figsize=(11, 8.5))
|
|
1572
1759
|
plt.imshow(a, extent=extent, **kwargs)
|
|
1760
|
+
ax = plt.gca()
|
|
1761
|
+
ax.set_title(title, fontsize='xx-large')
|
|
1762
|
+
ax.set_xlabel(column_label, fontsize='x-large')
|
|
1763
|
+
ax.set_ylabel(row_label, fontsize='x-large')
|
|
1573
1764
|
if show_grid:
|
|
1574
|
-
ax = plt.gca()
|
|
1575
1765
|
ax.grid(color=grid_color, linewidth=grid_linewidth)
|
|
1576
|
-
|
|
1577
|
-
|
|
1766
|
+
if (os_path.splitext(path)[1]
|
|
1767
|
+
not in plt.gcf().canvas.get_supported_filetypes()):
|
|
1768
|
+
path += '.png'
|
|
1578
1769
|
if save_only:
|
|
1579
1770
|
plt.savefig(path)
|
|
1580
|
-
plt.close(
|
|
1771
|
+
plt.close()
|
|
1581
1772
|
else:
|
|
1582
1773
|
if save_fig:
|
|
1583
1774
|
plt.savefig(path)
|
|
@@ -1588,8 +1779,8 @@ def quick_imshow(
|
|
|
1588
1779
|
def quick_plot(
|
|
1589
1780
|
*args, xerr=None, yerr=None, vlines=None, title=None, xlim=None,
|
|
1590
1781
|
ylim=None, xlabel=None, ylabel=None, legend=None, path=None, name=None,
|
|
1591
|
-
show_grid=False, save_fig=False, save_only=False,
|
|
1592
|
-
|
|
1782
|
+
show_grid=False, save_fig=False, save_only=False, block=False,
|
|
1783
|
+
**kwargs):
|
|
1593
1784
|
"""Display a 2D line plot."""
|
|
1594
1785
|
if title is not None and not isinstance(title, str):
|
|
1595
1786
|
illegal_value(title, 'title', 'quick_plot')
|
|
@@ -1623,9 +1814,6 @@ def quick_plot(
|
|
|
1623
1814
|
if not isinstance(save_only, bool):
|
|
1624
1815
|
illegal_value(save_only, 'save_only', 'quick_plot')
|
|
1625
1816
|
return
|
|
1626
|
-
if not isinstance(clear, bool):
|
|
1627
|
-
illegal_value(clear, 'clear', 'quick_plot')
|
|
1628
|
-
return
|
|
1629
1817
|
if not isinstance(block, bool):
|
|
1630
1818
|
illegal_value(block, 'block', 'quick_plot')
|
|
1631
1819
|
return
|
|
@@ -1642,14 +1830,9 @@ def quick_plot(
|
|
|
1642
1830
|
path = name
|
|
1643
1831
|
else:
|
|
1644
1832
|
path = f'{path}/{name}'
|
|
1645
|
-
if clear:
|
|
1646
|
-
try:
|
|
1647
|
-
plt.close(fig=title)
|
|
1648
|
-
except:
|
|
1649
|
-
pass
|
|
1650
1833
|
args = unwrap_tuple(args)
|
|
1651
1834
|
if depth_tuple(args) > 1 and (xerr is not None or yerr is not None):
|
|
1652
|
-
logger.warning('Error bars ignored
|
|
1835
|
+
logger.warning('Error bars ignored for multiple curves')
|
|
1653
1836
|
if not save_only:
|
|
1654
1837
|
if block:
|
|
1655
1838
|
plt.ioff()
|
|
@@ -1669,10 +1852,6 @@ def quick_plot(
|
|
|
1669
1852
|
vlines = [vlines]
|
|
1670
1853
|
for v in vlines:
|
|
1671
1854
|
plt.axvline(v, color='r', linestyle='--', **kwargs)
|
|
1672
|
-
# if vlines is not None:
|
|
1673
|
-
# for s in tuple(
|
|
1674
|
-
# ([x, x], list(plt.gca().get_ylim())) for x in vlines):
|
|
1675
|
-
# plt.plot(*s, color='red', **kwargs)
|
|
1676
1855
|
if xlim is not None:
|
|
1677
1856
|
plt.xlim(xlim)
|
|
1678
1857
|
if ylim is not None:
|