Write the model and dataset tutorials, update the dependencies, and various small bug fixes

This commit is contained in:
Abe Levitan
2019-10-04 16:53:22 -04:00
parent bc7caebdd9
commit 120e23ef15
9 changed files with 582 additions and 6 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ class SimplePtycho(CDIModel):
if mask is None:
self.mask = None
else:
self.mask = t.ByteTensor(mask)
self.mask = t.BoolTensor(mask)
# We rescale the probe here so it learns at the same rate as the
# object
+1
View File
@@ -6,3 +6,4 @@ pytorch>=1.2.0
h5py>=2.1
pytest
sphinx
sphinx_rtd_theme
+3 -1
View File
@@ -21,7 +21,9 @@ This script runs a ptychography reconstruction using the SimplePtycho model. Thi
.. literalinclude:: ../../examples/simple_ptycho.py
Running this script leads to a very poor reconstruction on the example data, however depending on the dataset the model can produce reasonable results. When reading this script, note the basic workflow. After the data is loaded, a model is created to match the geometry stored in the dataset, with a default initialization for all the parameters. Next, a reconstruction is run (and the progress reported out). Finally, the finished reconstruction is plotted.First, data is read
Because this script only runs ten iterations, the quality of the reconstruction is relatively poor - however, on this particular dataset, running 75 or so iterations will lead to a surprisingly good reconstruction. On other datasets, however, it will often fail to converge, even in some cases where there is little error in the data.
When reading this script, note the basic workflow. After the data is loaded, a model is created to match the geometry stored in the dataset, with a default initialization for all the parameters. Next, a reconstruction is run (and the progress reported out). Finally, the finished reconstruction is plotted.
Gold Balls Ptycho
+1
View File
@@ -39,6 +39,7 @@ And has optional dependencies on
* `pytest <https://docs.pytest.org/>`_
* `sphinx <https://www.sphinx-doc.org/>`_
* `sphinx-argparse <https://sphinx-argparse.readthedocs.io>`_
* `sphinx_rtd_theme <https://sphinx-rtd-theme.readthedocs.io/en/stable/>`_
* `pathlib2 <https://pypi.org/project/pathlib2/>`_
All of these can be installed via pip or conda. Finally, CDTools is written to be python 2.7+ compatible, but is only actively tested on python 3.
+398 -2
View File
@@ -105,10 +105,406 @@ Now, your file should match the example file in examples/lab_ptycho_data.py.
Datasets
--------
In this section, we will write a dataset class that can be used to import a nonstandard kind of ptychographic data where the beam is scanned longitudinally though the sample rather than rastered across the sample laterally. At the end of this tutorial, we will have written the class defined in
In this section, we will write a bare-bones dataset class for 2D ptychography data to demonstrate the process of writing a new dataset class. At the end of the tutorial, we will have written the file examples/basic_ptycho_dataset.py, which can be consulted for reference.
Basic Idea
++++++++++
At it's core, a dataset object for CDTools is just an object that implements the dataset interface from pytorch. For this reason, the base class (:code:`CDataset`) from which all the datasets are defined is itself a subclass of :code:`torch.utils.data.Dataset`. In addition, CDataset implements an extra layer that allows for a separation between the device (CPU or GPU) that the data is stored on and the device that it returns data on. This allows for GPU-based reconstructions on datasets that are too large to fit into the GPU in their entirety.
The pytorch Dataset interface is very simple. A dataset simply has to define two functions, :code:`__len__()` and :code:`__getitem__()`. Thus, we can always access the data in a Dataset :code:`mydata` using the syntax :code:`mydata[index]` or :code:`mydata[slice]`. Overriding these functions will be the first task in defining a new dataset.
In CDTools datasets, the layer that allows for separation between the device that data is stored on and the device that data is loaded onto is implemented in the :code:`__getitem__()` function. Instead of overriding this function directly, one should override the :code:`_load()` function, which is used internally by :code:`__getitem__()`.
In addition to acting as a pytorch Dataset, CDTools Datasets also work as interfaces to .cxi files. Therefore, when writing a new dataset, it is important to also override the functions :code:`to_cxi()` and :code:`from_cxi()` which handle writing to and reading from cxi files, respectively.
The final piece of the puzzle is the :code:`inspect()` method. This is not required to be defined for all datasets, however it is extremely valuable to offer a simple way of exploring a dataset visually. Therefore it is highly recommended to implement this function, which should load a plot or interactive plot that allows a user to visualize the data that they have loaded.
Writing the Skeleton
++++++++++++++++++++
We can start with the basic skeleton for this file. In addition to our standard imports, we also import the base CDataset class and the data tools. We then define an :code:`__all__` list as good practice, and set up the inheritance of our class.
.. code-block:: python
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from matplotlib import pyplot as plt
from CDTools.datasets import CDataset
from CDTools.tools import data as cdtdata
__all__ = ['BasicPtychoDataset']
class BasicPtychoDataset(CDataset):
"""The standard dataset for a 2D ptychography scan"""
pass
Initialization
++++++++++++++
The next thing to implement is the initialization code. Here we can leverage some of the work already done in the base CDataset class. There are a number of kinds of metadata that can be stored in a .cxi file that aren't related to the kind of experiment you're performing - sample ID, start and end times, and so on. The CDataset's initialization routine handles loading and storing these various kinds of metadata, so we can start the definition of our initialization routine by leveraging this:
.. code-block:: python
def __init__(self, *args, **kwargs):
super(BasicPtychoDataset,self).__init__(*args, **kwargs)
Of course, there is also some data that are unique to this kind of dataset. In this case, those data are the probe translations and the measured diffraction patterns. Therefore, we extend this definition to the following:
.. code-block:: python
def __init__(self, translations, patterns, *args, **kwargs):
"""Initialize the dataset from python objects"""
super(BasicPtychoDataset,self).__init__(*args, **kwargs)
self.translations = t.Tensor(translations).clone()
self.patterns = t.Tensor(patterns).clone()
Dataset Interface
+++++++++++++++++
The next set of functions to write are those that plug into the dataset interface. We want :code:`len(dataset)` to return the number of diffraction patterns, which is straightforward to implement.
For the :code:`_load()` implementation, we need to consider what format the data should be returned in. The standard for all CDTools datasets is to return a tuple of (inputs, output). The inputs should always be defined as a tuple of inputs, even if there is only one input for this kind of data. As we will see later in the section on constructing models, this makes it possible to write the automatic differentiation code in a way that is applicable to every model.
In this case, our "inputs" will be a tuple of (pattern index, probe translation). This is not the only reasonable choice - it would also be possible, for example to define the input as just a pattern index (and store the probe translations in the model). For simple ptychography models with no error correction, it's also possible to just take a probe translation as an input with no index. Requiring both is the compromise that's been implemented in the default ptychography models defined with CDTools, and therefore we will follow that format here.
.. code-block:: python
def __len__(self):
return self.patterns.shape[0]
def _load(self, index):
return (index, self.translations[index]), self.patterns[index]
Remember that it's not needed to worry about what device or datatype the data is stored as here, as the relevant conversions will be performed by the :code:`__getitem()` method defined in the superclass. However, we do generally implement a method, :code:`to()`, that moves the data back and forth between devices and datatypes. This lets a user speed up data loading onto the GPU by preloading the data, for example - provided there is enough space.
.. code-block:: python
def to(self, *args, **kwargs):
"""Sends the relevant data to the given device and dtype"""
super(BasicPtychoDataset,self).to(*args,**kwargs)
self.translations = self.translations.to(*args, **kwargs)
self.patterns = self.patterns.to(*args, **kwargs)
Here we can see that we first make sure to call the superclass function to handle sending any information (such as a pixel mask, or detector background) that would have been defined in CDataset to the relevant device. Then we handle the new objects that are defined specifically for this kind of dataset.
Loading and Saving
++++++++++++++++++
Now we turn to writing the tools to load and save data. First, to load the data, we override :code:`from_cxi()`, which is a factory method. In this case, we start by using the superclass to load the metadata. Then we explicitly load in and add the data that's specific to this dataset class
.. code-block:: python
@classmethod
def from_cxi(cls, cxi_file):
"""Generates a new CDataset from a .cxi file directly"""
# Generate a base dataset
dataset = CDataset.from_cxi(cxi_file)
# Mutate the class to this subclass (BasicPtychoDataset)
dataset.__class__ = cls
# Load the data that is only relevant for this class
patterns, axes = cdtdata.get_data(cxi_file)
translations = cdtdata.get_ptycho_translations(cxi_file)
# And now re-add it
dataset.translations = t.Tensor(translations).clone()
dataset.patterns = t.Tensor(patterns).clone()
return dataset
Now to save the data, we override :code:`to_cxi()`, in a fairly self-explanatory way.
.. code-block:: python
def to_cxi(self, cxi_file):
"""Saves out a BasicPtychoDataset as a .cxi file"""
super(BasicPtychoDataset,self).to_cxi(cxi_file)
cdtdata.add_data(cxi_file, self.patterns, axes=self.axes)
cdtdata.add_ptycho_translations(cxi_file, self.translations)
Note that these functions should be defined to work on h5py file objects representing the .cxi files (.cxi files are just .h5 files with a special formatting).
Inspecting
++++++++++
The final piece of the puzzle is writing a function to look at your data! This is an important thing to work on for a dataset class that you intend to use regularly, as being able to easily peruse your raw data has incalculable value. Here, we satisfy ourselves with just plotting a random diffraction pattern.
.. code-block:: python
def inspect(self):
"""Plots a random diffraction pattern"""
index = np.random.randint(len(self))
plt.figure()
plt.imshow(self.patterns[index,:,:].cpu().numpy())
Notes
+++++
This is a bare-bones class, set up to demonstrate the minimum neccessary to develop a new type of dataset class. As a result, it doesn't implement a number of things that are useful or valuable in practice (and which the default Ptycho2DDataset does implement). That includes a useful data inspector, the ability to load datasets directly from filenames, and default tweaks to how metadata such as backgrounds and masks are loaded.
Models
------
In this section, we will write a model to perform a reconstruction on the axial scanning ptychography d
In this section, we will write a basic model for 2D ptychography reconstructions. At the end of this tutorial, we will have written the class defined in examples/simple_ptycho_model.py
Basic Idea
++++++++++
Just like CDTools Datasets subclass pytorch Datasets, CDTools models subclass pytorch modules (yes, I know they are different words - we use the word "model" in CDTools to conform to usage in the world of ptychography/CDI). The major difference is that the base CDTools models also contains a few standard methods to run automatic differentiation reconstructions on itself. This isn't necessarily the cleanest or most portable approach, but we've found that it feels very natural from the perspective of an end user interacting with the toolbox only through some basic reconstruction scripts.
The models themselves have a :code:`model.forward()` function which contains the real meat. In any CDTools model, this forward function takes in a set of parameters describing the specific diffraction pattern to simulate, and outputs a simulated diffraction pattern. The inputs could be as simple as a diffraction pattern index, or could explicitly include other information like the probe position.
In practice, the forward model is defined in the top level :code:`CDIModel` class from which all other models are derived. The definition is quite simple:
.. code-block:: python
def forward(self, *args):
return self.measurement(self.forward_propagator(self.interaction(*args)))
So we can see that to fully implement this forward model, we have to define the three functions :code:`model.interaction()`, :code:`model.forward_propagator()`, and :code:`model.measurement()`, which simulate conceptual stages in the diffraction process.
Beyond the basic model definition, a few other tools need to be defined. The model has to be able to create itself from a dataset, has to have a loss function defined for use with automtic differentiation, has to know how to plot out it's progress, and has to be able to save out the results of a reconstruction. The details of how to implement all of this in a model are shown below.
Writing the Skeleton
++++++++++++++++++++
Once again, we start with the basic skeleton
.. code-block:: python
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from CDTools.models import CDIModel
from CDTools import tools
__all__ = ['SimplePtycho']
class SimplePtycho(CDIModel):
"""A simple ptychography model for exploring ideas and extensions"""
pass
Note that we imported the full tools package, as we will find ourselves using many low-level functions defined there to implement the model.
Initialization from Python
++++++++++++++++++++++++++
Two initialization functions need to be written. First, we write the :code:`__init__()` function, which initializes the model from a collection of python objects describing the system. We then write an initializer that creates a model using a dataset to define the various parameters.
It's important to note that there's not requirement for what the arguments to the initialization function of any particular model should be, only that they contain enough information to run the simulations! It should be chosen in a model-by-model way to allow for the most transparent code.
.. code-block:: python
def __init__(self, probe_basis, probe_guess, obj_guess,
min_translation = [0,0]):
# We have to initialize the Module
super(SimplePtycho,self).__init__()
# We first save the relevant information
self.min_translation = t.Tensor(min_translation)
self.probe_basis = t.Tensor(probe_basis)
# We rescale the probe so it learns at the same rate as the object
self.probe_norm = t.max(tools.cmath.cabs(probe_guess.to(t.float32)))
self.probe = t.nn.Parameter(probe_guess.to(t.float32)
/ self.probe_norm)
self.obj = t.nn.Parameter(obj_guess.to(t.float32))
Here, we chose to define the model based on a basis matrix describing the probe array, an initial guess at the probe, and an initial object. In addition, an optional offset for the translations is included.
The first part of this initialization is quite straightforward - we create some tensors for the minimum translation and the probe basis. But then, the next two pieces of information that we save are defined as :code:`t.nn.Parameter` objects, not Tensors! Parameters are different from Tensors in two ways.
The first way that they are different is that, by default, they have the :code:`requires_grad` flag set to :code:`True`, which means that the information needed for gradient calculations will be stored on every Tensor that results from a calculation including a Parameter. The second difference is that, when a Parameter is added to a Module, the Module adds that parameter to a list, which can be accessed by calling :code:`module.parameters()`.
The key here is that the model itself is subclassing a pytorch Module. So, every parameter that we will attempt to reconstruct, we add to the CDTools model as a Parameter. This way, the model automatically knows which variables to update with gradient descent, and which to keep as they are. Here, we only need to reconstruct the probe and object.
One final note is that we actually store a scaled version of the probe. This is a hack. It is simply because the Adam optimization method, which is the most commonly used, uses learning rates that scale with the amplitude of the parameter, rather than with the amplitude of it's gradients. Unti pytorch implements separate parameter-group learning rates in Adam, rescaling all the parameters to have a typical amplitude near 1 is the best way to get well-behaved reconstructions.
Initialization from Dataset
+++++++++++++++++++++++++++
To initialize the object from a dataset, we need to start by extracting the relevant information from the dataset. Then we can simply call the constructor we defined earlier.
.. code-block:: python
@classmethod
def from_dataset(cls, dataset):
# First, load the information from the dataset
wavelength = dataset.wavelength
det_basis = dataset.detector_geometry['basis']
det_shape = dataset[0][1].shape
distance = dataset.detector_geometry['distance']
(indices, translations), patterns = dataset[:]
# Then, generate the probe geometry
ewg = tools.initializers.exit_wave_geometry
probe_basis, probe_shape, det_slice = ewg(det_basis,
det_shape,
wavelength,
distance,
opt_for_fft=False)
# Next generate the object geometry
pix_translations = tools.interactions.translations_to_pixel(
probe_basis, translations)
obj_size, min_translation = tools.initializers.calc_object_setup(
probe_shape, pix_translations)
# Finally, initialize the probe and object using this information
probe = tools.initializers.SHARP_style_probe(dataset,
probe_shape,
det_slice)
obj = t.ones(obj_size+(2,))
return cls(probe_basis, probe, obj, min_translation=min_translation)
Here, we start by pulling the basic geometric information from the dataset. Then, we use a number of the basic tools to do calculations such as finding the probe basis from the detector geometry, or calculating how big our object array should be.
Once we have the basic setup ready, we then use one of the initialization functions - in this case, :code:`tools.initializers.SHARP_style_probe`, to find a sensible initialization for the probe. This particular initialization is based on the approach used in the SHARP package. Once all the needed information has been collected, we initialize the object.
The Forward Model
+++++++++++++++++
First, we have to implement the interaction model. This function should take the inputs defining the specific diffraction pattern or collection of patterns, and return an exit wave or set of exit waves that would be expected from that set of inputs. This models the interaction of the probe with the sample
.. code-block:: python
def interaction(self, index, translations):
pix_trans = tools.interactions.translations_to_pixel(self.probe_basis,
translations)
pix_trans -= self.min_translation
return tools.interactions.ptycho_2D_round(self.probe_norm * self.probe,
self.obj,
pix_trans)
Here, we take input in the form of an index and a translation. Note that this has to match the format that is output by the associated datasets that we will use for reconstruction.
We start by mapping the translation, given in real space, into pixel coordinates. Then, we use an "off-the-shelf" interaction model - :code:`ptycho_2d_round`, which models a standard 2D ptychography interaction, but rounds the translations to the nearest whole pixel (does not attempt subpixel translations).
The next three definitions amount to just choosing an off-the-shelf function to simulate each step in the chain.
.. code-block:: python
def forward_propagator(self, wavefields):
return tools.propagators.far_field(wavefields)
def measurement(self, wavefields):
return tools.measurements.intensity(wavefields)
def loss(self, sim_data, real_data):
return tools.losses.amplitude_mse(real_data, sim_data)
The forward propagator maps the exit wave to the wave at the surface of the detector, here using a far-field propagator. The measurement maps that exit wave to a measured pixel value, and the loss defines a loss function to attempt to minimize. The loss function we've chosen - the amplitude mean squared error - is the most broadly applicable one.
Device Management
+++++++++++++++++
Next we need to implement a :code:`to()` function, just like we did in the dataset, to allow the entire model to be moved between the GPU and CPU.
.. code-block:: python
def to(self, *args, **kwargs):
super(SimplePtycho, self).to(*args, **kwargs)
self.min_translation = self.min_translation.to(*args,**kwargs)
self.probe_basis = self.probe_basis.to(*args,**kwargs)
self.probe_norm = self.probe_norm.to(*args,**kwargs)
We start by calling the same function for the superclass, which will take care of moving every parameter attached to the model. Then we manually take care of moving over the other variables which, while not being updated in the gradient descent, still need to be moved over to the new device!
Plotting
++++++++
The base CDIModel class has a function, :code:`model.inspect()`, which looks for a class variable called :code:`plot_list` and plots everything contained within. The plot list should be formatted as a list of tuples, with each tuple containing:
* The title of the plot
* A function that takes in the model and generates the relevant plot
* Optional, a function that takes in the model and returns whether or not the plot should be generated
.. code-block:: python
plot_list = [
('Probe Amplitude',
lambda self, fig: tools.plotting.plot_amplitude(self.probe, fig=fig, basis=self.probe_basis)),
('Probe Phase',
lambda self, fig: tools.plotting.plot_phase(self.probe, fig=fig, basis=self.probe_basis)),
('Object Amplitude',
lambda self, fig: tools.plotting.plot_amplitude(self.obj, fig=fig, basis=self.probe_basis)),
('Object Phase',
lambda self, fig: tools.plotting.plot_phase(self.obj, fig=fig, basis=self.probe_basis))
]
In this case, we've made use of the convenience plotting functions defined in :code:`tools.plotting`.
Saving
++++++
At the moment, there is no consistent way to save out the results across the board. However, a function :code:`save_results()` should be defined, which should save out the results of the reconstruction into some reasonably formatted python object. Here we return a dictionary with the probe and object:
.. code-block:: python
def save_results(self):
probe = tools.cmath.torch_to_complex(self.probe.detach().cpu())
probe = probe * self.probe_norm.detach().cpu().numpy()
obj = tools.cmath.torch_to_complex(self.obj.detach().cpu())
return {'probe':probe,'obj':obj}
Testing
+++++++
We can test this model with a simple script, shown below. By filling in the backend here, we've been able to create a ptychography model that can be accessed and used in reconstructions via the same interface as the models we discussed in the examples section.
.. code-block:: python
from basic_ptycho_dataset import BasicPtychoDataset
from h5py import File
from matplotlib import pyplot as plt
filename = 'example_data/lab_ptycho_data.cxi'
with File(filename, 'r') as f:
dataset = BasicPtychoDataset.from_cxi(f)
model = SimplePtycho.from_dataset(dataset)
model.to(device='cuda')
dataset.get_as(device='cuda')
for i, loss in enumerate(model.Adam_optimize(100, dataset)):
model.inspect(dataset)
print(i,loss)
model.compare(dataset)
plt.show()
Happy modeling!
+69
View File
@@ -0,0 +1,69 @@
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from matplotlib import pyplot as plt
from CDTools.datasets import CDataset
from CDTools.tools import data as cdtdata
__all__ = ['BasicPtychoDataset']
class BasicPtychoDataset(CDataset):
"""The standard dataset for a 2D ptychography scan"""
def __init__(self, translations, patterns, *args, **kwargs):
"""Initialize the dataset from python objects"""
super(BasicPtychoDataset,self).__init__(*args, **kwargs)
self.translations = t.Tensor(translations).clone()
self.patterns = t.Tensor(patterns).clone()
def __len__(self):
return self.patterns.shape[0]
def _load(self, index):
return (index, self.translations[index]), self.patterns[index]
def to(self, *args, **kwargs):
"""Sends the relevant data to the given device and dtype"""
super(BasicPtychoDataset,self).to(*args,**kwargs)
self.translations = self.translations.to(*args, **kwargs)
self.patterns = self.patterns.to(*args, **kwargs)
@classmethod
def from_cxi(cls, cxi_file):
"""Generates a new CDataset from a .cxi file directly"""
# Generate a base dataset
dataset = CDataset.from_cxi(cxi_file)
# Mutate the class to this subclass (BasicPtychoDataset)
dataset.__class__ = cls
# Load the data that is only relevant for this class
patterns, axes = cdtdata.get_data(cxi_file)
translations = cdtdata.get_ptycho_translations(cxi_file)
# And now re-add it
dataset.translations = t.Tensor(translations).clone()
dataset.patterns = t.Tensor(patterns).clone()
return dataset
def to_cxi(self, cxi_file):
"""Saves out a BasicPtychoDataset as a .cxi file"""
super(BasicPtychoDataset,self).to_cxi(cxi_file)
cdtdata.add_data(cxi_file, self.patterns, axes=self.axes)
cdtdata.add_ptycho_translations(cxi_file, self.translations)
def inspect(self):
"""Plots a random diffraction pattern"""
index = np.random.randint(len(self))
plt.figure()
plt.imshow(self.patterns[index,:,:].cpu().numpy())
+1 -1
View File
@@ -4,7 +4,7 @@ import CDTools
from matplotlib import pyplot as plt
# First, we load an example dataset from a .cxi file
filename = 'example_data/AuBalls_700ms_30nmStep_3_6SS_filter.cxi'
filename = 'example_data/lab_ptycho_data.cxi'
dataset = CDTools.datasets.Ptycho2DDataset.from_cxi(filename)
# Next, we create a ptychography model from the dataset
+107
View File
@@ -0,0 +1,107 @@
from __future__ import division, print_function, absolute_import
import numpy as np
import torch as t
from CDTools.models import CDIModel
from CDTools import tools
__all__ = ['SimplePtycho']
class SimplePtycho(CDIModel):
"""A simple ptychography model for exploring ideas and extensions"""
def __init__(self, probe_basis, probe_guess, obj_guess,
min_translation = [0,0]):
# We have to initialize the Module
super(SimplePtycho,self).__init__()
# We first save the relevant information
self.min_translation = t.Tensor(min_translation)
self.probe_basis = t.Tensor(probe_basis)
# We rescale the probe so it learns at the same rate as the object
self.probe_norm = t.max(tools.cmath.cabs(probe_guess.to(t.float32)))
self.probe = t.nn.Parameter(probe_guess.to(t.float32)
/ self.probe_norm)
self.obj = t.nn.Parameter(obj_guess.to(t.float32))
@classmethod
def from_dataset(cls, dataset):
# First, load the information from the dataset
wavelength = dataset.wavelength
det_basis = dataset.detector_geometry['basis']
det_shape = dataset[0][1].shape
distance = dataset.detector_geometry['distance']
(indices, translations), patterns = dataset[:]
# Then, generate the probe geometry
ewg = tools.initializers.exit_wave_geometry
probe_basis, probe_shape, det_slice = ewg(det_basis,
det_shape,
wavelength,
distance,
opt_for_fft=False)
# Next generate the object geometry
pix_translations = tools.interactions.translations_to_pixel(
probe_basis, translations)
obj_size, min_translation = tools.initializers.calc_object_setup(
probe_shape, pix_translations)
# Finally, initialize the probe and object using this information
probe = tools.initializers.SHARP_style_probe(dataset,
probe_shape,
det_slice)
obj = t.ones(obj_size+(2,))
return cls(probe_basis, probe, obj, min_translation=min_translation)
def interaction(self, index, translations):
pix_trans = tools.interactions.translations_to_pixel(self.probe_basis,
translations)
pix_trans -= self.min_translation
return tools.interactions.ptycho_2D_round(self.probe_norm * self.probe,
self.obj,
pix_trans)
def forward_propagator(self, wavefields):
return tools.propagators.far_field(wavefields)
def measurement(self, wavefields):
return tools.measurements.intensity(wavefields)
def loss(self, sim_data, real_data):
return tools.losses.amplitude_mse(real_data, sim_data)
def to(self, *args, **kwargs):
super(SimplePtycho, self).to(*args, **kwargs)
self.min_translation = self.min_translation.to(*args,**kwargs)
self.probe_basis = self.probe_basis.to(*args,**kwargs)
self.probe_norm = self.probe_norm.to(*args,**kwargs)
plot_list = [
('Probe Amplitude',
lambda self, fig: tools.plotting.plot_amplitude(self.probe, fig=fig, basis=self.probe_basis)),
('Probe Phase',
lambda self, fig: tools.plotting.plot_phase(self.probe, fig=fig, basis=self.probe_basis)),
('Object Amplitude',
lambda self, fig: tools.plotting.plot_amplitude(self.obj, fig=fig, basis=self.probe_basis)),
('Object Phase',
lambda self, fig: tools.plotting.plot_phase(self.obj, fig=fig, basis=self.probe_basis))
]
def save_results(self):
probe = tools.cmath.torch_to_complex(self.probe.detach().cpu())
probe = probe * self.probe_norm.detach().cpu().numpy()
obj = tools.cmath.torch_to_complex(self.obj.detach().cpu())
return {'probe':probe,'obj':obj}
if __name__ == '__main__':
+1 -1
View File
@@ -22,7 +22,7 @@ setuptools.setup(
"pathlib2 ; python_version<'3.4'"],
extras_require={
'tests': ["pytest"],
'docs': ["sphinx","sphinx-argparse"],
'docs': ["sphinx","sphinx-argparse","sphinx_rtd_theme"],
":python_version<'3.4'": ["pathlib2"],
},
packages=setuptools.find_packages(),