Skip to content

Commit

Permalink
implement
Browse files Browse the repository at this point in the history
  • Loading branch information
stevehenke committed Oct 18, 2024
1 parent bf5ab77 commit 2f58d31
Show file tree
Hide file tree
Showing 22 changed files with 128 additions and 46 deletions.
1 change: 1 addition & 0 deletions ptychodus/api/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .observer import Observable
from .tree import SimpleTreeNode

BooleanArrayType: TypeAlias = numpy.typing.NDArray[numpy.bool_]
DiffractionPatternArrayType: TypeAlias = numpy.typing.NDArray[numpy.integer[Any]]
DiffractionPatternIndexes: TypeAlias = numpy.typing.NDArray[numpy.integer[Any]]

Expand Down
14 changes: 14 additions & 0 deletions ptychodus/api/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from sys import getsizeof

from .constants import ELECTRON_VOLT_J, LIGHT_SPEED_M_PER_S, PLANCK_CONSTANT_J_PER_HZ
from .object import Object
from .probe import Probe
from .scan import Scan
Expand All @@ -18,6 +19,19 @@ class ProductMetadata:
probePhotonsPerSecond: float
exposureTimeInSeconds: float

@property
def probeEnergyInJoules(self) -> float:
return self.probeEnergyInElectronVolts * ELECTRON_VOLT_J

@property
def probeWavelengthInMeters(self) -> float:
hc_Jm = PLANCK_CONSTANT_J_PER_HZ * LIGHT_SPEED_M_PER_S

try:
return hc_Jm / self.probeEnergyInJoules
except ZeroDivisionError:
return 0.0

@property
def sizeInBytes(self) -> int:
sz = getsizeof(self.name)
Expand Down
3 changes: 2 additions & 1 deletion ptychodus/api/reconstructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from pathlib import Path

from .product import Product
from .patterns import DiffractionPatternArrayType
from .patterns import BooleanArrayType, DiffractionPatternArrayType


@dataclass(frozen=True)
class ReconstructInput:
patterns: DiffractionPatternArrayType
goodPixelMask: BooleanArrayType
product: Product


Expand Down
2 changes: 1 addition & 1 deletion ptychodus/model/analysis/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(
self._fluorescenceSettings = FluorescenceSettings(settingsRegistry)
self.fluorescenceEnhancer = FluorescenceEnhancer(
self._fluorescenceSettings,
dataMatcher,
productRepository,
upscalingStrategyChooser,
deconvolutionStrategyChooser,
fluorescenceFileReaderChooser,
Expand Down
30 changes: 14 additions & 16 deletions ptychodus/model/analysis/fluorescence.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ptychodus.api.product import Product
from ptychodus.api.typing import RealArrayType

from ..reconstructor import DiffractionPatternPositionMatcher
from ..product import ProductRepository
from .settings import FluorescenceSettings

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,19 +63,19 @@ def get_axis_weights_and_indexes(


class VSPILinearOperator(LinearOperator):
def __init__(self, product: Product, xrf_nchannels: int) -> None:
def __init__(self, product: Product) -> None:
"""
M: number of XRF positions
N: number of ptychography object pixels
P: number of XRF channels
A[M,N] * X[N,P] = B[M,P]
"""
super().__init__(float, (len(product.scan), xrf_nchannels))
super().__init__(float, (len(product.scan), len(product.scan)))
self._product = product

def matmat(self, X: RealArrayType) -> RealArrayType:
AX = numpy.zeros(self.shape, dtype=self.dtype)
def _matvec(self, X: RealArrayType) -> RealArrayType:
AX = numpy.zeros(X.shape, dtype=self.dtype)

probeGeometry = self._product.probe.getGeometry()
dx_p_m = probeGeometry.pixelWidthInMeters
Expand Down Expand Up @@ -103,7 +103,7 @@ def matmat(self, X: RealArrayType) -> RealArrayType:
i_nz = numpy.ravel_multi_index(list(zip(IY.flat, IX.flat)), objectShape)
X_nz = X.take(i_nz, axis=0)

AX[index, :] = numpy.matmul(numpy.outer(wy, wx).ravel(), X_nz)
AX[index] = numpy.matmul(numpy.outer(wy, wx).ravel(), X_nz)

return AX

Expand All @@ -115,7 +115,7 @@ class FluorescenceEnhancer(Observable, Observer):
def __init__(
self,
settings: FluorescenceSettings,
dataMatcher: DiffractionPatternPositionMatcher, # FIXME match XRF too
productRepository: ProductRepository,
upscalingStrategyChooser: PluginChooser[UpscalingStrategy],
deconvolutionStrategyChooser: PluginChooser[DeconvolutionStrategy],
fileReaderChooser: PluginChooser[FluorescenceFileReader],
Expand All @@ -124,7 +124,7 @@ def __init__(
) -> None:
super().__init__()
self._settings = settings
self._dataMatcher = dataMatcher
self._productRepository = productRepository
self._upscalingStrategyChooser = upscalingStrategyChooser
self._deconvolutionStrategyChooser = deconvolutionStrategyChooser
self._fileReaderChooser = fileReaderChooser
Expand Down Expand Up @@ -152,7 +152,7 @@ def setProduct(self, productIndex: int) -> None:
self.notifyObservers()

def getProductName(self) -> str:
return self._dataMatcher.getProductName(self._productIndex)
return self._productRepository[self._productIndex].getName()

def getOpenFileFilterList(self) -> Sequence[str]:
return self._fileReaderChooser.getDisplayNameList()
Expand Down Expand Up @@ -222,14 +222,12 @@ def enhanceFluorescence(self) -> None:
if self._measured is None:
raise ValueError('Fluorescence dataset not loaded!')

reconstructInput = self._dataMatcher.matchDiffractionPatternsWithPositions(
self._productIndex
)
product = self._productRepository[self._productIndex].getProduct()
element_maps: list[ElementMap] = list()

if self._settings.useVSPI.getValue():
measured_emaps = self._measured.element_maps
A = VSPILinearOperator(reconstructInput.product, len(measured_emaps))
A = VSPILinearOperator(product)
B = numpy.stack([b.counts_per_second.flatten() for b in measured_emaps]).T
X, info = gmres(A, B, atol=1e-5) # TODO expose atol

Expand All @@ -246,8 +244,8 @@ def enhanceFluorescence(self) -> None:

for emap in self._measured.element_maps:
logger.info(f'Enhancing "{emap.name}"')
emap_upscaled = upscaler(emap, reconstructInput.product)
emap_enhanced = deconvolver(emap_upscaled, reconstructInput.product)
emap_upscaled = upscaler(emap, product)
emap_enhanced = deconvolver(emap_upscaled, product)
element_maps.append(emap_enhanced)

self._enhanced = FluorescenceDataset(
Expand All @@ -258,7 +256,7 @@ def enhanceFluorescence(self) -> None:
self.notifyObservers()

def getPixelGeometry(self) -> PixelGeometry:
return self._dataMatcher.getObjectPlanePixelGeometry(self._productIndex)
return self._productRepository[self._productIndex].getGeometry().getPixelGeometry()

def getEnhancedElementMap(self, channelIndex: int) -> ElementMap:
if self._enhanced is None:
Expand Down
10 changes: 10 additions & 0 deletions ptychodus/model/patterns/active.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ptychodus.api.geometry import ImageExtent
from ptychodus.api.patterns import (
BooleanArrayType,
DiffractionDataset,
DiffractionMetadata,
DiffractionPatternArray,
Expand Down Expand Up @@ -141,6 +142,15 @@ def insertArray(self, array: DiffractionPatternArray) -> None:

self._changedEvent.set()

def getGoodPixelMask(self) -> BooleanArrayType: # FIXME
return numpy.full(
(
self._diffractionPatternSizer.getHeightInPixels(),
self._diffractionPatternSizer.getWidthInPixels(),
),
True,
)

def getAssembledIndexes(self) -> Sequence[int]:
indexes: list[int] = list()

Expand Down
8 changes: 4 additions & 4 deletions ptychodus/model/product/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def copyScan(self, sourceIndex: int, destinationIndex: int) -> None:
logger.warning(f'Failed to access destination scan {destinationIndex} for copying!')
return

destinationItem.assign(sourceItem)
destinationItem.assignItem(sourceItem)

def getSaveFileFilterList(self) -> Sequence[str]:
return self._builderFactory.getSaveFileFilterList()
Expand Down Expand Up @@ -220,7 +220,7 @@ def copyProbe(self, sourceIndex: int, destinationIndex: int) -> None:
logger.warning(f'Failed to access destination probe {destinationIndex} for copying!')
return

destinationItem.assign(sourceItem)
destinationItem.assignItem(sourceItem)

def getSaveFileFilterList(self) -> Sequence[str]:
return self._builderFactory.getSaveFileFilterList()
Expand Down Expand Up @@ -328,7 +328,7 @@ def copyObject(self, sourceIndex: int, destinationIndex: int) -> None:
logger.warning(f'Failed to access destination object {destinationIndex} for copying!')
return

destinationItem.assign(sourceItem)
destinationItem.assignItem(sourceItem)

def getSaveFileFilterList(self) -> Sequence[str]:
return self._builderFactory.getSaveFileFilterList()
Expand Down Expand Up @@ -370,7 +370,7 @@ def insertNewProduct(
likeIndex: int = -1,
) -> int:
return self._repository.insertNewProduct(
name,
name=name,
comments=comments,
detectorDistanceInMeters=detectorDistanceInMeters,
probeEnergyInElectronVolts=probeEnergyInElectronVolts,
Expand Down
18 changes: 18 additions & 0 deletions ptychodus/model/product/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ def __init__(
self._addGroup('probe', self._probe, observe=True)
self._addGroup('object', self._object, observe=True)

def assignItem(self, item: ProductRepositoryItem, *, notify: bool = True) -> None:
self._metadata.assignItem(item.getMetadata())
self._scan.assignItem(item.getScan())
self._probe.assignItem(item.getProbe())
self._object.assignItem(item.getObject())
self._costs = list(item.getCosts())

if notify:
self._parent.handleCostsChanged(self)

def assign(self, product: Product) -> None:
self._metadata.assign(product.metadata)
self._scan.assign(product.scan)
self._probe.assign(product.probe)
self._object.assign(product.object_)
self._costs = list(product.costs)
self._parent.handleCostsChanged(self)

def syncToSettings(self) -> None:
self._metadata.syncToSettings()
self._scan.syncToSettings()
Expand Down
10 changes: 9 additions & 1 deletion ptychodus/model/product/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,22 @@ def __init__(

self._index = -1

def assign(self, item: MetadataRepositoryItem) -> None:
def assignItem(self, item: MetadataRepositoryItem, *, notify: bool = True) -> None:
self.setName(item.getName())
self.comments.setValue(item.comments.getValue())
self.detectorDistanceInMeters.setValue(item.detectorDistanceInMeters.getValue())
self.probeEnergyInElectronVolts.setValue(item.probeEnergyInElectronVolts.getValue())
self.probePhotonsPerSecond.setValue(item.probePhotonsPerSecond.getValue())
self.exposureTimeInSeconds.setValue(item.exposureTimeInSeconds.getValue())

def assign(self, metadata: ProductMetadata) -> None:
self.setName(metadata.name)
self.comments.setValue(metadata.comments)
self.detectorDistanceInMeters.setValue(metadata.detectorDistanceInMeters)
self.probeEnergyInElectronVolts.setValue(metadata.probeEnergyInElectronVolts)
self.probePhotonsPerSecond.setValue(metadata.probePhotonsPerSecond)
self.exposureTimeInSeconds.setValue(metadata.exposureTimeInSeconds)

def syncToSettings(self) -> None:
for parameter in self.parameters().values():
parameter.syncValueToParent()
Expand Down
2 changes: 2 additions & 0 deletions ptychodus/model/product/object/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ def __init__(
super().__init__(settings, 'from_file')
self._settings = settings
self.filePath = settings.filePath.copy()
self.filePath.setValue(filePath)
self._addParameter('file_path', self.filePath)
self.fileType = settings.fileType.copy()
self.fileType.setValue(fileType)
self._addParameter('file_type', self.fileType)
self._fileReader = fileReader

Expand Down
8 changes: 6 additions & 2 deletions ptychodus/model/product/object/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ptychodus.api.observer import Observable
from ptychodus.api.parametric import ParameterGroup

from .builder import ObjectBuilder
from .builder import FromMemoryObjectBuilder, ObjectBuilder
from .settings import ObjectSettings

logger = logging.getLogger(__name__)
Expand All @@ -32,10 +32,14 @@ def __init__(

self._rebuild()

def assign(self, item: ObjectRepositoryItem) -> None:
def assignItem(self, item: ObjectRepositoryItem) -> None:
self.layerDistanceInMeters.setValue(item.layerDistanceInMeters.getValue(), notify=False)
self.setBuilder(item.getBuilder().copy())

def assign(self, object_: Object) -> None:
builder = FromMemoryObjectBuilder(self._settings, object_)
self.setBuilder(builder)

def syncToSettings(self) -> None:
for parameter in self.parameters().values():
parameter.syncValueToParent()
Expand Down
2 changes: 2 additions & 0 deletions ptychodus/model/product/probe/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ def __init__(
super().__init__(settings, 'from_file')
self._settings = settings
self.filePath = settings.filePath.copy()
self.filePath.setValue(filePath)
self._addParameter('file_path', self.filePath)
self.fileType = settings.fileType.copy()
self.fileType.setValue(fileType)
self._addParameter('file_type', self.fileType)
self._fileReader = fileReader

Expand Down
13 changes: 10 additions & 3 deletions ptychodus/model/product/probe/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from ptychodus.api.parametric import ParameterGroup
from ptychodus.api.probe import Probe, ProbeGeometryProvider

from .builder import ProbeBuilder
from .builder import FromMemoryProbeBuilder, ProbeBuilder
from .multimodal import MultimodalProbeBuilder
from .settings import ProbeSettings

logger = logging.getLogger(__name__)

Expand All @@ -15,11 +16,13 @@ class ProbeRepositoryItem(ParameterGroup):
def __init__(
self,
geometryProvider: ProbeGeometryProvider,
settings: ProbeSettings,
builder: ProbeBuilder,
additionalModesBuilder: MultimodalProbeBuilder,
) -> None:
super().__init__()
self._geometryProvider = geometryProvider
self._settings = settings
self._builder = builder
self._additionalModesBuilder = additionalModesBuilder
self._probe = Probe()
Expand All @@ -29,14 +32,19 @@ def __init__(

self._rebuild()

def assign(self, item: ProbeRepositoryItem) -> None:
def assignItem(self, item: ProbeRepositoryItem) -> None:
self._removeGroup('additional_modes')
self._additionalModesBuilder.removeObserver(self)
self._additionalModesBuilder = item.getAdditionalModesBuilder().copy()
self._additionalModesBuilder.addObserver(self)
self._addGroup('additional_modes', self._additionalModesBuilder, observe=True)

self.setBuilder(item.getBuilder().copy())
self._rebuild()

def assign(self, probe: Probe) -> None:
builder = FromMemoryProbeBuilder(self._settings, probe)
self.setBuilder(builder)

def syncToSettings(self) -> None:
for parameter in self.parameters().values():
Expand All @@ -57,7 +65,6 @@ def setBuilder(self, builder: ProbeBuilder) -> None:
self._builder = builder
self._builder.addObserver(self)
self._addGroup('builder', self._builder, observe=True)
self._rebuild()

def _rebuild(self) -> None:
try:
Expand Down
4 changes: 2 additions & 2 deletions ptychodus/model/product/probe/itemFactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def create(
else FromMemoryProbeBuilder(self._settings, probe)
)
multimodalBuilder = MultimodalProbeBuilder(self._rng, self._settings)
return ProbeRepositoryItem(geometryProvider, builder, multimodalBuilder)
return ProbeRepositoryItem(geometryProvider, self._settings, builder, multimodalBuilder)

def createFromSettings(self, geometryProvider: ProbeGeometryProvider) -> ProbeRepositoryItem:
try:
Expand All @@ -43,4 +43,4 @@ def createFromSettings(self, geometryProvider: ProbeGeometryProvider) -> ProbeRe
builder = self._builderFactory.createDefault()

multimodalBuilder = MultimodalProbeBuilder(self._rng, self._settings)
return ProbeRepositoryItem(geometryProvider, builder, multimodalBuilder)
return ProbeRepositoryItem(geometryProvider, self._settings, builder, multimodalBuilder)
2 changes: 2 additions & 0 deletions ptychodus/model/product/probe/multimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ def _adjustRelativePower(self, probe: WavefieldArrayType) -> WavefieldArrayType:
def build(self, probe: Probe) -> Probe:
if self.numberOfModes.getValue() <= 1:
return probe
elif self.numberOfModes.getValue() == probe.numberOfModes:
return probe

array = self._initializeModes(probe.array)

Expand Down
Loading

0 comments on commit 2f58d31

Please sign in to comment.