ptychodus 1.0__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.
- ptychodus/__init__.py +19 -0
- ptychodus/__main__.py +145 -0
- ptychodus/api/__init__.py +0 -0
- ptychodus/api/fluorescence.py +62 -0
- ptychodus/api/geometry.py +150 -0
- ptychodus/api/object.py +218 -0
- ptychodus/api/observer.py +92 -0
- ptychodus/api/parametric.py +480 -0
- ptychodus/api/patterns.py +148 -0
- ptychodus/api/plugins.py +185 -0
- ptychodus/api/probe.py +282 -0
- ptychodus/api/product.py +83 -0
- ptychodus/api/propagator.py +147 -0
- ptychodus/api/reconstructor.py +122 -0
- ptychodus/api/scan.py +112 -0
- ptychodus/api/settings.py +108 -0
- ptychodus/api/tree.py +38 -0
- ptychodus/api/typing.py +10 -0
- ptychodus/api/units.py +3 -0
- ptychodus/api/visualization.py +208 -0
- ptychodus/api/workflow.py +122 -0
- ptychodus/controller/__init__.py +5 -0
- ptychodus/controller/agent/__init__.py +6 -0
- ptychodus/controller/agent/core.py +163 -0
- ptychodus/controller/agent/item_delegate.py +121 -0
- ptychodus/controller/agent/list_model.py +34 -0
- ptychodus/controller/automation.py +205 -0
- ptychodus/controller/core.py +183 -0
- ptychodus/controller/data.py +110 -0
- ptychodus/controller/image.py +241 -0
- ptychodus/controller/memory.py +30 -0
- ptychodus/controller/object/__init__.py +5 -0
- ptychodus/controller/object/core.py +233 -0
- ptychodus/controller/object/editor_factory.py +79 -0
- ptychodus/controller/object/frc.py +58 -0
- ptychodus/controller/object/tree_model.py +227 -0
- ptychodus/controller/object/xmcd.py +112 -0
- ptychodus/controller/parametric.py +785 -0
- ptychodus/controller/patterns/__init__.py +5 -0
- ptychodus/controller/patterns/core.py +148 -0
- ptychodus/controller/patterns/dataset.py +184 -0
- ptychodus/controller/patterns/info.py +123 -0
- ptychodus/controller/patterns/wizard/__init__.py +5 -0
- ptychodus/controller/patterns/wizard/core.py +62 -0
- ptychodus/controller/patterns/wizard/files.py +322 -0
- ptychodus/controller/patterns/wizard/metadata.py +81 -0
- ptychodus/controller/patterns/wizard/patterns.py +303 -0
- ptychodus/controller/probe/__init__.py +5 -0
- ptychodus/controller/probe/core.py +282 -0
- ptychodus/controller/probe/editor_factory.py +296 -0
- ptychodus/controller/probe/fluorescence.py +292 -0
- ptychodus/controller/probe/illumination.py +95 -0
- ptychodus/controller/probe/propagator.py +173 -0
- ptychodus/controller/probe/stxm.py +91 -0
- ptychodus/controller/probe/tree_model.py +235 -0
- ptychodus/controller/probe/zernike.py +106 -0
- ptychodus/controller/product/__init__.py +5 -0
- ptychodus/controller/product/core.py +342 -0
- ptychodus/controller/product/editor.py +196 -0
- ptychodus/controller/ptychi/__init__.py +5 -0
- ptychodus/controller/ptychi/core.py +97 -0
- ptychodus/controller/ptychi/object.py +472 -0
- ptychodus/controller/ptychi/opr.py +113 -0
- ptychodus/controller/ptychi/optimizer.py +93 -0
- ptychodus/controller/ptychi/positions.py +296 -0
- ptychodus/controller/ptychi/probe.py +259 -0
- ptychodus/controller/ptychi/reconstructor.py +313 -0
- ptychodus/controller/ptychonn.py +51 -0
- ptychodus/controller/ptychopinn.py +217 -0
- ptychodus/controller/reconstructor.py +301 -0
- ptychodus/controller/scan/__init__.py +5 -0
- ptychodus/controller/scan/core.py +225 -0
- ptychodus/controller/scan/editor_factory.py +229 -0
- ptychodus/controller/scan/table_model.py +114 -0
- ptychodus/controller/settings.py +127 -0
- ptychodus/controller/tike/__init__.py +5 -0
- ptychodus/controller/tike/core.py +58 -0
- ptychodus/controller/tike/view_controllers.py +248 -0
- ptychodus/controller/visualization/__init__.py +9 -0
- ptychodus/controller/visualization/controller.py +191 -0
- ptychodus/controller/visualization/parameters.py +71 -0
- ptychodus/controller/visualization/widget.py +38 -0
- ptychodus/controller/workflow/__init__.py +5 -0
- ptychodus/controller/workflow/authorization.py +54 -0
- ptychodus/controller/workflow/compute.py +71 -0
- ptychodus/controller/workflow/controller.py +49 -0
- ptychodus/controller/workflow/execution.py +59 -0
- ptychodus/controller/workflow/input_data.py +51 -0
- ptychodus/controller/workflow/output_data.py +59 -0
- ptychodus/controller/workflow/status.py +78 -0
- ptychodus/controller/workflow/table_model.py +71 -0
- ptychodus/model/__init__.py +5 -0
- ptychodus/model/agent/__init__.py +13 -0
- ptychodus/model/agent/argo.py +73 -0
- ptychodus/model/agent/chat.py +81 -0
- ptychodus/model/agent/core.py +49 -0
- ptychodus/model/agent/properties.py +50 -0
- ptychodus/model/agent/settings.py +35 -0
- ptychodus/model/analysis/__init__.py +20 -0
- ptychodus/model/analysis/affine.py +154 -0
- ptychodus/model/analysis/barycentric.py +112 -0
- ptychodus/model/analysis/core.py +41 -0
- ptychodus/model/analysis/frc.py +105 -0
- ptychodus/model/analysis/illumination.py +138 -0
- ptychodus/model/analysis/propagator.py +158 -0
- ptychodus/model/analysis/settings.py +36 -0
- ptychodus/model/analysis/stxm.py +106 -0
- ptychodus/model/analysis/xmcd.py +137 -0
- ptychodus/model/automation/__init__.py +9 -0
- ptychodus/model/automation/buffer.py +71 -0
- ptychodus/model/automation/core.py +193 -0
- ptychodus/model/automation/processor.py +91 -0
- ptychodus/model/automation/repository.py +69 -0
- ptychodus/model/automation/settings.py +28 -0
- ptychodus/model/automation/watcher.py +100 -0
- ptychodus/model/automation/workflow.py +48 -0
- ptychodus/model/core.py +285 -0
- ptychodus/model/fluorescence/__init__.py +10 -0
- ptychodus/model/fluorescence/core.py +220 -0
- ptychodus/model/fluorescence/settings.py +29 -0
- ptychodus/model/fluorescence/two_step.py +93 -0
- ptychodus/model/fluorescence/vspi.py +182 -0
- ptychodus/model/memory.py +20 -0
- ptychodus/model/metadata.py +137 -0
- ptychodus/model/patterns/__init__.py +21 -0
- ptychodus/model/patterns/api.py +134 -0
- ptychodus/model/patterns/core.py +58 -0
- ptychodus/model/patterns/dataset.py +420 -0
- ptychodus/model/patterns/processor.py +100 -0
- ptychodus/model/patterns/settings.py +84 -0
- ptychodus/model/patterns/sizer.py +224 -0
- ptychodus/model/phase_unwrapper.py +463 -0
- ptychodus/model/product/__init__.py +24 -0
- ptychodus/model/product/api.py +475 -0
- ptychodus/model/product/core.py +120 -0
- ptychodus/model/product/geometry.py +161 -0
- ptychodus/model/product/item.py +166 -0
- ptychodus/model/product/item_factory.py +148 -0
- ptychodus/model/product/metadata.py +139 -0
- ptychodus/model/product/object/__init__.py +15 -0
- ptychodus/model/product/object/builder.py +116 -0
- ptychodus/model/product/object/builder_factory.py +83 -0
- ptychodus/model/product/object/item.py +109 -0
- ptychodus/model/product/object/item_factory.py +48 -0
- ptychodus/model/product/object/random.py +115 -0
- ptychodus/model/product/object/settings.py +37 -0
- ptychodus/model/product/object_repository.py +65 -0
- ptychodus/model/product/probe/__init__.py +28 -0
- ptychodus/model/product/probe/average_pattern.py +46 -0
- ptychodus/model/product/probe/builder.py +136 -0
- ptychodus/model/product/probe/builder_factory.py +107 -0
- ptychodus/model/product/probe/disk.py +55 -0
- ptychodus/model/product/probe/fzp.py +106 -0
- ptychodus/model/product/probe/item.py +95 -0
- ptychodus/model/product/probe/item_factory.py +48 -0
- ptychodus/model/product/probe/multimodal.py +209 -0
- ptychodus/model/product/probe/rect.py +62 -0
- ptychodus/model/product/probe/settings.py +66 -0
- ptychodus/model/product/probe/super_gaussian.py +46 -0
- ptychodus/model/product/probe/zernike.py +155 -0
- ptychodus/model/product/probe_repository.py +65 -0
- ptychodus/model/product/repository.py +138 -0
- ptychodus/model/product/scan/__init__.py +24 -0
- ptychodus/model/product/scan/bounding_box.py +38 -0
- ptychodus/model/product/scan/builder.py +87 -0
- ptychodus/model/product/scan/builder_factory.py +91 -0
- ptychodus/model/product/scan/cartesian.py +106 -0
- ptychodus/model/product/scan/concentric.py +62 -0
- ptychodus/model/product/scan/item.py +152 -0
- ptychodus/model/product/scan/item_factory.py +46 -0
- ptychodus/model/product/scan/lissajous.py +67 -0
- ptychodus/model/product/scan/settings.py +74 -0
- ptychodus/model/product/scan/spiral.py +54 -0
- ptychodus/model/product/scan/streaming.py +40 -0
- ptychodus/model/product/scan/transform.py +115 -0
- ptychodus/model/product/scan_repository.py +63 -0
- ptychodus/model/product/settings.py +37 -0
- ptychodus/model/product/validator.py +79 -0
- ptychodus/model/ptychi/__init__.py +31 -0
- ptychodus/model/ptychi/affine.py +56 -0
- ptychodus/model/ptychi/autodiff.py +201 -0
- ptychodus/model/ptychi/core.py +98 -0
- ptychodus/model/ptychi/device.py +35 -0
- ptychodus/model/ptychi/dm.py +174 -0
- ptychodus/model/ptychi/enums.py +86 -0
- ptychodus/model/ptychi/epie.py +171 -0
- ptychodus/model/ptychi/helper.py +722 -0
- ptychodus/model/ptychi/lsqml.py +202 -0
- ptychodus/model/ptychi/pie.py +171 -0
- ptychodus/model/ptychi/rpie.py +171 -0
- ptychodus/model/ptychi/settings.py +548 -0
- ptychodus/model/ptychonn/__init__.py +11 -0
- ptychodus/model/ptychonn/buffers.py +85 -0
- ptychodus/model/ptychonn/core.py +80 -0
- ptychodus/model/ptychonn/model.py +65 -0
- ptychodus/model/ptychonn/reconstructor.py +208 -0
- ptychodus/model/ptychonn/settings.py +51 -0
- ptychodus/model/ptychopinn/__init__.py +5 -0
- ptychodus/model/ptychopinn/core.py +65 -0
- ptychodus/model/ptychopinn/enums.py +9 -0
- ptychodus/model/ptychopinn/reconstructor.py +273 -0
- ptychodus/model/ptychopinn/settings.py +88 -0
- ptychodus/model/reconstructor/__init__.py +11 -0
- ptychodus/model/reconstructor/api.py +157 -0
- ptychodus/model/reconstructor/core.py +69 -0
- ptychodus/model/reconstructor/log.py +21 -0
- ptychodus/model/reconstructor/matcher.py +89 -0
- ptychodus/model/reconstructor/presenter.py +116 -0
- ptychodus/model/reconstructor/queue.py +135 -0
- ptychodus/model/reconstructor/settings.py +15 -0
- ptychodus/model/tike/__init__.py +17 -0
- ptychodus/model/tike/core.py +76 -0
- ptychodus/model/tike/reconstructor.py +284 -0
- ptychodus/model/tike/settings.py +168 -0
- ptychodus/model/visualization/__init__.py +5 -0
- ptychodus/model/visualization/color_axis.py +46 -0
- ptychodus/model/visualization/color_model.py +114 -0
- ptychodus/model/visualization/color_model_renderer.py +84 -0
- ptychodus/model/visualization/colormap.py +56 -0
- ptychodus/model/visualization/colormap_renderer.py +81 -0
- ptychodus/model/visualization/components.py +65 -0
- ptychodus/model/visualization/core.py +173 -0
- ptychodus/model/visualization/renderer.py +45 -0
- ptychodus/model/visualization/transformation.py +132 -0
- ptychodus/model/workflow/__init__.py +17 -0
- ptychodus/model/workflow/api.py +178 -0
- ptychodus/model/workflow/authorizer.py +43 -0
- ptychodus/model/workflow/core.py +267 -0
- ptychodus/model/workflow/executor.py +106 -0
- ptychodus/model/workflow/globus.py +298 -0
- ptychodus/model/workflow/locator.py +137 -0
- ptychodus/model/workflow/settings.py +23 -0
- ptychodus/model/workflow/status.py +52 -0
- ptychodus/plugins/aps2id_diffraction_file.py +89 -0
- ptychodus/plugins/csaxs_diffraction_file.py +84 -0
- ptychodus/plugins/cssi_position_file.py +41 -0
- ptychodus/plugins/csv_object_file.py +31 -0
- ptychodus/plugins/csv_probe_file.py +38 -0
- ptychodus/plugins/cxi_file.py +125 -0
- ptychodus/plugins/deconvolution.py +53 -0
- ptychodus/plugins/delimited_position_file.py +81 -0
- ptychodus/plugins/fresnel_zone_plate.py +25 -0
- ptychodus/plugins/h5_diffraction_file.py +222 -0
- ptychodus/plugins/h5_product_file.py +204 -0
- ptychodus/plugins/isn_diffraction_file.py +100 -0
- ptychodus/plugins/lcls_file_readers.py +167 -0
- ptychodus/plugins/lynx_diffraction_file.py +69 -0
- ptychodus/plugins/lynx_orchestra_position_file.py +89 -0
- ptychodus/plugins/lynx_sgz_position_file.py +85 -0
- ptychodus/plugins/mda_position_file.py +526 -0
- ptychodus/plugins/nanomax_position_file.py +47 -0
- ptychodus/plugins/nexus/__init__.py +24 -0
- ptychodus/plugins/nexus/nexus_diffraction_file.py +286 -0
- ptychodus/plugins/nexus/velociprobe_position_file.py +91 -0
- ptychodus/plugins/npy_diffraction_file.py +79 -0
- ptychodus/plugins/npy_object_file.py +31 -0
- ptychodus/plugins/npy_probe_file.py +31 -0
- ptychodus/plugins/npz_diffraction_file.py +97 -0
- ptychodus/plugins/npz_product_file.py +202 -0
- ptychodus/plugins/nsls2_diffraction_file.py +76 -0
- ptychodus/plugins/nsls2_product_file.py +77 -0
- ptychodus/plugins/ptychoshelves_position_file.py +41 -0
- ptychodus/plugins/ptychoshelves_product_file.py +115 -0
- ptychodus/plugins/slac_npz_file.py +103 -0
- ptychodus/plugins/tiff_diffraction_file.py +113 -0
- ptychodus/plugins/upscaling.py +127 -0
- ptychodus/plugins/workflow.py +177 -0
- ptychodus/plugins/xrf_maps_file.py +108 -0
- ptychodus/ptychodus_bdp.py +230 -0
- ptychodus/ptychodus_stream_processor.py +193 -0
- ptychodus/view/__init__.py +5 -0
- ptychodus/view/agent.py +49 -0
- ptychodus/view/automation.py +95 -0
- ptychodus/view/core.py +153 -0
- ptychodus/view/image.py +311 -0
- ptychodus/view/make_qrc.sh +6 -0
- ptychodus/view/object.py +89 -0
- ptychodus/view/patterns.py +125 -0
- ptychodus/view/probe.py +247 -0
- ptychodus/view/product.py +119 -0
- ptychodus/view/ptychodus.svg +100 -0
- ptychodus/view/reconstructor.py +100 -0
- ptychodus/view/repository.py +93 -0
- ptychodus/view/resources.py +1616 -0
- ptychodus/view/resources.qrc +24 -0
- ptychodus/view/scan.py +28 -0
- ptychodus/view/settings.py +40 -0
- ptychodus/view/test.py +26 -0
- ptychodus/view/visualization.py +356 -0
- ptychodus/view/widgets/__init__.py +22 -0
- ptychodus/view/widgets/angle_widget.py +59 -0
- ptychodus/view/widgets/combo_box_item_delegate.py +74 -0
- ptychodus/view/widgets/decimal_line_edit.py +100 -0
- ptychodus/view/widgets/decimal_slider.py +120 -0
- ptychodus/view/widgets/exception_dialog.py +43 -0
- ptychodus/view/widgets/group_box.py +53 -0
- ptychodus/view/widgets/length_widget.py +78 -0
- ptychodus/view/widgets/progress_bar_item_delegate.py +31 -0
- ptychodus/view/widgets/uuid_line_edit.py +15 -0
- ptychodus/view/workflow.py +182 -0
- ptychodus-1.0.dist-info/METADATA +172 -0
- ptychodus-1.0.dist-info/RECORD +306 -0
- ptychodus-1.0.dist-info/WHEEL +5 -0
- ptychodus-1.0.dist-info/entry_points.txt +2 -0
- ptychodus-1.0.dist-info/licenses/LICENSE +42 -0
- ptychodus-1.0.dist-info/top_level.txt +1 -0
ptychodus/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = version('ptychodus')
|
|
5
|
+
except PackageNotFoundError:
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from .ptychodus_stream_processor import PtychodusAdImageProcessor
|
|
10
|
+
except ModuleNotFoundError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'api',
|
|
15
|
+
'model',
|
|
16
|
+
'view',
|
|
17
|
+
'controller',
|
|
18
|
+
'PtychodusAdImageProcessor',
|
|
19
|
+
]
|
ptychodus/__main__.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from ptychodus.model import ModelCore
|
|
9
|
+
import ptychodus
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def version_string() -> str:
|
|
15
|
+
return f'{ptychodus.__name__.title()} ({ptychodus.__version__})'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def verify_all_arguments_parsed(parser: argparse.ArgumentParser, argv: list[str]) -> None:
|
|
19
|
+
if argv:
|
|
20
|
+
parser.error('unrecognized arguments: %s' % ' '.join(argv))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main() -> int:
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog=ptychodus.__name__.lower(),
|
|
26
|
+
description=f'{ptychodus.__name__} is a ptychography analysis application',
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
'-b',
|
|
30
|
+
'--batch',
|
|
31
|
+
choices=('reconstruct', 'train'),
|
|
32
|
+
help='run action non-interactively',
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
'-d',
|
|
36
|
+
'--dev',
|
|
37
|
+
action='store_true',
|
|
38
|
+
help=argparse.SUPPRESS,
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
'--fluorescence-input',
|
|
42
|
+
metavar='FLUORESCENCE_INPUT_FILE',
|
|
43
|
+
type=argparse.FileType('r'),
|
|
44
|
+
help=argparse.SUPPRESS,
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'--fluorescence-output',
|
|
48
|
+
metavar='FLUORESCENCE_OUTPUT_FILE',
|
|
49
|
+
type=argparse.FileType('w'),
|
|
50
|
+
help=argparse.SUPPRESS,
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
'-i',
|
|
54
|
+
'--input',
|
|
55
|
+
metavar='INPUT_FILE',
|
|
56
|
+
type=argparse.FileType('r'),
|
|
57
|
+
help='input file (batch mode)',
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
'-o',
|
|
61
|
+
'--output',
|
|
62
|
+
metavar='OUTPUT_FILE',
|
|
63
|
+
type=argparse.FileType('w'),
|
|
64
|
+
help='output file (batch mode)',
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
# preprocessed diffraction patterns file (batch mode)
|
|
68
|
+
'-p',
|
|
69
|
+
'--patterns',
|
|
70
|
+
metavar='PATTERNS_FILE',
|
|
71
|
+
type=argparse.FileType('r'),
|
|
72
|
+
help=argparse.SUPPRESS,
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
'-s',
|
|
76
|
+
'--settings',
|
|
77
|
+
metavar='SETTINGS_FILE',
|
|
78
|
+
help='use settings from file',
|
|
79
|
+
type=argparse.FileType('r'),
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
'-v',
|
|
83
|
+
'--version',
|
|
84
|
+
action='version',
|
|
85
|
+
version=version_string(),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
parsed_args, unparsed_args = parser.parse_known_args()
|
|
89
|
+
settings_file = Path(parsed_args.settings.name) if parsed_args.settings else None
|
|
90
|
+
|
|
91
|
+
with ModelCore(settings_file, is_developer_mode_enabled=parsed_args.dev) as model:
|
|
92
|
+
if parsed_args.patterns is not None:
|
|
93
|
+
patterns_file_path = Path(parsed_args.patterns.name)
|
|
94
|
+
model.workflow_api.import_assembled_patterns(patterns_file_path)
|
|
95
|
+
|
|
96
|
+
if parsed_args.batch is not None:
|
|
97
|
+
verify_all_arguments_parsed(parser, unparsed_args)
|
|
98
|
+
|
|
99
|
+
if parsed_args.input is None or parsed_args.output is None:
|
|
100
|
+
parser.error('Batch mode requires input and output arguments!')
|
|
101
|
+
return -1
|
|
102
|
+
|
|
103
|
+
action = parsed_args.batch
|
|
104
|
+
input_file_path = Path(parsed_args.input.name)
|
|
105
|
+
output_file_path = Path(parsed_args.output.name)
|
|
106
|
+
fluorescence_input_file_path: Path | None = None
|
|
107
|
+
fluorescence_output_file_path: Path | None = None
|
|
108
|
+
|
|
109
|
+
if parsed_args.fluorescence_input is not None:
|
|
110
|
+
fluorescence_input_file_path = Path(parsed_args.fluorescence_input.name)
|
|
111
|
+
|
|
112
|
+
if parsed_args.fluorescence_output is not None:
|
|
113
|
+
fluorescence_output_file_path = Path(parsed_args.fluorescence_output.name)
|
|
114
|
+
|
|
115
|
+
return model.batch_mode_execute(
|
|
116
|
+
action,
|
|
117
|
+
input_file_path,
|
|
118
|
+
output_file_path,
|
|
119
|
+
fluorescence_input_file_path=fluorescence_input_file_path,
|
|
120
|
+
fluorescence_output_file_path=fluorescence_output_file_path,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
from PyQt5.QtWidgets import QApplication
|
|
125
|
+
except ModuleNotFoundError:
|
|
126
|
+
logger.warning('PyQt5 not found.')
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
# QApplication expects the first argument to be the program name
|
|
130
|
+
app = QApplication(sys.argv[:1] + unparsed_args)
|
|
131
|
+
verify_all_arguments_parsed(parser, app.arguments()[1:])
|
|
132
|
+
|
|
133
|
+
from ptychodus.view import ViewCore
|
|
134
|
+
|
|
135
|
+
view = ViewCore()
|
|
136
|
+
|
|
137
|
+
from ptychodus.controller import ControllerCore
|
|
138
|
+
|
|
139
|
+
controller = ControllerCore(model, view, is_developer_mode_enabled=parsed_args.dev)
|
|
140
|
+
controller.show_main_window(version_string())
|
|
141
|
+
|
|
142
|
+
return app.exec()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .product import Product
|
|
7
|
+
from .typing import RealArrayType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ElementMap:
|
|
12
|
+
name: str
|
|
13
|
+
counts_per_second: RealArrayType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class FluorescenceDataset:
|
|
18
|
+
element_maps: Sequence[ElementMap]
|
|
19
|
+
counts_per_second_path: str
|
|
20
|
+
channel_names_path: str
|
|
21
|
+
|
|
22
|
+
# TODO need to communicate association between element map pixels and scan order.
|
|
23
|
+
# integer-valued, same shape as counts_per_second
|
|
24
|
+
# scan_indexes: IntegerArray
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FluorescenceEnhancingAlgorithm(ABC):
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def enhance(self, dataset: FluorescenceDataset, product: Product) -> FluorescenceDataset:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FluorescenceFileReader(ABC):
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def read(self, file_path: Path) -> FluorescenceDataset:
|
|
36
|
+
"""reads a fluorescence dataset from file"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FluorescenceFileWriter(ABC):
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def write(self, file_path: Path, dataset: FluorescenceDataset) -> None:
|
|
43
|
+
"""writes a fluorescence dataset to file"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class UpscalingStrategy(ABC):
|
|
48
|
+
"""Uses ptychography-corrected scan positions to remap element
|
|
49
|
+
concentrations from the regular scan grid to the upscaled grid"""
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def __call__(self, emap: ElementMap, product: Product) -> ElementMap:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DeconvolutionStrategy(ABC):
|
|
57
|
+
"""Deconvolves the kernel from the accumulated array to obtain the
|
|
58
|
+
resolution-enhanced element map"""
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def __call__(self, emap: ElementMap, product: Product) -> ElementMap:
|
|
62
|
+
pass
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar('T', int, float, Decimal)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class AffineTransform:
|
|
11
|
+
a00: float
|
|
12
|
+
a01: float
|
|
13
|
+
a02: float
|
|
14
|
+
|
|
15
|
+
a10: float
|
|
16
|
+
a11: float
|
|
17
|
+
a12: float
|
|
18
|
+
|
|
19
|
+
def __call__(self, y: float, x: float) -> tuple[float, float]:
|
|
20
|
+
yp = self.a00 * y + self.a01 * x + self.a02
|
|
21
|
+
xp = self.a10 * y + self.a11 * x + self.a12
|
|
22
|
+
return yp, xp
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class PixelGeometry:
|
|
27
|
+
width_m: float
|
|
28
|
+
height_m: float
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def area_m2(self) -> float:
|
|
32
|
+
return self.width_m * self.height_m
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def aspect_ratio(self) -> float:
|
|
36
|
+
return self.width_m / self.height_m
|
|
37
|
+
|
|
38
|
+
def copy(self) -> PixelGeometry:
|
|
39
|
+
return PixelGeometry(
|
|
40
|
+
width_m=float(self.width_m),
|
|
41
|
+
height_m=float(self.height_m),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class ImageExtent:
|
|
47
|
+
width_px: int
|
|
48
|
+
height_px: int
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def size(self) -> int:
|
|
52
|
+
"""returns the number of pixels in the image"""
|
|
53
|
+
return self.width_px * self.height_px
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def shape(self) -> tuple[int, int]:
|
|
57
|
+
"""returns the image shape (height_px, width_px) tuple"""
|
|
58
|
+
return self.height_px, self.width_px
|
|
59
|
+
|
|
60
|
+
def __eq__(self, other: object) -> bool:
|
|
61
|
+
if isinstance(other, ImageExtent):
|
|
62
|
+
return self.shape == other.shape
|
|
63
|
+
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class Point2D:
|
|
69
|
+
x: float
|
|
70
|
+
y: float
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True)
|
|
74
|
+
class Line2D:
|
|
75
|
+
begin: Point2D
|
|
76
|
+
end: Point2D
|
|
77
|
+
|
|
78
|
+
def lerp(self, alpha: float) -> Point2D:
|
|
79
|
+
beta = 1 - alpha
|
|
80
|
+
x = beta * self.begin.x + alpha * self.end.x
|
|
81
|
+
y = beta * self.begin.y + alpha * self.end.y
|
|
82
|
+
return Point2D(x, y)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class Box2D:
|
|
87
|
+
x: float
|
|
88
|
+
y: float
|
|
89
|
+
width: float
|
|
90
|
+
height: float
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def x_begin(self) -> float:
|
|
94
|
+
return self.x
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def x_end(self) -> float:
|
|
98
|
+
return self.x + self.width
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def y_begin(self) -> float:
|
|
102
|
+
return self.y
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def y_end(self) -> float:
|
|
106
|
+
return self.y + self.height
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Interval(Generic[T]):
|
|
110
|
+
def __init__(self, lower: T, upper: T) -> None:
|
|
111
|
+
self.lower: T = lower
|
|
112
|
+
self.upper: T = upper
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def create_proper(cls, a: T, b: T) -> Interval[T]:
|
|
116
|
+
if b < a:
|
|
117
|
+
return Interval[T](b, a)
|
|
118
|
+
else:
|
|
119
|
+
return Interval[T](a, b)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def is_empty(self) -> bool:
|
|
123
|
+
return self.upper < self.lower
|
|
124
|
+
|
|
125
|
+
def clamp(self, value: T) -> T:
|
|
126
|
+
return max(self.lower, min(value, self.upper))
|
|
127
|
+
|
|
128
|
+
def hull(self, value: Interval[T] | T) -> Interval[T]:
|
|
129
|
+
if isinstance(value, Interval):
|
|
130
|
+
return Interval[T](min(self.lower, value.lower), max(self.upper, value.upper))
|
|
131
|
+
else:
|
|
132
|
+
return Interval[T](min(self.lower, value), max(self.upper, value))
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def length(self) -> T:
|
|
136
|
+
return self.upper - self.lower
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def midrange(self) -> T:
|
|
140
|
+
total = self.lower + self.upper
|
|
141
|
+
return total // 2 if isinstance(total, int) else total / 2
|
|
142
|
+
|
|
143
|
+
def copy(self) -> Interval[T]:
|
|
144
|
+
return Interval[T](self.lower, self.upper)
|
|
145
|
+
|
|
146
|
+
def __contains__(self, item: T) -> bool:
|
|
147
|
+
return self.lower <= item and item < self.upper
|
|
148
|
+
|
|
149
|
+
def __repr__(self) -> str:
|
|
150
|
+
return f'{type(self).__name__}({self.lower}, {self.upper})'
|
ptychodus/api/object.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import numpy
|
|
8
|
+
|
|
9
|
+
from .geometry import ImageExtent, PixelGeometry
|
|
10
|
+
from .scan import ScanPoint
|
|
11
|
+
from .typing import ComplexArrayType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ObjectCenter:
|
|
16
|
+
position_x_m: float
|
|
17
|
+
position_y_m: float
|
|
18
|
+
|
|
19
|
+
def copy(self) -> ObjectCenter:
|
|
20
|
+
return ObjectCenter(
|
|
21
|
+
position_x_m=float(self.position_x_m),
|
|
22
|
+
position_y_m=float(self.position_y_m),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ObjectPoint:
|
|
28
|
+
index: int
|
|
29
|
+
position_x_px: float
|
|
30
|
+
position_y_px: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ObjectGeometry:
|
|
35
|
+
width_px: int
|
|
36
|
+
height_px: int
|
|
37
|
+
pixel_width_m: float
|
|
38
|
+
pixel_height_m: float
|
|
39
|
+
center_x_m: float
|
|
40
|
+
center_y_m: float
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def width_m(self) -> float:
|
|
44
|
+
return self.width_px * self.pixel_width_m
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def height_m(self) -> float:
|
|
48
|
+
return self.height_px * self.pixel_height_m
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def minimum_x_m(self) -> float:
|
|
52
|
+
return self.center_x_m - self.width_m / 2.0
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def minimum_y_m(self) -> float:
|
|
56
|
+
return self.center_y_m - self.height_m / 2.0
|
|
57
|
+
|
|
58
|
+
def get_pixel_geometry(self) -> PixelGeometry:
|
|
59
|
+
return PixelGeometry(
|
|
60
|
+
width_m=self.pixel_width_m,
|
|
61
|
+
height_m=self.pixel_height_m,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def get_center(self) -> ObjectCenter:
|
|
65
|
+
return ObjectCenter(
|
|
66
|
+
position_x_m=self.center_x_m,
|
|
67
|
+
position_y_m=self.center_y_m,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def map_object_point_to_scan_point(self, point: ObjectPoint) -> ScanPoint:
|
|
71
|
+
rx_px = self.width_px / 2
|
|
72
|
+
ry_px = self.height_px / 2
|
|
73
|
+
dx_m = self.pixel_width_m
|
|
74
|
+
dy_m = self.pixel_height_m
|
|
75
|
+
|
|
76
|
+
x_m = self.center_x_m + dx_m * (point.position_x_px - rx_px)
|
|
77
|
+
y_m = self.center_y_m + dy_m * (point.position_y_px - ry_px)
|
|
78
|
+
|
|
79
|
+
return ScanPoint(point.index, x_m, y_m)
|
|
80
|
+
|
|
81
|
+
def map_scan_point_to_object_point(self, point: ScanPoint) -> ObjectPoint:
|
|
82
|
+
rx_px = self.width_px / 2
|
|
83
|
+
ry_px = self.height_px / 2
|
|
84
|
+
dx_m = self.pixel_width_m
|
|
85
|
+
dy_m = self.pixel_height_m
|
|
86
|
+
|
|
87
|
+
x_px = (point.position_x_m - self.center_x_m) / dx_m + rx_px
|
|
88
|
+
y_px = (point.position_y_m - self.center_y_m) / dy_m + ry_px
|
|
89
|
+
|
|
90
|
+
return ObjectPoint(point.index, x_px, y_px)
|
|
91
|
+
|
|
92
|
+
def contains(self, geometry: ObjectGeometry) -> bool:
|
|
93
|
+
dx = self.center_x_m - geometry.center_x_m
|
|
94
|
+
dy = self.center_y_m - geometry.center_y_m
|
|
95
|
+
dw = self.width_m - geometry.width_m
|
|
96
|
+
dh = self.height_m - geometry.height_m
|
|
97
|
+
return abs(dx) <= dw and abs(dy) <= dh
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ObjectGeometryProvider(ABC):
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def get_object_geometry(self) -> ObjectGeometry:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Object:
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
array: ComplexArrayType | None,
|
|
110
|
+
pixel_geometry: PixelGeometry | None,
|
|
111
|
+
center: ObjectCenter | None,
|
|
112
|
+
layer_spacing_m: Sequence[float] = [],
|
|
113
|
+
) -> None:
|
|
114
|
+
if array is None:
|
|
115
|
+
self._array: ComplexArrayType = numpy.zeros((1, 0, 0), dtype=complex)
|
|
116
|
+
elif numpy.iscomplexobj(array):
|
|
117
|
+
match array.ndim:
|
|
118
|
+
case 2:
|
|
119
|
+
self._array = array[numpy.newaxis, ...]
|
|
120
|
+
case 3:
|
|
121
|
+
self._array = array
|
|
122
|
+
case _:
|
|
123
|
+
raise ValueError('Object must be 2- or 3-dimensional ndarray.')
|
|
124
|
+
else:
|
|
125
|
+
raise TypeError('Object must be a complex-valued ndarray')
|
|
126
|
+
|
|
127
|
+
self._pixel_geometry = pixel_geometry
|
|
128
|
+
self._center = center
|
|
129
|
+
self._layer_spacing_m = layer_spacing_m
|
|
130
|
+
|
|
131
|
+
expected_layers = self._array.shape[-3]
|
|
132
|
+
actual_layers = len(layer_spacing_m) + 1
|
|
133
|
+
|
|
134
|
+
if actual_layers != expected_layers:
|
|
135
|
+
raise ValueError(f'Expected {expected_layers} layers; got {actual_layers}!')
|
|
136
|
+
|
|
137
|
+
def copy(self) -> Object:
|
|
138
|
+
return Object(
|
|
139
|
+
array=self._array.copy(),
|
|
140
|
+
pixel_geometry=None if self._pixel_geometry is None else self._pixel_geometry.copy(),
|
|
141
|
+
center=None if self._center is None else self._center.copy(),
|
|
142
|
+
layer_spacing_m=list(self._layer_spacing_m),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def get_array(self) -> ComplexArrayType:
|
|
146
|
+
return self._array
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def dtype(self) -> numpy.dtype:
|
|
150
|
+
return self._array.dtype
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def nbytes(self) -> int:
|
|
154
|
+
return self._array.nbytes
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def width_px(self) -> int:
|
|
158
|
+
return self._array.shape[-1]
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def height_px(self) -> int:
|
|
162
|
+
return self._array.shape[-2]
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def num_layers(self) -> int:
|
|
166
|
+
return self._array.shape[-3]
|
|
167
|
+
|
|
168
|
+
def get_pixel_geometry(self) -> PixelGeometry:
|
|
169
|
+
if self._pixel_geometry is None:
|
|
170
|
+
raise ValueError('Missing object pixel geometry!')
|
|
171
|
+
|
|
172
|
+
return self._pixel_geometry
|
|
173
|
+
|
|
174
|
+
def get_center(self) -> ObjectCenter:
|
|
175
|
+
if self._center is None:
|
|
176
|
+
raise ValueError('Missing object center!')
|
|
177
|
+
|
|
178
|
+
return self._center
|
|
179
|
+
|
|
180
|
+
def get_geometry(self) -> ObjectGeometry:
|
|
181
|
+
pixel_geometry = self.get_pixel_geometry()
|
|
182
|
+
center = self.get_center()
|
|
183
|
+
|
|
184
|
+
return ObjectGeometry(
|
|
185
|
+
width_px=self.width_px,
|
|
186
|
+
height_px=self.height_px,
|
|
187
|
+
pixel_width_m=pixel_geometry.width_m,
|
|
188
|
+
pixel_height_m=pixel_geometry.height_m,
|
|
189
|
+
center_x_m=center.position_x_m,
|
|
190
|
+
center_y_m=center.position_y_m,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def get_layer(self, number: int) -> ComplexArrayType:
|
|
194
|
+
return self._array[number, :, :]
|
|
195
|
+
|
|
196
|
+
def get_layers_flattened(self) -> ComplexArrayType:
|
|
197
|
+
return numpy.prod(self._array, axis=-3)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def layer_spacing_m(self) -> Sequence[float]:
|
|
201
|
+
return self._layer_spacing_m
|
|
202
|
+
|
|
203
|
+
def get_total_thickness_m(self) -> float:
|
|
204
|
+
return sum(self._layer_spacing_m)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ObjectFileReader(ABC):
|
|
208
|
+
@abstractmethod
|
|
209
|
+
def read(self, file_path: Path) -> Object:
|
|
210
|
+
"""reads an object from file"""
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ObjectFileWriter(ABC):
|
|
215
|
+
@abstractmethod
|
|
216
|
+
def write(self, file_path: Path, object_: Object) -> None:
|
|
217
|
+
"""writes an object to file"""
|
|
218
|
+
pass
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'Observer',
|
|
8
|
+
'Observable',
|
|
9
|
+
'SequenceObserver',
|
|
10
|
+
'ObservableSequence',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
T = TypeVar('T')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Observer(ABC):
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def _update(self, observable: Observable) -> None:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Observable:
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
self._observer_list: list[Observer] = list()
|
|
25
|
+
self._block_notifications = False
|
|
26
|
+
self._pending_notify = False
|
|
27
|
+
|
|
28
|
+
def add_observer(self, observer: Observer) -> None:
|
|
29
|
+
if observer not in self._observer_list:
|
|
30
|
+
self._observer_list.append(observer)
|
|
31
|
+
|
|
32
|
+
def remove_observer(self, observer: Observer) -> None:
|
|
33
|
+
try:
|
|
34
|
+
self._observer_list.remove(observer)
|
|
35
|
+
except ValueError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def block_notifications(self, block: bool) -> None:
|
|
39
|
+
self._block_notifications = block
|
|
40
|
+
|
|
41
|
+
if self._pending_notify:
|
|
42
|
+
self.notify_observers()
|
|
43
|
+
self._pending_notify = False
|
|
44
|
+
|
|
45
|
+
def notify_observers(self) -> None:
|
|
46
|
+
if self._block_notifications:
|
|
47
|
+
self._pending_notify = True
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
for observer in self._observer_list:
|
|
51
|
+
observer._update(self)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SequenceObserver(Generic[T], ABC):
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def handle_item_inserted(self, index: int, item: T) -> None:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def handle_item_changed(self, index: int, item: T) -> None:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def handle_item_removed(self, index: int, item: T) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ObservableSequence(Sequence[T]):
|
|
69
|
+
def __init__(self) -> None:
|
|
70
|
+
self._observer_list: list[SequenceObserver[T]] = list()
|
|
71
|
+
|
|
72
|
+
def add_observer(self, observer: SequenceObserver[T]) -> None:
|
|
73
|
+
if observer not in self._observer_list:
|
|
74
|
+
self._observer_list.append(observer)
|
|
75
|
+
|
|
76
|
+
def remove_observer(self, observer: SequenceObserver[T]) -> None:
|
|
77
|
+
try:
|
|
78
|
+
self._observer_list.remove(observer)
|
|
79
|
+
except ValueError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def notify_observers_item_inserted(self, index: int, item: T) -> None:
|
|
83
|
+
for observer in self._observer_list:
|
|
84
|
+
observer.handle_item_inserted(index, item)
|
|
85
|
+
|
|
86
|
+
def notify_observers_item_changed(self, index: int, item: T) -> None:
|
|
87
|
+
for observer in self._observer_list:
|
|
88
|
+
observer.handle_item_changed(index, item)
|
|
89
|
+
|
|
90
|
+
def notify_observers_item_removed(self, index: int, item: T) -> None:
|
|
91
|
+
for observer in self._observer_list:
|
|
92
|
+
observer.handle_item_removed(index, item)
|