Clean up namespaces and also add a test of RPI with incoherent probes

This commit is contained in:
Abe Levitan committed 2020-12-28 21:32:18 -05:00
1 parent 4be2af8609
commit 6b735a431f
39 files changed
+871 -764

No files matched your search

+1
View File
@@ -3,3 +3,4 @@ from __future__ import division, print_function, absolute_import
from CDTools import tools
from CDTools import datasets
from CDTools import models
+7 -260
View File
@@ -1,7 +1,7 @@
""" This module contains all the datasets for interacting with ptychography data
All the access to data from standard ptychography and CDI experiments is
coordinated through the various datasets defined in this file. They make use
coordinated through the various datasets defined in this module. They make use
of the lower-level data reading and writing functions defined in tools.data,
but critically all of these datasets subclass torch.Dataset. This allows
them to be used as standard torch datasets during reconstructions, which
@@ -9,8 +9,11 @@ helps make it easy to use the various data-handling strategies that are
implemented by default in pytorch (such as drawing data in a random order,
drawing minibatches, etc.)
Subclasses of CDataset are required to define their own implementations
of the following functions:
New Datasets can be defined a subclass of the main CDataset class defined
in the base.py file. Example implementations of all these functions
can be found in the code for the Ptycho2DDataset class. In addition, it is
recommended to read through the tutorial section on defining a new CDI
dataset before attempting to do so
* __init__
* __len__
@@ -20,267 +23,11 @@ of the following functions:
* to_cxi
* inspect
Example implementations of all these functions can be found in the code
for the Ptycho2DDataset class.
In addition, it is recommended to read through the tutorial section on
defining a new ptychography dataset before attempting to do so
"""
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from copy import copy
import h5py
try:
import pathlib
except ImportError:
import pathlib2 as pathlib
from CDTools.tools import data as cdtdata
from CDTools.tools import plotting
from torch.utils import data as torchdata
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
from matplotlib import ticker
__all__ = ['CDataset','Ptycho2DDataset']
#
# This loads and stores all the kinds of metadata that are common to
# All different kinds of diffraction experiments
# Other datasets can subclass this and not worry about loading and
# saving that metadata.
#
class CDataset(torchdata.Dataset):
""" The base dataset class which all other datasets subclass
Subclasses torch.utils.data.Dataset
This base dataset class defines the functionality which should be
common to all subclassed datasets. This includes the loading and
storage of the metadata portions of .cxi files, as well as the tools
needed to allow for easy mixing of data on the CPU and GPU.
"""
def __init__(self, entry_info=None, sample_info=None,
wavelength=None,
detector_geometry=None, mask=None,
background=None):
"""The __init__ function allows construction from python objects.
The detector_geometry dictionary is defined to have the
entries defined by the outputs of data.get_detector_geometry.
Parameters
----------
entry_info : dict
A dictionary containing the entry_info metadata
sample_info : dict
A dictionary containing the sample_info metadata
wavelength : float
The wavelength of light used in the experiment
detector_geometry : dict
A dictionary containing the various detector geometry
parameters
mask : array
A mask for the detector, defined as 1 for live pixels, 0
for dead
background : array
An initial guess for the not-previously-subtracted
detector background
"""
# Force pass-by-value-like behavior to stop strangeness
self.entry_info = copy(entry_info)
self.sample_info = copy(sample_info)
self.wavelength = wavelength
self.detector_geometry = copy(detector_geometry)
if mask is not None:
if isinstance(mask, t.Tensor):
self.mask = mask.detach().to(dtype=t.bool)
else:
self.mask = t.BoolTensor(mask)
else:
self.mask = None
if background is not None:
self.background = t.Tensor(background)
else:
self.background = None
self.get_as(device='cpu')
def to(self,*args,**kwargs):
"""Sends the relevant data to the given device and dtype
This function sends the stored mask and background to the
specified device and dtype
Accepts the same parameters as torch.Tensor.to
"""
# The mask should always stay a uint8, but it should switch devices
mask_kwargs = copy(kwargs)
try:
mask_kwargs.pop('dtype')
except KeyError as r:
pass
if self.mask is not None:
self.mask = self.mask.to(*args,**mask_kwargs)
if self.background is not None:
self.background = self.background.to(*args,**kwargs)
def get_as(self, *args, **kwargs):
"""Sets the dataset to return data on the given device and dtype
Oftentimes there isn't room to store an entire dataset on a GPU,
but it is still worth running the calculation on the GPU even with
the overhead incurred by transferring data back and forth. In that
case, get_as can be used instead of to, to declare a set of
device and dtype that the data should be returned as, whenever it
is accessed through the __getitem__ function (as it would be in
any reconstructions).
Parameters
----------
Accepts the same parameters as torch.Tensor.to
"""
self.get_as_args = (args, kwargs)
def __len__(self):
raise NotImplementedError()
def __getitem__(self, index):
# Deals with loading to appropriate device/dtype, if
# specified via a call to get_as
inputs, outputs = self._load(index)
if hasattr(self, 'get_as_args'):
outputs = outputs.to(*self.get_as_args[0],**self.get_as_args[1])
moved_inputs = []
for inp in inputs:
try:
moved_inputs.append(inp.to(*self.get_as_args[0],**self.get_as_args[1]) )
except:
moved_inputs.append(inp)
else:
moved_inputs = inputs
return moved_inputs, outputs
def _load(self, index):
""" Internal function to load data
In all subclasses of CDataset, a _load function should be defined.
This function is used internally by the global __getitem__ function
defined in the base class, which handles moving data around when
the dataset is (for example) storing the data on the CPU but
getting data as GPU tensors.
It should accept an index or slice, and return output as a tuple.
The first item of the tuple is a tuple containing the inputs to
the forward model for the related ptychography model. The second
item of the tuple should be the set of diffraction patterns
associated with the returned inputs.
Since there is no kind of data stored in a CDataset, this
function is defined as returing a NotImplemented Error
"""
raise NotImplementedError()
@classmethod
def from_cxi(cls, cxi_file):
"""Generates a new CDataset from a .cxi file directly
This is the most commonly used constructor for CDatasets and
subclasses thereof. It populates the dataset using the information
in a .cxi file. It can either take an h5py.File object directly,
or a filename or pathlib object pointing to the file
Parameters
----------
file : str, pathlib.Path, or h5py.File
The .cxi file to load from
Returns
-------
dataset : CDataset
The constructed dataset object
"""
# If a bare string is passed
if isinstance(cxi_file, str) or isinstance(cxi_file, pathlib.Path):
with h5py.File(cxi_file,'r') as f:
return cls.from_cxi(f)
entry_info = cdtdata.get_entry_info(cxi_file)
sample_info = cdtdata.get_sample_info(cxi_file)
wavelength = cdtdata.get_wavelength(cxi_file)
distance, basis, corner = cdtdata.get_detector_geometry(cxi_file)
detector_geometry = {'distance' : distance,
'basis' : basis,
'corner' : corner}
mask = cdtdata.get_mask(cxi_file)
dark = cdtdata.get_dark(cxi_file)
return cls(entry_info = entry_info,
sample_info = sample_info,
wavelength=wavelength,
detector_geometry=detector_geometry,
mask=mask, background=dark)
def to_cxi(self, cxi_file):
"""Saves out a CDataset as a .cxi file
This function saves all the compatible information in a CDataset
object into a .cxi file. This is useful for saving out modified
or simulated datasets
Parameters
----------
cxi_file : str, pathlib.Path, or h5py.File
The .cxi file to write to
"""
# If a bare string is passed
if isinstance(cxi_file, str) or isinstance(cxi_file, pathlib.Path):
with h5py.File(cxi_file,'w') as f:
return self.to_cxi(f)
if self.entry_info is not None:
cdtdata.add_entry_info(cxi_file, self.entry_info)
if self.sample_info is not None:
cdtdata.add_sample_info(cxi_file, self.sample_info)
if self.wavelength is not None:
cdtdata.add_source(cxi_file, self.wavelength)
if self.detector_geometry is not None:
if 'corner' in self.detector_geometry:
corner = self.detector_geometry['corner']
else:
corner = None
cdtdata.add_detector(cxi_file,
self.detector_geometry['distance'],
self.detector_geometry['basis'],
corner = corner)
if self.mask is not None:
cdtdata.add_mask(cxi_file, self.mask)
if self.background is not None:
cdtdata.add_dark(cxi_file, self.background)
def inspect(self):
"""The prototype for the inspect function
In all subclasses of CDataset, an inspect function should be
defined which opens a tool that shows the data in a natural
layout for that kind of experiment. In the base class, no actual
data is stored, so this is defined to raise a NotImplementedError
"""
raise NotImplementedError
from CDTools.datasets.base import CDataset
from CDTools.datasets.ptycho_2d_dataset import Ptycho2DDataset
+278
View File
@@ -0,0 +1,278 @@
""" This module contains the base CDataset class for handling CDI data
Subclasses of CDataset are required to define their own implementations
of the following functions:
* __init__
* __len__
* _load
* to
* from_cxi
* to_cxi
* inspect
Example implementations of all these functions can be found in the code
for the Ptycho2DDataset class.
In addition, it is recommended to read through the tutorial section on
defining a new ptychography dataset before attempting to do so
"""
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from copy import copy
import h5py
try:
import pathlib
except ImportError:
import pathlib2 as pathlib
from CDTools.tools import data as cdtdata
from CDTools.tools import plotting
from torch.utils import data as torchdata
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
from matplotlib import ticker
__all__ = ['CDataset']
#
# This loads and stores all the kinds of metadata that are common to
# All different kinds of diffraction experiments
# Other datasets can subclass this and not worry about loading and
# saving that metadata.
#
class CDataset(torchdata.Dataset):
""" The base dataset class which all other datasets subclass
Subclasses torch.utils.data.Dataset
This base dataset class defines the functionality which should be
common to all subclassed datasets. This includes the loading and
storage of the metadata portions of .cxi files, as well as the tools
needed to allow for easy mixing of data on the CPU and GPU.
"""
def __init__(self, entry_info=None, sample_info=None,
wavelength=None,
detector_geometry=None, mask=None,
background=None):
"""The __init__ function allows construction from python objects.
The detector_geometry dictionary is defined to have the
entries defined by the outputs of data.get_detector_geometry.
Parameters
----------
entry_info : dict
A dictionary containing the entry_info metadata
sample_info : dict
A dictionary containing the sample_info metadata
wavelength : float
The wavelength of light used in the experiment
detector_geometry : dict
A dictionary containing the various detector geometry
parameters
mask : array
A mask for the detector, defined as 1 for live pixels, 0
for dead
background : array
An initial guess for the not-previously-subtracted
detector background
"""
# Force pass-by-value-like behavior to stop strangeness
self.entry_info = copy(entry_info)
self.sample_info = copy(sample_info)
self.wavelength = wavelength
self.detector_geometry = copy(detector_geometry)
if mask is not None:
if isinstance(mask, t.Tensor):
self.mask = mask.detach().to(dtype=t.bool)
else:
self.mask = t.BoolTensor(mask)
else:
self.mask = None
if background is not None:
self.background = t.Tensor(background)
else:
self.background = None
self.get_as(device='cpu')
def to(self,*args,**kwargs):
"""Sends the relevant data to the given device and dtype
This function sends the stored mask and background to the
specified device and dtype
Accepts the same parameters as torch.Tensor.to
"""
# The mask should always stay a uint8, but it should switch devices
mask_kwargs = copy(kwargs)
try:
mask_kwargs.pop('dtype')
except KeyError as r:
pass
if self.mask is not None:
self.mask = self.mask.to(*args,**mask_kwargs)
if self.background is not None:
self.background = self.background.to(*args,**kwargs)
def get_as(self, *args, **kwargs):
"""Sets the dataset to return data on the given device and dtype
Oftentimes there isn't room to store an entire dataset on a GPU,
but it is still worth running the calculation on the GPU even with
the overhead incurred by transferring data back and forth. In that
case, get_as can be used instead of to, to declare a set of
device and dtype that the data should be returned as, whenever it
is accessed through the __getitem__ function (as it would be in
any reconstructions).
Parameters
----------
Accepts the same parameters as torch.Tensor.to
"""
self.get_as_args = (args, kwargs)
def __len__(self):
raise NotImplementedError()
def __getitem__(self, index):
# Deals with loading to appropriate device/dtype, if
# specified via a call to get_as
inputs, outputs = self._load(index)
if hasattr(self, 'get_as_args'):
outputs = outputs.to(*self.get_as_args[0],**self.get_as_args[1])
moved_inputs = []
for inp in inputs:
try:
moved_inputs.append(inp.to(*self.get_as_args[0],**self.get_as_args[1]) )
except:
moved_inputs.append(inp)
else:
moved_inputs = inputs
return moved_inputs, outputs
def _load(self, index):
""" Internal function to load data
In all subclasses of CDataset, a _load function should be defined.
This function is used internally by the global __getitem__ function
defined in the base class, which handles moving data around when
the dataset is (for example) storing the data on the CPU but
getting data as GPU tensors.
It should accept an index or slice, and return output as a tuple.
The first item of the tuple is a tuple containing the inputs to
the forward model for the related ptychography model. The second
item of the tuple should be the set of diffraction patterns
associated with the returned inputs.
Since there is no kind of data stored in a CDataset, this
function is defined as returing a NotImplemented Error
"""
raise NotImplementedError()
@classmethod
def from_cxi(cls, cxi_file):
"""Generates a new CDataset from a .cxi file directly
This is the most commonly used constructor for CDatasets and
subclasses thereof. It populates the dataset using the information
in a .cxi file. It can either take an h5py.File object directly,
or a filename or pathlib object pointing to the file
Parameters
----------
file : str, pathlib.Path, or h5py.File
The .cxi file to load from
Returns
-------
dataset : CDataset
The constructed dataset object
"""
# If a bare string is passed
if isinstance(cxi_file, str) or isinstance(cxi_file, pathlib.Path):
with h5py.File(cxi_file,'r') as f:
return cls.from_cxi(f)
entry_info = cdtdata.get_entry_info(cxi_file)
sample_info = cdtdata.get_sample_info(cxi_file)
wavelength = cdtdata.get_wavelength(cxi_file)
distance, basis, corner = cdtdata.get_detector_geometry(cxi_file)
detector_geometry = {'distance' : distance,
'basis' : basis,
'corner' : corner}
mask = cdtdata.get_mask(cxi_file)
dark = cdtdata.get_dark(cxi_file)
return cls(entry_info = entry_info,
sample_info = sample_info,
wavelength=wavelength,
detector_geometry=detector_geometry,
mask=mask, background=dark)
def to_cxi(self, cxi_file):
"""Saves out a CDataset as a .cxi file
This function saves all the compatible information in a CDataset
object into a .cxi file. This is useful for saving out modified
or simulated datasets
Parameters
----------
cxi_file : str, pathlib.Path, or h5py.File
The .cxi file to write to
"""
# If a bare string is passed
if isinstance(cxi_file, str) or isinstance(cxi_file, pathlib.Path):
with h5py.File(cxi_file,'w') as f:
return self.to_cxi(f)
if self.entry_info is not None:
cdtdata.add_entry_info(cxi_file, self.entry_info)
if self.sample_info is not None:
cdtdata.add_sample_info(cxi_file, self.sample_info)
if self.wavelength is not None:
cdtdata.add_source(cxi_file, self.wavelength)
if self.detector_geometry is not None:
if 'corner' in self.detector_geometry:
corner = self.detector_geometry['corner']
else:
corner = None
cdtdata.add_detector(cxi_file,
self.detector_geometry['distance'],
self.detector_geometry['basis'],
corner = corner)
if self.mask is not None:
cdtdata.add_mask(cxi_file, self.mask)
if self.background is not None:
cdtdata.add_dark(cxi_file, self.background)
def inspect(self):
"""The prototype for the inspect function
In all subclasses of CDataset, an inspect function should be
defined which opens a tool that shows the data in a natural
layout for that kind of experiment. In the base class, no actual
data is stored, so this is defined to raise a NotImplementedError
"""
raise NotImplementedError
+10 -494
View File
@@ -9,508 +9,24 @@ corresponds to the measured data (usually, a diffraction pattern). This
model can then be used as the heart of an automatic differentiation
reconstruction which retrieves the parameters that were used in the model.
A main CDIModel class is defined in the base.py file, and models for
various CDI geometries can be defined as subclasses of this base model.
The subclasses of the main CDIModel class are required to implement a set of
functions defined in the base.py file. Example implementations of
these functions can be found in the code for the SimplePtycho class.
The subclasses of the main CDIModel class are required to define their
own implementations of the following functions:
Finally, it is recommended to read through the tutorial section on
defining a new ptychography model before attempting to do so.
Loading and Saving
------------------
from_dataset
Creates a CDIModel from an appropriate CDataset
simulate_to_dataset
Creates a CDataset from the simulation defined in the model
save_results
Saves out a dictionary with the recovered parameters
Simulation
----------
interaction
Simulates exit waves from experimental parameters
forward_propagator
The propagator from the experiment plane to the detector plane
backward_propagator
Optional, the propagator from the detector plane to the experiment plane
measurement
Simulates the detector readout from a detector plane wavefront
loss
the loss function to report and use for automatic differentiation
Example implementations of all these functions can be found in the code
for the SimplePtycho class.
In addition, it is recommended to read through the tutorial section on
defining a new ptychography model before attempting to do so
"""
from __future__ import division, print_function, absolute_import
import torch as t
from torch.utils import data as torchdata
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
from matplotlib import ticker
import numpy as np
# I don't believe that __all__ really needed, but it's nice to define it
# to be explicit that import * is safe
__all__ = ['CDIModel', 'SimplePtycho', 'FancyPtycho', 'Bragg2DPtycho', 'SMatrixPtycho', 'RPI']
class CDIModel(t.nn.Module):
"""This base model defines all the functions that must be exposed for a valid CDIModel subclass
Most of the functions only raise a NotImplementedError at this level and
must be explicitly defined by any subclass - these are noted explocitly
in the module-level intro. The work of defining the various subclasses
boils down to creating an appropriate implementation for this set of
functions.
"""
def from_dataset(self, dataset):
raise NotImplementedError()
def interaction(self, *args):
raise NotImplementedError()
def forward_propagator(self, exit_wave):
raise NotImplementedError()
def backward_propagator(self, detector_wave):
raise NotImplementedError()
def measurement(self, detector_wave):
raise NotImplementedError()
def forward(self, *args):
"""The complete forward model
This model relies on composing the interaction, forward propagator,
and measurement functions which are required to be defined by all
subclasses. It therefore should not be redefined by the subclasses.
The arguments to this function, for any given subclass, will be
the same as the arguments to the interaction function.
"""
return self.measurement(self.forward_propagator(self.interaction(*args)))
def loss(self, sim_data, real_data):
raise NotImplementedError()
def to(self, *args, **kwargs):
super(CDIModel,self).to(*args,**kwargs)
def simulate_to_dataset(self, args_list):
raise NotImplementedError()
def save_results(self):
raise NotImplementedError()
def AD_optimize(self, iterations, data_loader, optimizer,\
scheduler=None, regularization_factor=None):
"""Runs a round of reconstruction using the provided optimizer
This is the basic automatic differentiation reconstruction tool
which all the other, algorithm-specific tools, use.
Like all the other optimization routines, it is defined as a
generator function which yields the average loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
optimizer : torch.optim.Optimizer
The optimizer to run the reconstruction with
scheduler : torch.optim.lr_scheduler._LRScheduler
Optional, a learning rate scheduler to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
# First, calculate the normalization
normalization = 0
for inputs, patterns in data_loader:
normalization += t.sum(patterns).cpu().numpy()
for it in range(iterations):
loss = 0
N = 0
for inputs, patterns in data_loader:
N += 1
def closure():
optimizer.zero_grad()
sim_patterns = self.forward(*inputs)
if hasattr(self, 'mask'):
loss = self.loss(patterns,sim_patterns, mask=self.mask)
else:
loss = self.loss(patterns,sim_patterns)
if regularization_factor is not None \
and hasattr(self, 'regularizer'):
loss += self.regularizer(regularization_factor)
loss.backward()
return loss
loss += optimizer.step(closure).detach().cpu().numpy()
loss /= normalization
if scheduler is not None:
scheduler.step(loss)
yield loss
def Adam_optimize(self, iterations, dataset, batch_size=15, lr=0.005,
schedule=False, amsgrad=False, subset=None,
regularization_factor=None):
"""Runs a round of reconstruction using the Adam optimizer
This is generally accepted to be the most robust algorithm for use
with ptychography. Like all the other optimization routines,
it is defined as a generator function, which yields the average
loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, The learning rate (alpha) to use
schedule : float
Optional, whether to use the ReduceLROnPlateau scheduler
subset : list(int) or int
Optional, a pattern index or list of pattern indices to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
# Define the optimizer
optimizer = t.optim.Adam(self.parameters(), lr = lr, amsgrad=amsgrad)
# Define the scheduler
if schedule:
scheduler = t.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.2,threshold=1e-9)
else:
scheduler = None
return self.AD_optimize(iterations, data_loader, optimizer,
scheduler=scheduler,
regularization_factor=regularization_factor)
def LBFGS_optimize(self, iterations, dataset, batch_size=None,
lr=0.1,history_size=2, subset=None,
regularization_factor=None):
"""Runs a round of reconstruction using the L-BFGS optimizer
This algorithm is often less stable that Adam, however in certain
situations or geometries it can be shockingly efficient. Like all
the other optimization routines, it is defined as a generator
function which yields the average loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, the learning rate to use
history_size : int
Optional, the length of the history to use.
subset : list(int) or int
Optional, a pattern index or list of pattern indices to ues
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
if batch_size is not None:
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
else:
data_loader = torchdata.DataLoader(dataset)
# Define the optimizer
optimizer = t.optim.LBFGS(self.parameters(),
lr = lr, history_size=history_size)
return self.AD_optimize(iterations, data_loader, optimizer,
regularization_factor=regularization_factor)
def SGD_optimize(self, iterations, dataset, batch_size=None,
lr=0.01, momentum=0, dampening=0, weight_decay=0,
nesterov=False, subset=None, regularization_factor=None):
"""Runs a round of reconstruction using the SGDoptimizer
This algorithm is often less stable that Adam, but it is simpler
and is the basic workhorse of gradience descent.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, the learning rate to use
momentum : float
Optional, the length of the history to use.
subset : list(int) or int
Optional, a pattern index or list of pattern indices to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
if batch_size is not None:
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
else:
data_loader = torchdata.DataLoader(dataset)
# Define the optimizer
optimizer = t.optim.SGD(self.parameters(),
lr = lr, momentum=momentum,
dampening=dampening,
weight_decay=weight_decay,
nesterov=nesterov)
return self.AD_optimize(iterations, data_loader, optimizer,
regularization_factor=regularization_factor)
# By default, the plot_list is empty
plot_list = []
def inspect(self, dataset=None, update=True):
"""Plots all the plots defined in the model's plot_list attribute
If update is set to True, it will update any previously plotted set
of plots, if one exists, and then redraw them. Otherwise, it will
plot a new set, and any subsequent updates will update the new set
Optionally, a dataset can be passed, which then will plot any
registered plots which need to incorporate some information from
the dataset (such as geometry or a comparison with measured data).
Plots can be registered in any subclass by defining the plot_list
attribute. This should be a list of tuples in the following format:
( 'Plot Title', function_to_generate_plot(self),
function_to_determine_whether_to_plot(self))
Where the third element in the tuple (a function that returns
True if the plot is relevant) is not required.
Parameters
----------
dataset : CDataset
Optional, a dataset matched to the model type
update : bool
Whether to update existing plots or plot new ones
"""
first_update = False
if update and hasattr(self, 'figs') and self.figs:
figs = self.figs
elif update:
figs = None
self.figs = []
first_update = True
else:
figs = None
self.figs = []
idx = 0
for plots in self.plot_list:
# If a conditional is included in the plot
try:
if len(plots) >=3 and not plots[2](self):
continue
except TypeError as e:
if len(plots) >= 3 and not plots[2](self, dataset):
continue
name = plots[0]
plotter = plots[1]
if figs is None:
fig = plt.figure()
self.figs.append(fig)
else:
fig = figs[idx]
try:
plotter(self,fig)
plt.title(name)
except TypeError as e:
if dataset is not None:
try:
plotter(self, fig, dataset)
plt.title(name)
except (IndexError, KeyError, AttributeError) as e:
pass
except (IndexError, KeyError, AttributeError) as e:
pass
idx += 1
if update:
plt.draw()
fig.canvas.start_event_loop(0.001)
if first_update:
plt.pause(0.05 * len(self.figs))
def compare(self, dataset):
"""Opens a tool for comparing simulated and measured diffraction patterns
Parameters
----------
dataset : CDataset
A dataset containing the simulated diffraction patterns to compare against
"""
fig, axes = plt.subplots(1,3,figsize=(12,5.3))
fig.tight_layout(rect=[0.02, 0.09, 0.98, 0.96])
axslider = plt.axes([0.15,0.06,0.75,0.03])
def update_colorbar(im):
# If the update brought the colorbar out of whack
# (say, from clicking back in the navbar)
# Holy fuck this was annoying. Sorry future for how
# crappy this solution is.
#if not np.allclose(im.colorbar.ax.get_xlim(),
# (np.min(im.get_array()),
# np.max(im.get_array()))):
if hasattr(im, 'norecurse') and im.norecurse:
im.norecurse=False
return
im.norecurse=True
im.colorbar.set_clim(vmin=np.min(im.get_array()),vmax=np.max(im.get_array()))
im.colorbar.ax.set_ylim(0,1)
im.colorbar.set_ticks(ticker.LinearLocator(numticks=5))
im.colorbar.draw_all()
def update(idx):
idx = int(idx) % len(dataset)
fig.pattern_idx = idx
updating = True if len(axes[0].images) >= 1 else False
inputs, output = dataset[idx]
sim_data = self.forward(*inputs).detach().cpu().numpy()
sim_data = sim_data
meas_data = output.detach().cpu().numpy()
if hasattr(self, 'mask') and self.mask is not None:
mask = self.mask.detach().cpu().numpy()
else:
mask = 1
if not updating:
axes[0].set_title('Simulated')
axes[1].set_title('Measured')
axes[2].set_title('Difference')
sim = axes[0].imshow(sim_data)
meas = axes[1].imshow(meas_data * mask)
diff = axes[2].imshow((sim_data-meas_data) * mask)
cb1 = plt.colorbar(sim, ax=axes[0], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb1.ax.tick_params(labelrotation=20)
cb1.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(sim))
cb2 = plt.colorbar(meas, ax=axes[1], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb2.ax.tick_params(labelrotation=20)
cb2.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(meas))
cb3 = plt.colorbar(diff, ax=axes[2], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb3.ax.tick_params(labelrotation=20)
cb3.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(diff))
else:
sim = axes[0].images[-1]
sim.set_data(sim_data)
update_colorbar(sim)
meas = axes[1].images[-1]
meas.set_data(meas_data * mask)
update_colorbar(meas)
diff = axes[2].images[-1]
diff.set_data((sim_data-meas_data) * mask)
update_colorbar(diff)
# This is dumb but the slider doesn't work unless a reference to it is
# kept somewhere...
self.slider = Slider(axslider, 'Pattern #', 0, len(dataset)-1, valstep=1, valfmt="%d")
self.slider.on_changed(update)
def on_action(event):
if not hasattr(event, 'button'):
event.button = None
if not hasattr(event, 'key'):
event.key = None
if event.key == 'up' or event.button == 'up':
update(fig.pattern_idx - 1)
elif event.key == 'down' or event.button == 'down':
update(fig.pattern_idx + 1)
self.slider.set_val(fig.pattern_idx)
plt.draw()
fig.canvas.mpl_connect('key_press_event',on_action)
fig.canvas.mpl_connect('scroll_event',on_action)
update(0)
from CDTools.models.base import CDIModel
from CDTools.models.simple_ptycho import SimplePtycho
from CDTools.models.fancy_ptycho import FancyPtycho
from CDTools.models.pinhole_plane_ptycho import PinholePlanePtycho
+496
View File
@@ -0,0 +1,496 @@
"""This module contains the base CDIModel class for CDI Models.
The subclasses of the main CDIModel class are required to define their
own implementations of the following functions:
Loading and Saving
------------------
from_dataset
Creates a CDIModel from an appropriate CDataset
simulate_to_dataset
Creates a CDataset from the simulation defined in the model
save_results
Saves out a dictionary with the recovered parameters
Simulation
----------
interaction
Simulates exit waves from experimental parameters
forward_propagator
The propagator from the experiment plane to the detector plane
backward_propagator
Optional, the propagator from the detector plane to the experiment plane
measurement
Simulates the detector readout from a detector plane wavefront
loss
the loss function to report and use for automatic differentiation
"""
from __future__ import division, print_function, absolute_import
import torch as t
from torch.utils import data as torchdata
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
from matplotlib import ticker
import numpy as np
__all__ = ['CDIModel']
class CDIModel(t.nn.Module):
"""This base model defines all the functions that must be exposed for a valid CDIModel subclass
Most of the functions only raise a NotImplementedError at this level and
must be explicitly defined by any subclass - these are noted explocitly
in the module-level intro. The work of defining the various subclasses
boils down to creating an appropriate implementation for this set of
functions.
"""
def from_dataset(self, dataset):
raise NotImplementedError()
def interaction(self, *args):
raise NotImplementedError()
def forward_propagator(self, exit_wave):
raise NotImplementedError()
def backward_propagator(self, detector_wave):
raise NotImplementedError()
def measurement(self, detector_wave):
raise NotImplementedError()
def forward(self, *args):
"""The complete forward model
This model relies on composing the interaction, forward propagator,
and measurement functions which are required to be defined by all
subclasses. It therefore should not be redefined by the subclasses.
The arguments to this function, for any given subclass, will be
the same as the arguments to the interaction function.
"""
return self.measurement(self.forward_propagator(self.interaction(*args)))
def loss(self, sim_data, real_data):
raise NotImplementedError()
def to(self, *args, **kwargs):
super(CDIModel,self).to(*args,**kwargs)
def simulate_to_dataset(self, args_list):
raise NotImplementedError()
def save_results(self):
raise NotImplementedError()
def AD_optimize(self, iterations, data_loader, optimizer,\
scheduler=None, regularization_factor=None):
"""Runs a round of reconstruction using the provided optimizer
This is the basic automatic differentiation reconstruction tool
which all the other, algorithm-specific tools, use.
Like all the other optimization routines, it is defined as a
generator function which yields the average loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
optimizer : torch.optim.Optimizer
The optimizer to run the reconstruction with
scheduler : torch.optim.lr_scheduler._LRScheduler
Optional, a learning rate scheduler to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
# First, calculate the normalization
normalization = 0
for inputs, patterns in data_loader:
normalization += t.sum(patterns).cpu().numpy()
for it in range(iterations):
loss = 0
N = 0
for inputs, patterns in data_loader:
N += 1
def closure():
optimizer.zero_grad()
sim_patterns = self.forward(*inputs)
if hasattr(self, 'mask'):
loss = self.loss(patterns,sim_patterns, mask=self.mask)
else:
loss = self.loss(patterns,sim_patterns)
if regularization_factor is not None \
and hasattr(self, 'regularizer'):
loss += self.regularizer(regularization_factor)
loss.backward()
return loss
loss += optimizer.step(closure).detach().cpu().numpy()
loss /= normalization
if scheduler is not None:
scheduler.step(loss)
yield loss
def Adam_optimize(self, iterations, dataset, batch_size=15, lr=0.005,
schedule=False, amsgrad=False, subset=None,
regularization_factor=None):
"""Runs a round of reconstruction using the Adam optimizer
This is generally accepted to be the most robust algorithm for use
with ptychography. Like all the other optimization routines,
it is defined as a generator function, which yields the average
loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, The learning rate (alpha) to use
schedule : float
Optional, whether to use the ReduceLROnPlateau scheduler
subset : list(int) or int
Optional, a pattern index or list of pattern indices to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
# Define the optimizer
optimizer = t.optim.Adam(self.parameters(), lr = lr, amsgrad=amsgrad)
# Define the scheduler
if schedule:
scheduler = t.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.2,threshold=1e-9)
else:
scheduler = None
return self.AD_optimize(iterations, data_loader, optimizer,
scheduler=scheduler,
regularization_factor=regularization_factor)
def LBFGS_optimize(self, iterations, dataset, batch_size=None,
lr=0.1,history_size=2, subset=None,
regularization_factor=None):
"""Runs a round of reconstruction using the L-BFGS optimizer
This algorithm is often less stable that Adam, however in certain
situations or geometries it can be shockingly efficient. Like all
the other optimization routines, it is defined as a generator
function which yields the average loss each epoch.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, the learning rate to use
history_size : int
Optional, the length of the history to use.
subset : list(int) or int
Optional, a pattern index or list of pattern indices to ues
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
if batch_size is not None:
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
else:
data_loader = torchdata.DataLoader(dataset)
# Define the optimizer
optimizer = t.optim.LBFGS(self.parameters(),
lr = lr, history_size=history_size)
return self.AD_optimize(iterations, data_loader, optimizer,
regularization_factor=regularization_factor)
def SGD_optimize(self, iterations, dataset, batch_size=None,
lr=0.01, momentum=0, dampening=0, weight_decay=0,
nesterov=False, subset=None, regularization_factor=None):
"""Runs a round of reconstruction using the SGDoptimizer
This algorithm is often less stable that Adam, but it is simpler
and is the basic workhorse of gradience descent.
Parameters
----------
iterations : int
How many epochs of the algorithm to run
dataset : CDataset
The dataset to reconstruct against
batch_size : int
Optional, the size of the minibatches to use
lr : float
Optional, the learning rate to use
momentum : float
Optional, the length of the history to use.
subset : list(int) or int
Optional, a pattern index or list of pattern indices to use
regularization_factor : float or list(float)
Optional, if the model has a regularizer defined, the set of parameters to pass the regularizer method
"""
if subset is not None:
# if just one pattern, turn into a list for convenience
if type(subset) == type(1):
subset = [subset]
dataset = torchdata.Subset(dataset, subset)
# Make a dataloader
if batch_size is not None:
data_loader = torchdata.DataLoader(dataset, batch_size=batch_size,
shuffle=True)
else:
data_loader = torchdata.DataLoader(dataset)
# Define the optimizer
optimizer = t.optim.SGD(self.parameters(),
lr = lr, momentum=momentum,
dampening=dampening,
weight_decay=weight_decay,
nesterov=nesterov)
return self.AD_optimize(iterations, data_loader, optimizer,
regularization_factor=regularization_factor)
# By default, the plot_list is empty
plot_list = []
def inspect(self, dataset=None, update=True):
"""Plots all the plots defined in the model's plot_list attribute
If update is set to True, it will update any previously plotted set
of plots, if one exists, and then redraw them. Otherwise, it will
plot a new set, and any subsequent updates will update the new set
Optionally, a dataset can be passed, which then will plot any
registered plots which need to incorporate some information from
the dataset (such as geometry or a comparison with measured data).
Plots can be registered in any subclass by defining the plot_list
attribute. This should be a list of tuples in the following format:
( 'Plot Title', function_to_generate_plot(self),
function_to_determine_whether_to_plot(self))
Where the third element in the tuple (a function that returns
True if the plot is relevant) is not required.
Parameters
----------
dataset : CDataset
Optional, a dataset matched to the model type
update : bool
Whether to update existing plots or plot new ones
"""
first_update = False
if update and hasattr(self, 'figs') and self.figs:
figs = self.figs
elif update:
figs = None
self.figs = []
first_update = True
else:
figs = None
self.figs = []
idx = 0
for plots in self.plot_list:
# If a conditional is included in the plot
try:
if len(plots) >=3 and not plots[2](self):
continue
except TypeError as e:
if len(plots) >= 3 and not plots[2](self, dataset):
continue
name = plots[0]
plotter = plots[1]
if figs is None:
fig = plt.figure()
self.figs.append(fig)
else:
fig = figs[idx]
try:
plotter(self,fig)
plt.title(name)
except TypeError as e:
if dataset is not None:
try:
plotter(self, fig, dataset)
plt.title(name)
except (IndexError, KeyError, AttributeError) as e:
pass
except (IndexError, KeyError, AttributeError) as e:
pass
idx += 1
if update:
plt.draw()
fig.canvas.start_event_loop(0.001)
if first_update:
plt.pause(0.05 * len(self.figs))
def compare(self, dataset):
"""Opens a tool for comparing simulated and measured diffraction patterns
Parameters
----------
dataset : CDataset
A dataset containing the simulated diffraction patterns to compare against
"""
fig, axes = plt.subplots(1,3,figsize=(12,5.3))
fig.tight_layout(rect=[0.02, 0.09, 0.98, 0.96])
axslider = plt.axes([0.15,0.06,0.75,0.03])
def update_colorbar(im):
# If the update brought the colorbar out of whack
# (say, from clicking back in the navbar)
# Holy fuck this was annoying. Sorry future for how
# crappy this solution is.
#if not np.allclose(im.colorbar.ax.get_xlim(),
# (np.min(im.get_array()),
# np.max(im.get_array()))):
if hasattr(im, 'norecurse') and im.norecurse:
im.norecurse=False
return
im.norecurse=True
im.colorbar.set_clim(vmin=np.min(im.get_array()),vmax=np.max(im.get_array()))
im.colorbar.ax.set_ylim(0,1)
im.colorbar.set_ticks(ticker.LinearLocator(numticks=5))
im.colorbar.draw_all()
def update(idx):
idx = int(idx) % len(dataset)
fig.pattern_idx = idx
updating = True if len(axes[0].images) >= 1 else False
inputs, output = dataset[idx]
sim_data = self.forward(*inputs).detach().cpu().numpy()
sim_data = sim_data
meas_data = output.detach().cpu().numpy()
if hasattr(self, 'mask') and self.mask is not None:
mask = self.mask.detach().cpu().numpy()
else:
mask = 1
if not updating:
axes[0].set_title('Simulated')
axes[1].set_title('Measured')
axes[2].set_title('Difference')
sim = axes[0].imshow(sim_data)
meas = axes[1].imshow(meas_data * mask)
diff = axes[2].imshow((sim_data-meas_data) * mask)
cb1 = plt.colorbar(sim, ax=axes[0], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb1.ax.tick_params(labelrotation=20)
cb1.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(sim))
cb2 = plt.colorbar(meas, ax=axes[1], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb2.ax.tick_params(labelrotation=20)
cb2.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(meas))
cb3 = plt.colorbar(diff, ax=axes[2], orientation='horizontal',format='%.2e',ticks=ticker.LinearLocator(numticks=5),pad=0.1,fraction=0.1)
cb3.ax.tick_params(labelrotation=20)
cb3.ax.callbacks.connect('xlim_changed', lambda ax: update_colorbar(diff))
else:
sim = axes[0].images[-1]
sim.set_data(sim_data)
update_colorbar(sim)
meas = axes[1].images[-1]
meas.set_data(meas_data * mask)
update_colorbar(meas)
diff = axes[2].images[-1]
diff.set_data((sim_data-meas_data) * mask)
update_colorbar(diff)
# This is dumb but the slider doesn't work unless a reference to it is
# kept somewhere...
self.slider = Slider(axslider, 'Pattern #', 0, len(dataset)-1, valstep=1, valfmt="%d")
self.slider.on_changed(update)
def on_action(event):
if not hasattr(event, 'button'):
event.button = None
if not hasattr(event, 'key'):
event.key = None
if event.key == 'up' or event.button == 'up':
update(fig.pattern_idx - 1)
elif event.key == 'down' or event.button == 'down':
update(fig.pattern_idx + 1)
self.slider.set_val(fig.pattern_idx)
plt.draw()
fig.canvas.mpl_connect('key_press_event',on_action)
fig.canvas.mpl_connect('scroll_event',on_action)
update(0)
+1
View File
@@ -12,6 +12,7 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['Bragg2DPtycho']
#
# Key ideas:
+1
View File
@@ -11,6 +11,7 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['FancyPtycho']
class FancyPtycho(CDIModel):
+1
View File
@@ -11,6 +11,7 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['Multislice2DPtycho']
class Multislice2DPtycho(CDIModel):
+1
View File
@@ -11,6 +11,7 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['PinholePlanePtycho']
class PinholePlanePtycho(CDIModel):
+2
View File
@@ -13,6 +13,8 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['RPI']
#
# This model has to work a bit differently from a ptychography model
# because a typical RPI dataset will have lots of images, each of which
+1
View File
@@ -11,6 +11,7 @@ from datetime import datetime
import numpy as np
from copy import copy
__all__ = ['SMatrixPtycho']
class SMatrixPtycho(CDIModel):
+2
View File
@@ -11,6 +11,8 @@ from matplotlib import pyplot as plt
from datetime import datetime
import numpy as np
__all__ = ['SimplePtycho']
class SimplePtycho(CDIModel):
"""A simple ptychography model for exploring ideas and extensions
+19 -1
View File
@@ -1,13 +1,31 @@
""" This module contains various packages of tools supporting common needs
It at times feels a bit silly to keep all the tools siloed under a particular
subpackage such as "projectors" or "measurements", especially given that
"flat is better than nested", but I find something comforting about the
organization and the ability to only pull in the particular set of tools
that one needs for a specific application.
The submodules are all structured as modules with their own __init__ files,
which use an import * statement to import from a file defining the various
functions. This is done to prevent leakage of imported packages into the
namespace of CDTools. I know, we're all consenting adults, but I just hate
having numpy and torch defined under CDTools.tools.cmath, you know?
"""
from __future__ import division, print_function, absolute_import
from CDTools.tools import cmath
from CDTools.tools import losses
from CDTools.tools import data
from CDTools.tools import image_processing
from CDTools.tools import initializers
from CDTools.tools import losses
from CDTools.tools import plotting
from CDTools.tools import projectors
from CDTools.tools import interactions
from CDTools.tools import propagators
from CDTools.tools import measurements
from CDTools.tools import analysis
from CDTools.tools import atoms
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.analysis.analysis import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.atoms.atoms import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.cmath.cmath import *
@@ -12,8 +12,8 @@ import numpy as np
import torch as t
__all__ = ['complex_to_torch','torch_to_complex','cabssq','cabs','cconj',
'cmult', 'cdiv', 'cphase', 'fftshift', 'ifftshift','expi']
__all__ = ['complex_to_torch', 'torch_to_complex', 'cabssq', 'cabs', 'cconj',
'cmult', 'cdiv', 'cphase', 'fftshift', 'ifftshift', 'expi']
#
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.data.data import *
File renamed without changes.
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.image_processing.image_processing import *
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.initializers.initializers import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.interactions.interactions import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.losses.losses import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.measurements.measurements import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.plotting.plotting import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.projectors.projectors import *
File renamed without changes.
+3
View File
@@ -0,0 +1,3 @@
from __future__ import division, print_function, absolute_import
from CDTools.tools.propagators.propagators import *
@@ -14,7 +14,10 @@ from matplotlib import pyplot as plt
__all__ = ['far_field', 'near_field',
'generate_angular_spectrum_propagator',
'inverse_far_field', 'inverse_near_field']
'inverse_far_field', 'inverse_near_field',
'generate_high_NA_k_intensity_map',
'high_NA_far_field',
'generate_generalized_angular_spectrum_propagator']
def far_field(wavefront):
+9 -6
View File
@@ -9,7 +9,8 @@ from torch.utils.data import Subset
ss_filename = 'example_data/Optical_Data_ss.cxi'
with open('example_data/Optical_ptycho.pickle', 'rb') as f:
#with open('example_data/Optical_ptycho.pickle', 'rb') as f:
with open('example_data/Optical_ptycho_incoherent.pickle', 'rb') as f:
ptycho_results = pickle.load(f)
probe = ptycho_results['probe']
@@ -19,7 +20,7 @@ dataset = CDTools.datasets.Ptycho2DDataset.from_cxi(ss_filename)
# Next, we create a ptychography model from the dataset
# Note that we explicitly as for two incoherent probe modes
model = CDTools.models.RPI.from_dataset(dataset, probe, [800,800],
model = CDTools.models.RPI.from_dataset(dataset, probe, [900,900],
background=background, n_modes=2)
@@ -31,12 +32,14 @@ dataset.get_as(device='cuda')
# The regularization is an L2 regularizer that empirically helps accelerate
# convergence
for i, loss in enumerate(model.LBFGS_optimize(30, dataset, lr=0.4, regularization_factor=[0.05,0.05])):#0.1)):
model.inspect(dataset)
#model.inspect(dataset)
print(i,loss)
model.inspect(dataset)
# Now we use the regularizer to damp all but the top modes
for i, loss in enumerate(model.LBFGS_optimize(20, dataset, lr=0.4, regularization_factor=[0.001,0.1])):
model.inspect(dataset)
for i, loss in enumerate(model.LBFGS_optimize(50, dataset, lr=0.4, regularization_factor=[0.001,0.1])):
#model.inspect(dataset)
print(i,loss)
results = model.save_results()