Skip to content

Commit

Permalink
Misc requested fixes (#60)
Browse files Browse the repository at this point in the history
- [ ] Support replacing saturated (bad) pixel values in diffraction patterns
- [ ] Change diffraction data directory widget to editable combobox
- [ ] Add controls and settings for detector bit depth
- [ ] Add data readers for APS 2-ID beamlines: diffraction patterns & scan positions
- [ ] Avoid rescaling additional probe mode power when loading from file
- [ ] Make scan centroid override behavior more intuitive
- [ ] Fix how dimensions are transposed for MATLAB probe and object readers 
- [ ] Update required PtychoNN version; mention pytorch-gpu package needed for GPU support
- [ ] Clean up extraneous log messages
  • Loading branch information
stevehenke authored Aug 28, 2023
1 parent d943a21 commit ed84ed8
Show file tree
Hide file tree
Showing 33 changed files with 432 additions and 169 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Developer Installation
.. code-block:: shell
$ conda install -n ptychodus -c conda-forge ptychonn
$ conda install -n ptychodus -c conda-forge ptychonn pytorch-gpu
* To launch the `ptychodus` GUI (with the "ptychodus" conda environment activated):
Expand Down
1 change: 1 addition & 0 deletions ptychodus/api/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class DiffractionMetadata:
detectorDistanceInMeters: Optional[Decimal] = None
detectorExtentInPixels: Optional[ImageExtent] = None
detectorPixelGeometry: Optional[PixelGeometry] = None
detectorBitDepth: Optional[int] = None
cropCenterInPixels: Optional[Array2D[int]] = None
probeEnergyInElectronVolts: Optional[Decimal] = None
filePath: Optional[Path] = None
Expand Down
18 changes: 15 additions & 3 deletions ptychodus/controller/data/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import re

from PyQt5.QtCore import Qt, QDir, QModelIndex, QSortFilterProxyModel
from PyQt5.QtCore import Qt, QDir, QFileInfo, QModelIndex, QSortFilterProxyModel
from PyQt5.QtWidgets import QAbstractItemView, QFileSystemModel

from ...api.observer import Observable, Observer
Expand Down Expand Up @@ -49,8 +49,12 @@ def createInstance(cls, presenter: DiffractionDatasetInputOutputPresenter,
controller._setNameFiltersInFileSystemModel)
controller._syncModelToView()

controller._fileSystemModel.rootPathChanged.connect(
view.contentsView.filePathLineEdit.setText)
view.contentsView.directoryComboBox.addItem(
str(fileDialogFactory.getOpenWorkingDirectory()))
view.contentsView.directoryComboBox.addItem(str(Path.home()))
view.contentsView.directoryComboBox.setEditable(True)
view.contentsView.directoryComboBox.textActivated.connect(
controller._handleDirectoryComboBoxActivated)
controller._setRootPath(fileDialogFactory.getOpenWorkingDirectory())

view.contentsView.fileSystemTableView.doubleClicked.connect(
Expand All @@ -68,6 +72,14 @@ def _setRootPath(self, rootPath: Path) -> None:
index = self._fileSystemModel.setRootPath(str(rootPath))
proxyIndex = self._fileSystemProxyModel.mapFromSource(index)
self._view.contentsView.fileSystemTableView.setRootIndex(proxyIndex)
self._view.contentsView.directoryComboBox.setCurrentText(str(rootPath))
self._fileDialogFactory.setOpenWorkingDirectory(rootPath)

def _handleDirectoryComboBoxActivated(self, text: str) -> None:
fileInfo = QFileInfo(text)

if fileInfo.isDir():
self._setRootPath(Path(fileInfo.canonicalFilePath()))

def _handleFileSystemTableDoubleClicked(self, proxyIndex: QModelIndex) -> None:
index = self._fileSystemProxyModel.mapToSource(proxyIndex)
Expand Down
7 changes: 7 additions & 0 deletions ptychodus/controller/data/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def _importMetadata(self) -> None:
if self._view.contentsView.detectorPixelSizeCheckBox.isChecked():
self._presenter.syncDetectorPixelSize()

if self._view.contentsView.detectorBitDepthCheckBox.isChecked():
self._presenter.syncDetectorBitDepth()

if self._view.contentsView.detectorDistanceCheckBox.isChecked():
self._presenter.syncDetectorDistance()

Expand All @@ -50,6 +53,10 @@ def _syncModelToView(self) -> None:
self._view.contentsView.detectorPixelSizeCheckBox.setVisible(canSyncDetectorPixelSize)
self._view.contentsView.detectorPixelSizeCheckBox.setChecked(canSyncDetectorPixelSize)

canSyncDetectorBitDepth = self._presenter.canSyncDetectorBitDepth()
self._view.contentsView.detectorBitDepthCheckBox.setVisible(canSyncDetectorBitDepth)
self._view.contentsView.detectorBitDepthCheckBox.setChecked(canSyncDetectorBitDepth)

canSyncDetectorDistance = self._presenter.canSyncDetectorDistance()
self._view.contentsView.detectorDistanceCheckBox.setVisible(canSyncDetectorDistance)
self._view.contentsView.detectorDistanceCheckBox.setChecked(canSyncDetectorDistance)
Expand Down
30 changes: 21 additions & 9 deletions ptychodus/controller/data/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,34 @@ def createInstance(cls, presenter: DiffractionPatternPresenter,
controller = cls(presenter, view)
presenter.addObserver(controller)

view.thresholdCheckBox.toggled.connect(presenter.setThresholdEnabled)
view.thresholdSpinBox.valueChanged.connect(presenter.setThresholdValue)
view.valueLowerBoundCheckBox.toggled.connect(presenter.setValueLowerBoundEnabled)
view.valueLowerBoundSpinBox.valueChanged.connect(presenter.setValueLowerBound)
view.valueUpperBoundCheckBox.toggled.connect(presenter.setValueUpperBoundEnabled)
view.valueUpperBoundSpinBox.valueChanged.connect(presenter.setValueUpperBound)
view.flipXCheckBox.toggled.connect(presenter.setFlipXEnabled)
view.flipYCheckBox.toggled.connect(presenter.setFlipYEnabled)

controller._syncModelToView()
return controller

def _syncModelToView(self) -> None:
self._view.thresholdCheckBox.setChecked(self._presenter.isThresholdEnabled())

self._view.thresholdSpinBox.blockSignals(True)
self._view.thresholdSpinBox.setRange(self._presenter.getThresholdValueLimits().lower,
self._presenter.getThresholdValueLimits().upper)
self._view.thresholdSpinBox.setValue(self._presenter.getThresholdValue())
self._view.thresholdSpinBox.blockSignals(False)
self._view.valueLowerBoundCheckBox.setChecked(self._presenter.isValueLowerBoundEnabled())

self._view.valueLowerBoundSpinBox.blockSignals(True)
self._view.valueLowerBoundSpinBox.setRange(
self._presenter.getValueLowerBoundLimits().lower,
self._presenter.getValueLowerBoundLimits().upper)
self._view.valueLowerBoundSpinBox.setValue(self._presenter.getValueLowerBound())
self._view.valueLowerBoundSpinBox.blockSignals(False)

self._view.valueUpperBoundCheckBox.setChecked(self._presenter.isValueUpperBoundEnabled())

self._view.valueUpperBoundSpinBox.blockSignals(True)
self._view.valueUpperBoundSpinBox.setRange(
self._presenter.getValueUpperBoundLimits().lower,
self._presenter.getValueUpperBoundLimits().upper)
self._view.valueUpperBoundSpinBox.setValue(self._presenter.getValueUpperBound())
self._view.valueUpperBoundSpinBox.blockSignals(False)

self._view.flipXCheckBox.setChecked(self._presenter.isFlipXEnabled())
self._view.flipYCheckBox.setChecked(self._presenter.isFlipYEnabled())
Expand Down
8 changes: 8 additions & 0 deletions ptychodus/controller/detector/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def createInstance(cls, presenter: DetectorPresenter, apparatusPresenter: Appara
view.numberOfPixelsYSpinBox.valueChanged.connect(presenter.setNumberOfPixelsY)
view.pixelSizeXWidget.lengthChanged.connect(presenter.setPixelSizeXInMeters)
view.pixelSizeYWidget.lengthChanged.connect(presenter.setPixelSizeYInMeters)
view.bitDepthSpinBox.valueChanged.connect(presenter.setBitDepth)
view.detectorDistanceWidget.lengthChanged.connect(presenter.setDetectorDistanceInMeters)

controller._syncModelToView()
Expand All @@ -49,6 +50,13 @@ def _syncModelToView(self) -> None:

self._view.pixelSizeXWidget.setLengthInMeters(self._presenter.getPixelSizeXInMeters())
self._view.pixelSizeYWidget.setLengthInMeters(self._presenter.getPixelSizeYInMeters())

self._view.bitDepthSpinBox.blockSignals(True)
self._view.bitDepthSpinBox.setRange(self._presenter.getBitDepthLimits().lower,
self._presenter.getBitDepthLimits().upper)
self._view.bitDepthSpinBox.setValue(self._presenter.getBitDepth())
self._view.bitDepthSpinBox.blockSignals(False)

self._view.detectorDistanceWidget.setLengthInMeters(
self._presenter.getDetectorDistanceInMeters())

Expand Down
5 changes: 4 additions & 1 deletion ptychodus/controller/reconstructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ def _redrawPlot(self) -> None:
ax.set_xlabel(axisX.label)
ax.set_ylabel(axisY.label)
ax.grid(True)
ax.legend(loc='best')

if len(axisX.series) > 0:
ax.legend(loc='upper right')

self._plotView.figureCanvas.draw()

def _syncModelToView(self) -> None:
Expand Down
10 changes: 10 additions & 0 deletions ptychodus/controller/scan/transformController.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ def createInstance(cls, item: ScanRepositoryItem,

view.transformComboBox.currentTextChanged.connect(item.setTransformByName)
view.jitterRadiusWidget.lengthChanged.connect(item.setJitterRadiusInMeters)

view.centroidXCheckBox.toggled.connect(item.setOverrideCentroidXEnabled)
view.centroidXWidget.lengthChanged.connect(item.setCentroidXInMeters)

view.centroidYCheckBox.toggled.connect(item.setOverrideCentroidYEnabled)
view.centroidYWidget.lengthChanged.connect(item.setCentroidYInMeters)

controller._syncModelToView()
Expand All @@ -39,7 +43,13 @@ def _syncModelToView(self) -> None:
self._view.indexFilterComboBox.setCurrentText(self._item.getIndexFilterName())
self._view.transformComboBox.setCurrentText(self._item.getTransformName())
self._view.jitterRadiusWidget.setLengthInMeters(self._item.getJitterRadiusInMeters())

self._view.centroidXCheckBox.setChecked(self._item.isOverrideCentroidXEnabled)
self._view.centroidXWidget.setEnabled(self._item.isOverrideCentroidXEnabled)
self._view.centroidXWidget.setLengthInMeters(self._item.getCentroidXInMeters())

self._view.centroidYCheckBox.setChecked(self._item.isOverrideCentroidYEnabled)
self._view.centroidYWidget.setEnabled(self._item.isOverrideCentroidYEnabled)
self._view.centroidYWidget.setLengthInMeters(self._item.getCentroidYInMeters())

def update(self, observable: Observable) -> None:
Expand Down
1 change: 1 addition & 0 deletions ptychodus/model/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def configureLogger() -> None:
encoding='utf-8',
level=logging.DEBUG)
logging.getLogger('matplotlib').setLevel(logging.WARNING)
logging.getLogger('tike').setLevel(logging.WARNING)

logger.info(f'Ptychodus {version("ptychodus")}')
logger.info(f'NumPy {version("numpy")}')
Expand Down
11 changes: 8 additions & 3 deletions ptychodus/model/data/active.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ def insertArray(self, array: DiffractionPatternArray) -> None:
if array.getState() == DiffractionPatternState.LOADED:
data = self._diffractionPatternSizer(array.getData())

if self._patternSettings.thresholdEnabled.value:
thresholdValue = self._patternSettings.thresholdValue.value
data[data < thresholdValue] = thresholdValue
if self._patternSettings.valueUpperBoundEnabled.value:
valueLowerBound = self._patternSettings.valueLowerBound.value
valueUpperBound = self._patternSettings.valueUpperBound.value
data[data >= valueUpperBound] = valueLowerBound

if self._patternSettings.valueLowerBoundEnabled.value:
valueLowerBound = self._patternSettings.valueLowerBound.value
data[data < valueLowerBound] = valueLowerBound

if self._patternSettings.flipXEnabled.value:
data = numpy.flip(data, axis=-1)
Expand Down
33 changes: 24 additions & 9 deletions ptychodus/model/data/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,35 @@ def isFlipYEnabled(self) -> bool:
def setFlipYEnabled(self, value: bool) -> None:
self._settings.flipYEnabled.value = value

def isThresholdEnabled(self) -> bool:
return self._settings.thresholdEnabled.value
def isValueLowerBoundEnabled(self) -> bool:
return self._settings.valueLowerBoundEnabled.value

def setThresholdEnabled(self, value: bool) -> None:
self._settings.thresholdEnabled.value = value
def setValueLowerBoundEnabled(self, value: bool) -> None:
self._settings.valueLowerBoundEnabled.value = value

def getThresholdValueLimits(self) -> Interval[int]:
def getValueLowerBoundLimits(self) -> Interval[int]:
return Interval[int](0, self.MAX_INT)

def getThresholdValue(self) -> int:
return self._settings.thresholdValue.value
def getValueLowerBound(self) -> int:
return self._settings.valueLowerBound.value

def setThresholdValue(self, value: int) -> None:
self._settings.thresholdValue.value = value
def setValueLowerBound(self, value: int) -> None:
self._settings.valueLowerBound.value = value

def isValueUpperBoundEnabled(self) -> bool:
return self._settings.valueUpperBoundEnabled.value

def setValueUpperBoundEnabled(self, value: bool) -> None:
self._settings.valueUpperBoundEnabled.value = value

def getValueUpperBoundLimits(self) -> Interval[int]:
return Interval[int](0, self.MAX_INT)

def getValueUpperBound(self) -> int:
return self._settings.valueUpperBound.value

def setValueUpperBound(self, value: int) -> None:
self._settings.valueUpperBound.value = value

def update(self, observable: Observable) -> None:
if observable is self._sizer:
Expand Down
8 changes: 6 additions & 2 deletions ptychodus/model/data/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ def __init__(self, settingsGroup: SettingsGroup) -> None:
self.cropExtentYInPixels = settingsGroup.createIntegerEntry('CropExtentYInPixels', 64)
self.flipXEnabled = settingsGroup.createBooleanEntry('FlipXEnabled', False)
self.flipYEnabled = settingsGroup.createBooleanEntry('FlipYEnabled', False)
self.thresholdEnabled = settingsGroup.createBooleanEntry('ThresholdEnabled', False)
self.thresholdValue = settingsGroup.createIntegerEntry('ThresholdValue', 0)
self.valueLowerBoundEnabled = settingsGroup.createBooleanEntry(
'ValueLowerBoundEnabled', False)
self.valueLowerBound = settingsGroup.createIntegerEntry('ValueLowerBound', 0)
self.valueUpperBoundEnabled = settingsGroup.createBooleanEntry(
'ValueUpperBoundEnabled', False)
self.valueUpperBound = settingsGroup.createIntegerEntry('ValueUpperBound', 65535)

@classmethod
def createInstance(cls, settingsRegistry: SettingsRegistry) -> DiffractionPatternSettings:
Expand Down
14 changes: 14 additions & 0 deletions ptychodus/model/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self, settingsGroup: SettingsGroup) -> None:
self.pixelSizeXInMeters = settingsGroup.createRealEntry('PixelSizeXInMeters', '75e-6')
self.numberOfPixelsY = settingsGroup.createIntegerEntry('NumberOfPixelsY', 1024)
self.pixelSizeYInMeters = settingsGroup.createRealEntry('PixelSizeYInMeters', '75e-6')
self.bitDepth = settingsGroup.createIntegerEntry('BitDepth', 8)
self.detectorDistanceInMeters = settingsGroup.createRealEntry(
'DetectorDistanceInMeters', '2')

Expand Down Expand Up @@ -56,6 +57,9 @@ def getPixelGeometry(self) -> PixelGeometry:
heightInMeters=max(Decimal(), self._settings.pixelSizeYInMeters.value),
)

def getBitDepth(self) -> int:
return max(1, self._settings.bitDepth.value)

def getDetectorDistanceInMeters(self) -> Decimal:
return max(Decimal(), self._settings.detectorDistanceInMeters.value)

Expand Down Expand Up @@ -108,6 +112,16 @@ def getPixelSizeYInMeters(self) -> Decimal:
def setPixelSizeYInMeters(self, value: Decimal) -> None:
self._settings.pixelSizeYInMeters.value = value

def getBitDepthLimits(self) -> Interval[int]:
return Interval[int](1, 64)

def getBitDepth(self) -> int:
limits = self.getBitDepthLimits()
return limits.clamp(self._settings.bitDepth.value)

def setBitDepth(self, value: int) -> None:
self._settings.bitDepth.value = value

def getDetectorDistanceInMeters(self) -> Decimal:
return self._detector.getDetectorDistanceInMeters()

Expand Down
7 changes: 7 additions & 0 deletions ptychodus/model/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ def syncDetectorPixelSize(self) -> None:
self._detectorSettings.pixelSizeYInMeters.value = \
self._metadata.detectorPixelGeometry.heightInMeters

def canSyncDetectorBitDepth(self) -> bool:
return (self._metadata.detectorBitDepth is not None)

def syncDetectorBitDepth(self) -> None:
if self._metadata.detectorBitDepth:
self._detectorSettings.bitDepth.value = self._metadata.detectorBitDepth

def canSyncDetectorDistance(self) -> bool:
return (self._metadata.detectorDistanceInMeters is not None)

Expand Down
10 changes: 2 additions & 8 deletions ptychodus/model/object/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,8 @@ def getSelectedObject(self) -> str:
return self._object.getSelectedName()

def getSelectedObjectArray(self) -> Optional[ObjectArrayType]:
array: Optional[ObjectArrayType] = None

try:
array = self._objectAPI.getSelectedObjectArray()
except ValueError as err:
logger.debug(err)

return array
selectedObject = self._object.getSelectedItem()
return None if selectedObject is None else selectedObject.getArray()

def getSelectableNames(self) -> Sequence[str]:
return self._object.getSelectableNames()
Expand Down
15 changes: 8 additions & 7 deletions ptychodus/model/probe/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,16 @@ def reinitialize(self) -> None:
return

try:
initialProbe = self._initializer()
array = self._initializer()
except Exception:
logger.exception('Failed to reinitialize probe!')
return

numberOfModes = max(self._numberOfModes, 1)
array = self._modesFactory.build(initialProbe, numberOfModes,
self._orthogonalizeModesEnabled, self._modeDecayType,
self._modeDecayRatio)
if self._numberOfModes > 0:
array = self._modesFactory.build(array, self._numberOfModes,
self._orthogonalizeModesEnabled, self._modeDecayType,
self._modeDecayRatio)

self._setArray(array)

def getInitializerSimpleName(self) -> str:
Expand Down Expand Up @@ -170,7 +171,7 @@ def getNumberOfModesLimits(self) -> Interval[int]:
return Interval[int](1, self.MAX_INT)

def getNumberOfModes(self) -> int:
return self._array.shape[0]
return self._array.shape[-3]

def setNumberOfModes(self, number: int) -> None:
if self._numberOfModes != number:
Expand Down Expand Up @@ -210,7 +211,7 @@ def getMode(self, mode: int) -> ProbeArrayType:
return self._array[mode, :, :]

def getModesFlattened(self) -> ProbeArrayType:
return self._array.transpose((1, 0, 2)).reshape(self._array.shape[1], -1)
return self._array.transpose((1, 0, 2)).reshape(self._array.shape[-2], -1)

def getModeRelativePower(self, mode: int) -> Decimal:
if numpy.isnan(self._array).any():
Expand Down
Loading

0 comments on commit ed84ed8

Please sign in to comment.