Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BDP Tools #88

Merged
merged 4 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 12 additions & 47 deletions ptychodus/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import argparse
import sys

from ptychodus.model import ModelArgs, ModelCore
from ptychodus.model import ModelCore
import ptychodus


Expand All @@ -17,17 +17,6 @@ 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 @@ -46,25 +35,18 @@ def main() -> int:
help=argparse.SUPPRESS,
)
parser.add_argument(
'-f',
'--file-prefix',
help='replace file path prefix in settings',
)
parser.add_argument(
# input data product file (batch mode)
'-i',
'--input',
metavar='INPUT_FILE',
type=argparse.FileType('r'),
help=argparse.SUPPRESS,
help='input file (batch mode)',
)
parser.add_argument(
# output data product file (batch mode)
'-o',
'--output',
metavar='OUTPUT_FILE',
type=argparse.FileType('w'),
help=argparse.SUPPRESS,
help='output file (batch mode)',
)
parser.add_argument(
# preprocessed diffraction patterns file (batch mode)
Expand All @@ -87,25 +69,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()
settingsFile = Path(parsedArgs.settings.name) if parsedArgs.settings else None

modelArgs = ModelArgs(
settingsFile=Path(parsedArgs.settings.name) if parsedArgs.settings else None,
patternsFile=Path(parsedArgs.patterns.name) if parsedArgs.patterns else None,
replacementPathPrefix=parsedArgs.file_prefix,
)

with ModelCore(modelArgs, isDeveloperModeEnabled=parsedArgs.dev) as model:
if parsedArgs.write is not None:
return model.stageReconstructionInputs(parsedArgs.write)
with ModelCore(settingsFile, isDeveloperModeEnabled=parsedArgs.dev) as model:
if parsedArgs.patterns is not None:
patternsFilePath = Path(parsedArgs.patterns.name)
model.workflowAPI.importProcessedPatterns(patternsFilePath)

if parsedArgs.batch is not None:
verifyAllArgumentsParsed(parser, unparsedArgs)
Expand All @@ -114,16 +85,10 @@ def main() -> int:
parser.error('Batch mode requires input and output arguments!')
return -1

inputPath = Path(parsedArgs.input.name)
outputPath = Path(parsedArgs.output.name)

if parsedArgs.batch == 'reconstruct':
return model.batchModeReconstruct(inputPath, outputPath)
elif parsedArgs.batch == 'train':
return model.batchModeTrain(inputPath, outputPath)
else:
parser.error(f'Unknown batch mode action \"{parsedArgs.batch}\"!')
return -1
action = parsedArgs.batch
inputFilePath = Path(parsedArgs.input.name)
outputFilePath = Path(parsedArgs.output.name)
return model.batchModeExecute(action, inputFilePath, outputFilePath)

try:
from PyQt5.QtWidgets import QApplication
Expand Down
65 changes: 0 additions & 65 deletions ptychodus/api/automation.py

This file was deleted.

2 changes: 1 addition & 1 deletion ptychodus/api/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import pkgutil
import re

from .automation import FileBasedWorkflow
from .fluorescence import (DeconvolutionStrategy, FluorescenceFileReader, FluorescenceFileWriter,
UpscalingStrategy)
from .object import ObjectPhaseCenteringStrategy, ObjectFileReader, ObjectFileWriter
Expand All @@ -17,6 +16,7 @@
from .probe import FresnelZonePlate, ProbeFileReader, ProbeFileWriter
from .product import ProductFileReader, ProductFileWriter
from .scan import ScanFileReader, ScanFileWriter
from .workflow import FileBasedWorkflow

__all__ = [
'PluginChooser',
Expand Down
44 changes: 22 additions & 22 deletions ptychodus/api/settings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations
from collections.abc import Iterator, Sequence
from dataclasses import dataclass
from decimal import Decimal
from pathlib import Path
from typing import Any, Callable, Final, Generic, TypeVar
from typing import Any, Callable, Generic, TypeVar
from uuid import UUID
import configparser
import logging
Expand All @@ -14,6 +15,12 @@
logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class PathPrefixChange:
findPathPrefix: Path
replacementPathPrefix: Path


class SettingsEntry(Generic[T], Observable):

def __init__(self, name: str, defaultValue: T, stringConverter: Callable[[str], T]) -> None:
Expand Down Expand Up @@ -115,11 +122,9 @@ def update(self, observable: Observable) -> None:


class SettingsRegistry(Observable):
PREFIX_PLACEHOLDER_TEXT: Final[str] = 'PREFIX'

def __init__(self, replacementPathPrefix: str | None) -> None:
def __init__(self) -> None:
super().__init__()
self._replacementPathPrefix = replacementPathPrefix
self._groupList: list[SettingsGroup] = list()
self._fileFilterList: list[str] = ['Initialization Files (*.ini)']

Expand All @@ -142,12 +147,6 @@ def __getitem__(self, index: int) -> SettingsGroup:
def __len__(self) -> int:
return len(self._groupList)

def getReplacementPathPrefix(self) -> str | None:
return self._replacementPathPrefix

def setReplacementPathPrefix(self, replacementPathPrefix: str) -> None:
self._replacementPathPrefix = replacementPathPrefix

def getOpenFileFilterList(self) -> Sequence[str]:
return self._fileFilterList

Expand All @@ -166,13 +165,6 @@ def openSettings(self, filePath: Path) -> None:
for settingsEntry in settingsGroup:
if config.has_option(settingsGroup.name, settingsEntry.name):
valueString = config.get(settingsGroup.name, settingsEntry.name)

if self._replacementPathPrefix is not None \
and isinstance(settingsEntry.value, Path):
if valueString.startswith(SettingsRegistry.PREFIX_PLACEHOLDER_TEXT):
valueString = self._replacementPathPrefix \
+ valueString[len(SettingsRegistry.PREFIX_PLACEHOLDER_TEXT):]

settingsEntry.setValueFromString(valueString)

self.notifyObservers()
Expand All @@ -183,7 +175,9 @@ def getSaveFileFilterList(self) -> Sequence[str]:
def getSaveFileFilter(self) -> str:
return self._fileFilterList[0]

def saveSettings(self, filePath: Path) -> None:
def saveSettings(self,
filePath: Path,
changePathPrefix: PathPrefixChange | None = None) -> None:
config = configparser.ConfigParser(interpolation=None)
setattr(config, 'optionxform', lambda option: option)

Expand All @@ -193,10 +187,16 @@ def saveSettings(self, filePath: Path) -> None:
for settingsEntry in settingsGroup:
valueString = str(settingsEntry.value)

if self._replacementPathPrefix and isinstance(settingsEntry.value, Path):
if valueString.startswith(self._replacementPathPrefix):
valueString = SettingsRegistry.PREFIX_PLACEHOLDER_TEXT \
+ valueString[len(self._replacementPathPrefix):]
if changePathPrefix and isinstance(settingsEntry.value, Path):
try:
relativePath = settingsEntry.value.relative_to(
changePathPrefix.findPathPrefix)
except ValueError:
pass
else:
modifiedPath = changePathPrefix.replacementPathPrefix / relativePath
valueString = str(modifiedPath)

config.set(settingsGroup.name, settingsEntry.name, valueString)

logger.debug(f'Writing settings to \"{filePath}\"')
Expand Down
106 changes: 106 additions & 0 deletions ptychodus/api/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from abc import ABC, abstractmethod
from collections.abc import Mapping
from pathlib import Path
from typing import Any

from ptychodus.api.geometry import ImageExtent
from ptychodus.api.patterns import CropCenter
from ptychodus.api.settings import PathPrefixChange


class WorkflowProductAPI(ABC):

@abstractmethod
def openScan(self, filePath: Path, *, fileType: str | None = None) -> None:
pass

@abstractmethod
def buildScan(self, builderName: str, builderParameters: Mapping[str, Any] = {}) -> None:
pass

@abstractmethod
def openProbe(self, filePath: Path, *, fileType: str | None = None) -> None:
pass

@abstractmethod
def buildProbe(self, builderName: str, builderParameters: Mapping[str, Any] = {}) -> None:
pass

@abstractmethod
def openObject(self, filePath: Path, *, fileType: str | None = None) -> None:
pass

@abstractmethod
def buildObject(self, builderName: str, builderParameters: Mapping[str, Any] = {}) -> None:
pass

@abstractmethod
def reconstruct(self) -> None:
pass

@abstractmethod
def saveProduct(self, filePath: Path, *, fileType: str | None = None) -> None:
pass


class WorkflowAPI(ABC):

@abstractmethod
def openPatterns(
self,
filePath: Path,
*,
fileType: str | None = None,
cropCenter: CropCenter | None = None,
cropExtent: ImageExtent | None = None,
) -> None:
'''opens diffraction patterns from file'''
pass

@abstractmethod
def importProcessedPatterns(self, filePath: Path) -> None:
'''import processed patterns'''
pass

@abstractmethod
def exportProcessedPatterns(self, filePath: Path) -> None:
'''export processed patterns'''
pass

@abstractmethod
def openProduct(self, filePath: Path, *, fileType: str | None = None) -> WorkflowProductAPI:
'''opens product from file'''
pass

@abstractmethod
def createProduct(
self,
name: str,
*,
comments: str = '',
detectorDistanceInMeters: float | None = None,
probeEnergyInElectronVolts: float | None = None,
probePhotonsPerSecond: float | None = None,
exposureTimeInSeconds: float | None = None,
) -> WorkflowProductAPI:
'''creates a new product'''
pass

@abstractmethod
def saveSettings(self,
filePath: Path,
changePathPrefix: PathPrefixChange | None = None) -> None:
pass


class FileBasedWorkflow(ABC):

@abstractmethod
def getFilePattern(self) -> str:
'''UNIX-style filename pattern. For rules see fnmatch from Python standard library.'''
pass

@abstractmethod
def execute(self, api: WorkflowAPI, filePath: Path) -> None:
'''uses workflow API to execute the workflow'''
pass
Loading
Loading