diff --git a/.gitignore b/.gitignore index 312a681c5e63e704ff86e4e9030f841dfd20c91b..ae878af4de378213b3aa88b306f0c10bd8b6689c 100755 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,6 @@ /figures/ /results/ /logs/ -*.hdf5 -*.png -*.eps -*.mp4 #jupyter checkpoints .ipynb_checkpoints/ diff --git a/MANIFEST.in b/MANIFEST.in old mode 100644 new mode 100755 index 523902ae46b58a1345f8fb9541aa9d2c4c3b5f0b..273f97285be35932338beea1a8b651dc7a238e12 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,6 @@ -global-include *.pyx -global-include *.c \ No newline at end of file +graft tests +graft pyrost/config +graft pyrost/bin +exclude dev.pyx +global-exclude *.py[cod] +global-exclude *.so \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 656459407afa16be7eccdbafda3df3c587ad8bd9..786ec05d72bc032d7f39978906ab07f7ec4f5f4a 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,8 +12,8 @@ # import os import sys - -sys.path.insert(0, os.path.abspath('..')) +import pyrost +# sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- @@ -22,7 +22,7 @@ copyright = '2020, Nikolay Ivanov' author = 'Nikolay Ivanov' # The full version, including alpha/beta/rc tags -release = '0.1.2' +release = '0.1.3' # -- General configuration --------------------------------------------------- diff --git a/docs/figures/1d_sim_fits.png b/docs/figures/1d_sim_fits.png new file mode 100755 index 0000000000000000000000000000000000000000..5cf4ce0369f204c5e5034c39fccfad31276ed9d5 Binary files /dev/null and b/docs/figures/1d_sim_fits.png differ diff --git a/docs/figures/1d_sim_res.png b/docs/figures/1d_sim_res.png new file mode 100755 index 0000000000000000000000000000000000000000..a2d550451abe9f50318a0026c74c23b6289c9e72 Binary files /dev/null and b/docs/figures/1d_sim_res.png differ diff --git a/docs/figures/diatom_image.png b/docs/figures/diatom_image.png new file mode 100755 index 0000000000000000000000000000000000000000..229d9e32776b90ddcd199241f7834dc0dfbf17bb Binary files /dev/null and b/docs/figures/diatom_image.png differ diff --git a/docs/figures/diatom_phase.png b/docs/figures/diatom_phase.png new file mode 100755 index 0000000000000000000000000000000000000000..04ac51669270cc1dfd024c91c71e6d39b5314118 Binary files /dev/null and b/docs/figures/diatom_phase.png differ diff --git a/docs/figures/phase_fit.png b/docs/figures/phase_fit.png new file mode 100755 index 0000000000000000000000000000000000000000..8d0676a68d5daeb2c13eda805fe35cb859be43a5 Binary files /dev/null and b/docs/figures/phase_fit.png differ diff --git a/docs/figures/ptychograph.png b/docs/figures/ptychograph.png new file mode 100644 index 0000000000000000000000000000000000000000..c01f7623eb463b5b0cc41173d5a3091dfa4d7afa Binary files /dev/null and b/docs/figures/ptychograph.png differ diff --git a/docs/figures/sweep_scan.png b/docs/figures/sweep_scan.png new file mode 100755 index 0000000000000000000000000000000000000000..966dfdfd1cf8acd1155e362c5e0b7609dad5a762 Binary files /dev/null and b/docs/figures/sweep_scan.png differ diff --git a/pyrost/data_processing.py b/pyrost/data_processing.py index 7a8a5307d96a01a21b806872966b4073a751f6a8..e9921a1b62e1b74b1774d9e845b1a6d88bf009ed 100755 --- a/pyrost/data_processing.py +++ b/pyrost/data_processing.py @@ -693,7 +693,8 @@ class SpeckleTracking(DataContainer): return {'pixel_map': pixel_map} def iter_update(self, sw_fs, sw_ss=0, ls_pm=3., ls_ri=10., - n_iter=5, f_tol=3e-3, verbose=True, method='search'): + n_iter=5, f_tol=3e-3, verbose=True, method='search', + return_errors=True): """Perform iterative Robust Speckle Tracking update. `ls_ri` and `ls_pm` define high frequency cut-off to supress the noise. Iterative update terminates when the difference between total @@ -723,6 +724,8 @@ class SpeckleTracking(DataContainer): * 'newton' : Iterative Newton's method based on finite difference gradient approximation. * 'search' : Grid search along the search window. + return_errors : bool, optional + Return errors array if True. Returns ------- @@ -730,7 +733,8 @@ class SpeckleTracking(DataContainer): A new :class:`SpeckleTracking` object with the updated `pixel_map` and `reference_image`. list - List of total MSE values for each iteration. + List of total MSE values for each iteration. Only if + `return_errors` is True. """ obj = self.update_reference(ls_ri=ls_ri, sw_ss=sw_ss, sw_fs=sw_fs) errors = [obj.total_mse(ls_ri=ls_ri)] @@ -759,7 +763,10 @@ class SpeckleTracking(DataContainer): print('Iteration No. {:d}: Total MSE = {:.3f}'.format(it, errors[-1])) if errors[-2] - errors[-1] <= f_tol: break - return obj, errors + if return_errors: + return obj, errors + else: + return obj def total_mse(self, ls_ri=3.): """Return average mean-squared-error (MSE) per pixel. diff --git a/pyrost/simulation/__init__.py b/pyrost/simulation/__init__.py index 52326949c39d0be0f3cf642aa51961c5bfa36eb9..ee6275bccc096b7fc6ede7a9023b242058b28207 100755 --- a/pyrost/simulation/__init__.py +++ b/pyrost/simulation/__init__.py @@ -3,4 +3,4 @@ Speckle Tracking scans. Wavefront propagation is based on the Fresnel diffraction theory. """ from .st_sim_param import STParams, parameters -from .st_sim import STSim, STConverter \ No newline at end of file +from .st_sim import STSim, STConverter, converter diff --git a/pyrost/simulation/st_sim.py b/pyrost/simulation/st_sim.py index 9887f9eb2f7b86f91c319ff1c1d0bd9cf70fb2b1..3cd9b9f5a6613aff6d7668fce2738b9103b687ac 100755 --- a/pyrost/simulation/st_sim.py +++ b/pyrost/simulation/st_sim.py @@ -90,6 +90,7 @@ from sys import stdout import h5py import numpy as np from ..protocol import cxi_protocol, ROOT_PATH +from ..data_processing import STData from .st_sim_param import STParams, parameters from ..bin import aperture_wp, barcode_steps, barcode_profile, lens_wp from ..bin import make_frames, make_whitefield @@ -117,14 +118,18 @@ class STSim: def __init__(self, st_params, bsteps=None): self.parameters = st_params - self.__dict__.update(**self.parameters.export_dict()) self._init_logging() self._init_coord() self._init_lens() self._init_barcode(bsteps) self._init_detector() + def __getattr__(self, attr): + if attr in self.parameters: + return self.parameters.__getattr__(attr) + def _init_logging(self): + os.makedirs(self.log_dir, exist_ok=True) self.logger = logging.getLogger(self.__class__.__name__) self.logger.level = logging.INFO filename = os.path.join(self.log_dir, datetime.datetime.now().strftime('%d-%m-%Y_%H-%M-%S.log')) @@ -314,7 +319,8 @@ class STConverter: * basis_vectors : Detector basis vectors. * data : Measured intensity frames. - * defocus : Defocus distance. + * defocus_fs : Defocus distance along the fast detector axis. + * defocus_ss : Defocus distance along the slow detector axis. * distance : Sample-to-detector distance. * energy : Incoming beam photon energy [eV]. * good_frames : An array of good frames' indices. @@ -353,37 +359,88 @@ class STConverter: ini_parsers['protocol'] = self.protocol.export_ini() return ini_parsers - def _pixel_vector(self, st_params): - return self.crd_rat * np.array([st_params.pix_size, st_params.pix_size, 0]) + def export_dict(self, data, st_params): + """Export simulated data `data` (fetched from :func:`STSim.frames` + or :func:`STSim.ptychograph`) and `st_params` to :class:`dict` object. + + Parameters + ---------- + data : numpy.ndarray + Simulated data. + st_params : STParams + Experimental parameters. + + Returns + ------- + data_dict : dict + Dictionary with all the data from `data` and `st_params`. + + See Also + -------- + STConverter - full list of the attributes stored in `data_dict`. + """ + data_dict = {} + + # Initialize basis vectors + pix_vec = self.crd_rat * np.array([st_params.pix_size, st_params.pix_size, 0]) + data_dict['x_pixel_size'] = pix_vec[0] + data_dict['y_pixel_size'] = pix_vec[1] + vec_fs = np.tile(pix_vec * self.unit_vector_fs, (st_params.n_frames, 1)) + vec_ss = np.tile(pix_vec * self.unit_vector_ss, (st_params.n_frames, 1)) + data_dict['basis_vectors'] = np.stack((vec_ss, vec_fs), axis=1) + + # Initialize data + data_dict['data'] = data + data_dict['good_frames'] = np.arange(data.shape[0], + dtype=self.protocol.get_dtype('good_frames')) + data_dict['mask'] = np.ones(data.shape[1:], dtype=self.protocol.get_dtype('mask')) + data_dict['whitefield'] = make_whitefield(mask=data_dict['mask'], data=data) - def _basis_vectors(self, st_params): - pix_vec = self._pixel_vector(st_params) - _vec_fs = np.tile(pix_vec * self.unit_vector_fs, (st_params.n_frames, 1)) - _vec_ss = np.tile(pix_vec * self.unit_vector_ss, (st_params.n_frames, 1)) - return np.stack((_vec_ss, _vec_fs), axis=1) + # Initialize defocus distances + data_dict['defocus_fs'] = self.crd_rat * st_params.defocus + data_dict['defocus_ss'] = self.crd_rat * st_params.defocus - def _defocus(self, st_params): - return self.crd_rat * st_params.defocus + # Initialize sample-to-detector distance + data_dict['distance'] = self.crd_rat * st_params.det_dist - def _distance(self, st_params): - return self.crd_rat * st_params.det_dist + # Initialize beam's wavelength and energy + data_dict['wavelength'] = self.crd_rat * st_params.wl + data_dict['energy'] = self.e_to_wl / data_dict['wavelength'] - def _energy(self, st_params): - return self.e_to_wl / self._wavelength(st_params) + # Initialize region of interest + fs_lb, fs_ub = st_params.fs_roi() + data_dict['roi'] = np.array([0, data.shape[1], fs_lb, fs_ub]) - def _translations(self, st_params): - t_arr = np.zeros((st_params.n_frames, 3), dtype=np.float64) + # Initialize sample translations + t_arr = np.zeros((st_params.n_frames, 3), dtype=self.protocol.get_dtype('translations')) t_arr[:, 0] = -np.arange(0, st_params.n_frames) * st_params.step_size - return self.crd_rat * t_arr + data_dict['translations'] = self.crd_rat * t_arr - def _wavelength(self, st_params): - return self.crd_rat * st_params.wl + for attr in data_dict: + data_dict[attr] = np.asarray(data_dict[attr], dtype=self.protocol.get_dtype(attr)) + return data_dict + + def export_data(self, data, st_params): + """Export simulated data `data` (fetched from :func:`STSim.frames` + or :func:`STSim.ptychograph`) and `st_params` to a data container. + + Parameters + ---------- + data : numpy.ndarray + Simulated data. + st_params : STParams + Experimental parameters. - def _x_pixel_size(self, st_params): - return self._pixel_vector(st_params)[0] + Returns + ------- + STData + Data container with all the data from `data` and `st_params`. - def _y_pixel_size(self, st_params): - return self._pixel_vector(st_params)[1] + See Also + -------- + STConverter - full list of the attributes stored in `data_dict`. + """ + return STData(protocol=self.protocol, **self.export_dict(data, st_params)) def save_sim(self, data, st_sim, dir_path): """Export simulated data `data` (fetched from :func:`STSim.frames` @@ -409,14 +466,13 @@ class STConverter: * {'calc_error.ini', 'calculate_phase.ini', 'generate_pixel_map.ini', 'make_reference.ini', 'speckle_gui.ini', 'update_pixel_map.ini', 'update_translations.ini', 'zernike.ini'} : INI files to work with - Andrew's `speckle_tracking`_ GUI. - - .. _speckle_tracking: https://github.com/andyofmelbourne/speckle-tracking + Andrew's `speckle_tracking <https://github.com/andyofmelbourne/speckle-tracking>`_ + GUI. """ self.save(data=data, st_params=st_sim.parameters, dir_path=dir_path, logger=st_sim.logger) - def save(self, data, st_params, dir_path, logger=None, roi=None): + def save(self, data, st_params, dir_path, logger=None): """Export simulated data `data` (fetched from :func:`STSim.frames` or :func:`STSim.ptychograph`) and `st_params` to `dir_path` folder. @@ -430,8 +486,6 @@ class STConverter: Path to the folder, where all the files are saved. logger : logging.Logger, optional Logging interface. - roi : numpy.ndarray, optional - Region of interest at the detector. See Also -------- @@ -451,23 +505,35 @@ class STConverter: logger.info("Making a cxi file...") logger.info("Using the following cxi protocol:") self.protocol.log(logger) - data_dict = {'data': data, 'mask': np.ones(data.shape[1:], dtype=np.uint8), - 'good_frames': np.arange(data.shape[0])} - data_dict['whitefield'] = make_whitefield(mask=data_dict['mask'], data=data) - if roi is None: - fs_lb, fs_ub = st_params.fs_roi() - roi = np.array([0, data.shape[1], fs_lb, fs_ub]) - data_dict['roi'] = np.asarray(roi) + data_dict = self.export_dict(data, st_params) with h5py.File(os.path.join(dir_path, 'data.cxi'), 'w') as cxi_file: - for attr in self.write_attrs: - if attr in data_dict: - self.protocol.write_cxi(attr, data_dict[attr], cxi_file) - else: - dset = self.__getattribute__('_' + attr)(st_params) - self.protocol.write_cxi(attr, dset, cxi_file) + for attr in data_dict: + self.protocol.write_cxi(attr, data_dict[attr], cxi_file) if logger: logger.info("{:s} saved".format(os.path.join(dir_path, 'data.cxi'))) +def converter(coord_ratio=1e-6, float_precision='float64'): + """Return the default simulation converter. + + Parameters + ---------- + coord_ratio : float, optional + Coordinates ratio between the simulated and saved data. + float_precision: {'float32', 'float64'}, optional + Floating point precision. + + Returns + ------- + STConverter + Default simulation data converter. + + See Also + -------- + STConverter : Full converter class description. + """ + return STConverter(protocol=cxi_protocol(float_precision), + coord_ratio=coord_ratio) + def main(): """Main fuction to run Speckle Tracking simulation and save the results to a CXI file. """ diff --git a/pyrost/simulation/st_sim_param.py b/pyrost/simulation/st_sim_param.py index c6e0e3623ae3d9c8956bec68095d34669cb06b7d..590a2db87bc3b888bc514578eaa61752b4241949 100755 --- a/pyrost/simulation/st_sim_param.py +++ b/pyrost/simulation/st_sim_param.py @@ -126,6 +126,12 @@ class STParams(INIParser): super(STParams, self).__init__(**kwargs) self.__dict__['_lookup'] = self.lookup_dict() + def __iter__(self): + return self._lookup.__iter__() + + def __contains__(self, attr): + return attr in self._lookup + def __getattr__(self, attr): if attr in self._lookup: return self.__dict__[self._lookup[attr]][attr] diff --git a/pytest.ini b/pytest.ini index b30dddcd92a195a3dde59278db4f7f16d82c230a..6e7ae8f6073cc6bdfd5a6073298ccba274d18590 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,5 @@ [pytest] markers = st_sim: test Speckle Tracking simulation package - rst: rest Robust Speckle Tracking package \ No newline at end of file + rst: test Robust Speckle Tracking package + standalone: standalone test \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9af7e6f11bb01f7306f796faf7bfbe3e2955cd94..0000000000000000000000000000000000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest \ No newline at end of file diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index fd2a4be6d6a3a7872b1effcabbecf10233882331..c4a850a8a034992851946031fb1208e2eb84db9f --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ with open('README.md', 'r') as readme: long_description = readme.read() setup(name='pyrost', - version='0.1.2', + version='0.1.3', author='Nikolay Ivanov', author_email="nikolay.ivanov@desy.de", long_description=long_description, @@ -50,11 +50,10 @@ setup(name='pyrost', url="https://github.com/simply-nicky/rst", packages=find_packages(), include_package_data=True, - package_data={'pyrost.bin': ['*.pyx']}, + package_data={'pyrost.bin': ['*.pyx', '*.c'], + 'pyrost': ['config/*.ini']}, install_requires=['Cython', 'CythonGSL', 'h5py', 'numpy', 'scipy',], extras_require={'interactive': ['matplotlib', 'jupyter', 'pyximport']}, - setup_requires=['pytest-runner'], - tests_require=['pytest'], ext_modules=extensions, classifiers=[ "Programming Language :: Python", diff --git a/test_pyrost.py b/tests/test_pyrost.py similarity index 80% rename from test_pyrost.py rename to tests/test_pyrost.py index aa07065d61622d581fafaf4368f8f4a2a916bc25..0ed993abca8b7a37caaab6287caa27e38b0ecaa1 100755 --- a/test_pyrost.py +++ b/tests/test_pyrost.py @@ -4,9 +4,9 @@ import pyrost as rst import pyrost.simulation as st_sim import numpy as np -@pytest.fixture(params=[{'det_dist': 5e5, 'n_frames': 10, 'ap_x': 5, +@pytest.fixture(params=[{'det_dist': 5e5, 'n_frames': 10, 'ap_x': 4, 'ap_y': 1, 'focus': 3e3, 'defocus': 2e2}, - {'det_dist': 4.5e5, 'n_frames': 5, 'ap_x': 8, + {'det_dist': 4.5e5, 'n_frames': 5, 'ap_x': 3, 'ap_y': 1.5, 'focus': 2e3, 'defocus': 1e2}]) def st_params(request): """Return a default instance of simulation parameters. @@ -45,10 +45,17 @@ def exp_data_2d(request): @pytest.fixture(params=['float32', 'float64']) def loader(request): """ - Return a default cxi protocol + Return the default loader. """ return rst.loader(request.param) +@pytest.fixture(params=['float32', 'float64']) +def converter(request): + """ + Return the default loader. + """ + return st_sim.converter(float_precision=request.param) + @pytest.fixture(scope='function') def ini_path(): """Return a path to the experimental speckle tracking data. @@ -68,8 +75,8 @@ def test_st_params(st_params, ini_path): @pytest.mark.st_sim def test_st_sim(st_params): - sim = st_sim.STSim(st_params) - ptych = sim.ptychograph() + with st_sim.STSim(st_params) as sim_obj: + ptych = sim_obj.ptychograph() assert len(ptych.shape) == 3 assert ptych.shape[0] == st_params.n_frames @@ -95,6 +102,7 @@ def test_iter_update(sim_data, loader): data_path = os.path.join(sim_data, 'data.cxi') assert os.path.isfile(data_path) st_data = loader.load(data_path, roi=(0, 1, 400, 1450)) + assert st_data.data.dtype == loader.protocol.known_types['float'] st_obj = st_data.get_st() pixel_map0 = st_obj.pixel_map.copy() st_obj.iter_update(sw_ss=0, sw_fs=150, ls_pm=2.5, ls_ri=15, @@ -108,3 +116,15 @@ def test_data_process_routines(exp_data_2d, loader): data = loader.load(**exp_data_2d) data = data.make_mask(method='eiger-bad') assert (data.get('whitefield') <= 65535).all() + +@pytest.mark.standalone +def test_full(st_params, converter): + with st_sim.STSim(st_params) as sim_obj: + ptych = sim_obj.ptychograph() + data = converter.export_data(ptych, st_params) + assert data.data.dtype == converter.protocol.known_types['float'] + st_obj = data.get_st() + st_res = st_obj.iter_update(sw_fs=20, ls_pm=3, ls_ri=5, + verbose=True, n_iter=10, return_errors=False) + assert (st_obj.pixel_map != st_res.pixel_map).any() + assert st_res.pixel_map.dtype == converter.protocol.known_types['float'] \ No newline at end of file