Skip to content

Commit

Permalink
Stage Reconstruction Inputs (#86)
Browse files Browse the repository at this point in the history
- stage reconstruction inputs to directory
- add exposure time metadata
- prototype analysis dialogs
  • Loading branch information
stevehenke authored May 23, 2024
1 parent 37e0e3e commit c43c5a9
Show file tree
Hide file tree
Showing 95 changed files with 1,918 additions and 530 deletions.
12 changes: 4 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Standard Installation
$ conda activate ptychodus
$ ptychodus -h
usage: ptychodus [-h] [-b {reconstruct,train}] [-f FILE_PREFIX] [-i INPUT_FILE] [-o OUTPUT_FILE] [-p PATTERNS_FILE] [-s SETTINGS] [-v]
usage: ptychodus [-h] [-b {reconstruct,train}] [-f FILE_PREFIX] [-s SETTINGS_FILE] [-v] [-w OUTPUT_DIR]
ptychodus is a ptychography analysis application
Expand All @@ -46,15 +46,11 @@ Standard Installation
run action non-interactively
-f FILE_PREFIX, --file-prefix FILE_PREFIX
replace file path prefix in settings
-i INPUT_FILE, --input INPUT_FILE
input data product file (batch mode)
-o OUTPUT_FILE, --output OUTPUT_FILE
output data product file (batch mode)
-p PATTERNS_FILE, --patterns PATTERNS_FILE
use diffraction patterns from file
-s SETTINGS, --settings SETTINGS
-s SETTINGS_FILE, --settings SETTINGS_FILE
use settings from file
-v, --version show program's version number and exit
-w OUTPUT_DIR, --write OUTPUT_DIR
stage reconstruction inputs to directory
$ ptychodus
Expand Down
32 changes: 29 additions & 3 deletions ptychodus/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ def verifyAllArgumentsParsed(parser: argparse.ArgumentParser, argv: list[str]) -
parser.error('unrecognized arguments: %s' % ' '.join(argv))


class DirectoryType:

def __call__(self, string: str) -> Path:
path = Path(string)

if not path.is_dir():
raise argparse.ArgumentTypeError(f'\"{string}\" is not a directory!')

return path


def main() -> int:
parser = argparse.ArgumentParser(
prog=ptychodus.__name__.lower(),
Expand All @@ -40,29 +51,33 @@ def main() -> int:
help='replace file path prefix in settings',
)
parser.add_argument(
# input data product file (batch mode)
'-i',
'--input',
metavar='INPUT_FILE',
help='input data product file (batch mode)',
type=argparse.FileType('r'),
help=argparse.SUPPRESS,
)
parser.add_argument(
# output data product file (batch mode)
'-o',
'--output',
metavar='OUTPUT_FILE',
help='output data product file (batch mode)',
type=argparse.FileType('w'),
help=argparse.SUPPRESS,
)
parser.add_argument(
# preprocessed diffraction patterns file (batch mode)
'-p',
'--patterns',
metavar='PATTERNS_FILE',
help='use diffraction patterns from file',
type=argparse.FileType('r'),
help=argparse.SUPPRESS,
)
parser.add_argument(
'-s',
'--settings',
metavar='SETTINGS_FILE',
help='use settings from file',
type=argparse.FileType('r'),
)
Expand All @@ -72,6 +87,14 @@ def main() -> int:
action='version',
version=versionString(),
)
parser.add_argument(
'-w',
'--write',
metavar='OUTPUT_DIR',
type=DirectoryType(),
help='stage reconstruction inputs to directory',
)

parsedArgs, unparsedArgs = parser.parse_known_args()

modelArgs = ModelArgs(
Expand All @@ -81,6 +104,9 @@ def main() -> int:
)

with ModelCore(modelArgs, isDeveloperModeEnabled=parsedArgs.dev) as model:
if parsedArgs.write is not None:
return model.stageReconstructionInputs(parsedArgs.write)

if parsedArgs.batch is not None:
verifyAllArgumentsParsed(parser, unparsedArgs)

Expand Down
58 changes: 58 additions & 0 deletions ptychodus/api/fluorescence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path

from .product import Product
from .typing import RealArrayType


@dataclass(frozen=True)
class ElementMap:
name: str
counts_per_second: RealArrayType


@dataclass(frozen=True)
class FluorescenceDataset:
element_maps: Sequence[ElementMap]
counts_per_second_path: str
channel_names_path: str

# TODO need to communicate association between element map pixels and scan order.
# integer-valued, same shape as counts_per_second
#scan_indexes: IntegerArray


class FluorescenceFileReader(ABC):

@abstractmethod
def read(self, filePath: Path) -> FluorescenceDataset:
'''reads a fluorescence dataset from file'''
pass


class FluorescenceFileWriter(ABC):

@abstractmethod
def write(self, filePath: Path, dataset: FluorescenceDataset) -> None:
'''writes a fluorescence dataset to file'''
pass


class UpscalingStrategy(ABC):
'''Uses ptychography-corrected scan positions to remap element
concentrations from the regular scan grid to the upscaled grid'''

@abstractmethod
def __call__(self, emap: ElementMap, product: Product) -> ElementMap:
pass


class DeconvolutionStrategy(ABC):
'''Deconvolves the kernel from the accumulated array to obtain the
resolution-enhanced element map'''

@abstractmethod
def __call__(self, emap: ElementMap, product: Product) -> ElementMap:
pass
6 changes: 6 additions & 0 deletions ptychodus/api/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import re

from .automation import FileBasedWorkflow
from .fluorescence import (DeconvolutionStrategy, FluorescenceFileReader, FluorescenceFileWriter,
UpscalingStrategy)
from .object import ObjectPhaseCenteringStrategy, ObjectFileReader, ObjectFileWriter
from .observer import Observable
from .patterns import DiffractionFileReader, DiffractionFileWriter
Expand Down Expand Up @@ -111,6 +113,10 @@ def __init__(self) -> None:
self.productFileReaders = PluginChooser[ProductFileReader]()
self.productFileWriters = PluginChooser[ProductFileWriter]()
self.fileBasedWorkflows = PluginChooser[FileBasedWorkflow]()
self.fluorescenceFileReaders = PluginChooser[FluorescenceFileReader]()
self.fluorescenceFileWriters = PluginChooser[FluorescenceFileWriter]()
self.upscalingStrategies = PluginChooser[UpscalingStrategy]()
self.deconvolutionStrategies = PluginChooser[DeconvolutionStrategy]()

@classmethod
def loadPlugins(cls) -> PluginRegistry:
Expand Down
9 changes: 6 additions & 3 deletions ptychodus/api/probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any, TypeAlias
from typing import TypeAlias

import numpy
import numpy.typing

from .geometry import ImageExtent, PixelGeometry
from .typing import ComplexArrayType, RealArrayType

WavefieldArrayType: TypeAlias = numpy.typing.NDArray[numpy.complexfloating[Any, Any]]
WavefieldArrayType: TypeAlias = ComplexArrayType


@dataclass(frozen=True)
Expand Down Expand Up @@ -180,6 +180,9 @@ def getModeRelativePower(self, number: int) -> float:
def getCoherence(self) -> float:
return numpy.sqrt(numpy.sum(numpy.square(self._modeRelativePower)))

def getIntensity(self) -> RealArrayType:
return numpy.absolute(self._array).sum(axis=-3)


class ProbeFileReader(ABC):

Expand Down
2 changes: 2 additions & 0 deletions ptychodus/api/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ProductMetadata:
detectorDistanceInMeters: float
probeEnergyInElectronVolts: float
probePhotonsPerSecond: float
exposureTimeInSeconds: float

@property
def sizeInBytes(self) -> int:
Expand All @@ -24,6 +25,7 @@ def sizeInBytes(self) -> int:
sz += getsizeof(self.detectorDistanceInMeters)
sz += getsizeof(self.probeEnergyInElectronVolts)
sz += getsizeof(self.probePhotonsPerSecond)
sz += getsizeof(self.exposureTimeInSeconds)
return sz


Expand Down
12 changes: 12 additions & 0 deletions ptychodus/api/typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import Any, TypeAlias

import numpy
import numpy.typing

IntegerArrayType: TypeAlias = numpy.typing.NDArray[numpy.integer[Any]]
Float32ArrayType: TypeAlias = numpy.typing.NDArray[numpy.float32]
RealArrayType: TypeAlias = numpy.typing.NDArray[numpy.floating[Any]]
ComplexArrayType: TypeAlias = numpy.typing.NDArray[numpy.complexfloating[Any, Any]]

NumberTypes: TypeAlias = numpy.integer[Any] | numpy.floating[Any] | numpy.complexfloating[Any, Any]
NumberArrayType: TypeAlias = numpy.typing.NDArray[NumberTypes]
8 changes: 2 additions & 6 deletions ptychodus/api/visualization.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
from __future__ import annotations
from collections.abc import Iterator, Sequence
from dataclasses import dataclass
from typing import Any, Final, TypeAlias
from typing import Final

from scipy.stats import gaussian_kde
import numpy
import numpy.typing

from .geometry import Box2D, Interval, Line2D, PixelGeometry

RealArrayType: TypeAlias = numpy.typing.NDArray[numpy.floating[Any]]
NumberTypes: TypeAlias = numpy.integer[Any] | numpy.floating[Any] | numpy.complexfloating[Any, Any]
NumberArrayType: TypeAlias = numpy.typing.NDArray[NumberTypes]
from .typing import NumberArrayType, RealArrayType


@dataclass(frozen=True)
Expand Down
3 changes: 2 additions & 1 deletion ptychodus/controller/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex, QObject, QTimer
from PyQt5.QtGui import QFont

from ..api.observer import Observable, Observer
from ptychodus.api.observer import Observable, Observer

from ..model.automation import (AutomationCore, AutomationDatasetState, AutomationPresenter,
AutomationProcessingPresenter)
from ..view.automation import AutomationView, AutomationProcessingView, AutomationWatchdogView
Expand Down
8 changes: 5 additions & 3 deletions ptychodus/controller/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ def __init__(self, model: ModelCore, view: ViewCore) -> None:
self._probeController = ProbeController.createInstance(
model.probeRepository, model.probeAPI, self._probeImageController,
model.probePropagator, model.probePropagatorVisualizationEngine, view.probeView,
view.statusBar(), self._fileDialogFactory)
self._fileDialogFactory)
self._objectImageController = ImageController.createInstance(
model.objectVisualizationEngine, view.objectImageView, view.statusBar(),
self._fileDialogFactory)
self._objectController = ObjectController.createInstance(
model.objectRepository, model.objectAPI, self._objectImageController,
model.fourierRingCorrelator, model.dichroicAnalyzer, model.dichroicVisualizationEngine,
view.objectView, view.statusBar(), self._fileDialogFactory)
model.fourierRingCorrelator, model.stxmAnalyzer, model.stxmVisualizationEngine,
model.exposureAnalyzer, model.exposureVisualizationEngine, model.fluorescenceEnhancer,
model.fluorescenceVisualizationEngine, model.xmcdAnalyzer,
model.xmcdVisualizationEngine, view.objectView, self._fileDialogFactory)
self._reconstructorParametersController = ReconstructorController.createInstance(
model.reconstructorPresenter,
model.productRepository,
Expand Down
Loading

0 comments on commit c43c5a9

Please sign in to comment.