Skip to content

Commit 9c15f71

Browse files
committed
load bad pixels from file (done)
1 parent c583b8e commit 9c15f71

File tree

8 files changed

+133
-35
lines changed

8 files changed

+133
-35
lines changed

src/ptychodus/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def verify_all_arguments_parsed(parser: argparse.ArgumentParser, argv: list[str]
2323
def main() -> int:
2424
parser = argparse.ArgumentParser(
2525
prog=ptychodus.__name__.lower(),
26-
description=f'{ptychodus.__name__} is a ptychography analysis application',
26+
description=f'{ptychodus.__name__} is a ptychography data analysis application',
2727
)
2828
parser.add_argument(
2929
'-b',

src/ptychodus/controller/patterns/core.py

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33

44
from PyQt5.QtCore import QModelIndex
5-
from PyQt5.QtWidgets import QAbstractItemView, QFormLayout, QMessageBox
5+
from PyQt5.QtWidgets import (
6+
QAbstractItemView,
7+
QFormLayout,
8+
QHBoxLayout,
9+
QLineEdit,
10+
QMessageBox,
11+
QPushButton,
12+
QWidget,
13+
)
14+
15+
from ptychodus.api.parametric import PathParameter, StringParameter
616

717
from ...model.metadata import MetadataPresenter
818
from ...model.patterns import (
@@ -17,16 +27,80 @@
1727
from ...view.widgets import ExceptionDialog, ProgressBarItemDelegate
1828
from ..data import FileDialogFactory
1929
from ..image import ImageController
20-
from ..parametric import LengthWidgetParameterViewController, SpinBoxParameterViewController
30+
from ..parametric import (
31+
LengthWidgetParameterViewController,
32+
ParameterViewController,
33+
SpinBoxParameterViewController,
34+
)
2135
from .dataset import DatasetTreeModel
22-
from .dataset_file_layout import DatasetFileLayoutViewController
36+
from .dataset_layout import DatasetLayoutViewController
2337
from .wizard import OpenDatasetWizardController
2438

2539
logger = logging.getLogger(__name__)
2640

2741

42+
class BadPixelsViewController(ParameterViewController):
43+
def __init__(
44+
self,
45+
bad_pixels_file_path: PathParameter,
46+
bad_pixels_file_type: StringParameter,
47+
patterns_api: PatternsAPI,
48+
file_dialog_factory: FileDialogFactory,
49+
) -> None:
50+
self._bad_pixels_file_path = bad_pixels_file_path
51+
self._bad_pixels_file_type = bad_pixels_file_type
52+
self._patterns_api = patterns_api
53+
self._file_dialog_factory = file_dialog_factory
54+
55+
self._line_edit = QLineEdit()
56+
self._line_edit.setReadOnly(True)
57+
self._browse_button = QPushButton('Browse...')
58+
self._browse_button.clicked.connect(self._open_bad_pixels)
59+
self._clear_button = QPushButton('Clear')
60+
self._clear_button.clicked.connect(self._patterns_api.clear_bad_pixels)
61+
self._widget = QWidget()
62+
63+
layout = QHBoxLayout()
64+
layout.setContentsMargins(0, 0, 0, 0)
65+
layout.addWidget(self._line_edit)
66+
layout.addWidget(self._browse_button)
67+
layout.addWidget(self._clear_button)
68+
self._widget.setLayout(layout)
69+
70+
self.set_num_bad_pixels(0)
71+
72+
def _open_bad_pixels(self) -> None:
73+
file_reader_chooser = self._patterns_api.get_bad_pixels_file_reader_chooser()
74+
current_plugin = file_reader_chooser.get_current_plugin()
75+
file_path, name_filter = self._file_dialog_factory.get_open_file_path(
76+
self._widget,
77+
'Open Bad Pixels File',
78+
name_filters=[plugin.display_name for plugin in file_reader_chooser],
79+
selected_name_filter=current_plugin.simple_name,
80+
)
81+
82+
if file_path:
83+
try:
84+
self._patterns_api.open_bad_pixels(file_path, file_type=name_filter)
85+
except Exception as exc:
86+
logger.exception(exc)
87+
ExceptionDialog.show_exception('Bad Pixels File Reader', exc)
88+
89+
def get_widget(self) -> QWidget:
90+
return self._widget
91+
92+
def set_num_bad_pixels(self, num_bad_pixels: int) -> None:
93+
self._line_edit.setText(str(num_bad_pixels))
94+
95+
2896
class DetectorController:
29-
def __init__(self, settings: DetectorSettings, view: DetectorView) -> None:
97+
def __init__(
98+
self,
99+
settings: DetectorSettings,
100+
patterns_api: PatternsAPI,
101+
view: DetectorView,
102+
file_dialog_factory: FileDialogFactory,
103+
) -> None:
30104
self._width_px_view_controller = SpinBoxParameterViewController(settings.width_px)
31105
self._height_px_view_controller = SpinBoxParameterViewController(settings.height_px)
32106
self._pixel_width_view_controller = LengthWidgetParameterViewController(
@@ -36,15 +110,25 @@ def __init__(self, settings: DetectorSettings, view: DetectorView) -> None:
36110
settings.pixel_height_m
37111
)
38112
self._bit_depth_view_controller = SpinBoxParameterViewController(settings.bit_depth)
113+
self._bad_pixels_view_controller = BadPixelsViewController(
114+
settings.bad_pixels_file_path,
115+
settings.bad_pixels_file_type,
116+
patterns_api,
117+
file_dialog_factory,
118+
)
39119

40120
layout = QFormLayout()
41121
layout.addRow('Detector Width [px]:', self._width_px_view_controller.get_widget())
42122
layout.addRow('Detector Height [px]:', self._height_px_view_controller.get_widget())
43123
layout.addRow('Pixel Width:', self._pixel_width_view_controller.get_widget())
44124
layout.addRow('Pixel Height:', self._pixel_height_view_controller.get_widget())
45125
layout.addRow('Bit Depth:', self._bit_depth_view_controller.get_widget())
126+
layout.addRow('Bad Pixels:', self._bad_pixels_view_controller.get_widget())
46127
view.setLayout(layout)
47128

129+
def set_num_bad_pixels(self, num_bad_pixels: int) -> None:
130+
self._bad_pixels_view_controller.set_num_bad_pixels(num_bad_pixels)
131+
48132

49133
class PatternsController(DiffractionDatasetObserver):
50134
def __init__(
@@ -66,7 +150,9 @@ def __init__(
66150
self._view = view
67151
self._image_controller = image_controller
68152
self._file_dialog_factory = file_dialog_factory
69-
self._detector_controller = DetectorController(detector_settings, view.detector_view)
153+
self._detector_controller = DetectorController(
154+
detector_settings, patterns_api, view.detector_view, file_dialog_factory
155+
)
70156
self._wizard_controller = OpenDatasetWizardController(
71157
pattern_settings,
72158
pattern_sizer,
@@ -83,17 +169,14 @@ def __init__(
83169
view.tree_view.selectionModel().currentChanged.connect(self._update_view)
84170
self._update_view(QModelIndex(), QModelIndex())
85171

86-
open_bad_pixels_action = view.button_box.load_menu.addAction('Open Bad Pixels...')
87-
open_bad_pixels_action.triggered.connect(self._open_bad_pixels)
88-
89-
open_dataset_action = view.button_box.load_menu.addAction('Open Dataset...')
172+
open_dataset_action = view.button_box.load_menu.addAction('Open File...')
90173
open_dataset_action.triggered.connect(self._wizard_controller.open_dataset)
91174

92175
view.button_box.save_button.clicked.connect(self._save_dataset)
93176
view.button_box.close_button.clicked.connect(self._close_dataset)
94177

95-
file_layout_action = view.button_box.analyze_menu.addAction('Dataset File Layout...')
96-
file_layout_action.triggered.connect(self._show_file_layout)
178+
dataset_layout_action = view.button_box.analyze_menu.addAction('Dataset Layout...')
179+
dataset_layout_action.triggered.connect(self._show_dataset_layout)
97180

98181
dataset.add_observer(self)
99182
self._sync_model_to_view()
@@ -107,23 +190,6 @@ def _update_view(self, current: QModelIndex, previous: QModelIndex) -> None:
107190
else:
108191
self._image_controller.clear_array()
109192

110-
def _open_bad_pixels(self) -> None:
111-
file_reader_chooser = self._patterns_api.get_bad_pixels_file_reader_chooser()
112-
current_plugin = file_reader_chooser.get_current_plugin()
113-
file_path, name_filter = self._file_dialog_factory.get_open_file_path(
114-
self._view,
115-
'Open Bad Pixels File',
116-
name_filters=[plugin.simple_name for plugin in file_reader_chooser],
117-
selected_name_filter=current_plugin.simple_name,
118-
)
119-
120-
if file_path:
121-
try:
122-
self._patterns_api.load_bad_pixels(file_path, file_type=name_filter)
123-
except Exception as exc:
124-
logger.exception(exc)
125-
ExceptionDialog.show_exception('Bad Pixels File Reader', exc)
126-
127193
def _save_dataset(self) -> None:
128194
file_writer_chooser = self._patterns_api.get_file_writer_chooser()
129195
file_path, name_filter = self._file_dialog_factory.get_save_file_path(
@@ -140,8 +206,8 @@ def _save_dataset(self) -> None:
140206
logger.exception(exc)
141207
ExceptionDialog.show_exception('File Writer', exc)
142208

143-
def _show_file_layout(self) -> None:
144-
DatasetFileLayoutViewController.show_dialog(self._dataset, self._view)
209+
def _show_dataset_layout(self) -> None:
210+
DatasetLayoutViewController.show_dialog(self._dataset, self._view)
145211

146212
def _close_dataset(self) -> None:
147213
button = QMessageBox.question(
@@ -162,6 +228,9 @@ def _sync_model_to_view(self) -> None:
162228
info_text = self._dataset.get_info_text()
163229
self._view.info_label.setText(info_text)
164230

231+
def handle_bad_pixels_changed(self, num_bad_pixels: int) -> None:
232+
self._detector_controller.set_num_bad_pixels(num_bad_pixels)
233+
165234
def handle_array_inserted(self, index: int) -> None:
166235
self._tree_model.insert_array(index, self._dataset[index])
167236

src/ptychodus/controller/patterns/dataset_file_layout.py renamed to src/ptychodus/controller/patterns/dataset_layout.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def columnCount(self, parent: QModelIndex = QModelIndex()) -> int: # noqa: N802
9191
return len(node.item_data)
9292

9393

94-
class DatasetFileLayoutViewController(DiffractionDatasetObserver):
94+
class DatasetLayoutViewController(DiffractionDatasetObserver):
9595
def __init__(self, dataset: AssembledDiffractionDataset, tree_model: SimpleTreeModel) -> None:
9696
super().__init__()
9797
self._dataset = dataset
@@ -113,6 +113,9 @@ def show_dialog(cls, dataset: AssembledDiffractionDataset, parent: QWidget) -> N
113113
def _sync_model_to_view(self) -> None:
114114
self._tree_model.set_root_node(self._dataset.get_contents_tree())
115115

116+
def handle_bad_pixels_changed(self, num_bad_pixels: int) -> None:
117+
pass
118+
116119
def handle_array_inserted(self, index: int) -> None:
117120
pass
118121

src/ptychodus/model/metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ def sync_detector_distance(self) -> None:
127127
if distance_m:
128128
self._product_settings.detector_distance_m.set_value(distance_m)
129129

130+
def handle_bad_pixels_changed(self, num_bad_pixels: int) -> None:
131+
pass
132+
130133
def handle_array_inserted(self, index: int) -> None:
131134
pass
132135

src/ptychodus/model/patterns/api.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def create_streaming_context(self, metadata: DiffractionMetadata) -> PatternsStr
6767
def get_bad_pixels_file_reader_chooser(self) -> PluginChooser[BadPixelsFileReader]:
6868
return self._bad_pixels_file_reader_chooser
6969

70-
def load_bad_pixels(self, file_path: Path, *, file_type: str | None = None) -> None:
70+
def open_bad_pixels(self, file_path: Path, *, file_type: str | None = None) -> None:
7171
if file_path.is_file():
7272
if file_type is not None:
7373
self._bad_pixels_file_reader_chooser.set_current_plugin(file_type)
@@ -84,6 +84,9 @@ def load_bad_pixels(self, file_path: Path, *, file_type: str | None = None) -> N
8484
else:
8585
logger.warning(f'Refusing to read invalid file path {file_path}')
8686

87+
def clear_bad_pixels(self) -> None:
88+
self._dataset.set_bad_pixels(None)
89+
8790
def get_file_reader_chooser(self) -> PluginChooser[DiffractionFileReader]:
8891
return self._file_reader_chooser
8992

src/ptychodus/model/patterns/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def stop(self) -> None:
5757

5858
def _update(self, observable: Observable) -> None:
5959
if observable is self._reinit_observable:
60-
self.patterns_api.load_bad_pixels(
60+
self.patterns_api.open_bad_pixels(
6161
file_path=self.detector_settings.bad_pixels_file_path.get_value(),
6262
file_type=self.detector_settings.bad_pixels_file_type.get_value(),
6363
)

src/ptychodus/model/patterns/dataset.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838

3939

4040
class DiffractionDatasetObserver(ABC):
41+
@abstractmethod
42+
def handle_bad_pixels_changed(self, num_bad_pixels: int) -> None:
43+
pass
44+
4145
@abstractmethod
4246
def handle_array_inserted(self, index: int) -> None:
4347
pass
@@ -275,8 +279,25 @@ def get_metadata(self) -> DiffractionMetadata:
275279
return self._metadata
276280

277281
def set_bad_pixels(self, bad_pixels: BadPixelsArray | None) -> None:
282+
if bad_pixels is not None:
283+
if bad_pixels.ndim != 2:
284+
raise ValueError(f'Bad pixels array must be 2D, got {bad_pixels.ndim}D.')
285+
286+
actual_extent = ImageExtent(
287+
width_px=bad_pixels.shape[-1], height_px=bad_pixels.shape[-2]
288+
)
289+
expected_extent = self._sizer.get_detector_extent()
290+
291+
if actual_extent != expected_extent:
292+
raise ValueError(f'Shape mismatch: {actual_extent=} {expected_extent=}')
293+
278294
self._bad_pixels = bad_pixels
279295

296+
num_bad_pixels = 0 if self._bad_pixels is None else numpy.count_nonzero(self._bad_pixels)
297+
298+
for observer in self._observer_list:
299+
observer.handle_bad_pixels_changed(num_bad_pixels)
300+
280301
def get_bad_pixels(self) -> BadPixelsArray | None:
281302
return self._bad_pixels
282303

src/ptychodus/model/patterns/processor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ class DiffractionPatternProcessor:
8888
transpose: bool
8989

9090
def process_bad_pixels(self, bad_pixels: BadPixelsArray) -> BadPixelsArray:
91-
# FIXME sync bad pixels file_path to settings in controller?
9291
if bad_pixels.ndim != 2:
9392
raise ValueError(f'Invalid bad_pixel dimensions! (shape={bad_pixels.shape})')
9493

0 commit comments

Comments
 (0)