diff --git a/.travis.yml b/.travis.yml index 5dbf2ec..d7e6cde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,10 +53,11 @@ before_install: install: - export GIT_FULL_HASH=`git rev-parse HEAD` - - conda create -n testenv nose python=$TRAVIS_PYTHON_VERSION pytest coverage pip databroker bluesky flake8 pyFAI mongoquery codecov attrs metadatastore filestore -c conda-forge -c lightsource2-tag -c soft-matter + - conda create -n testenv nose python=$TRAVIS_PYTHON_VERSION pytest coverage pip databroker flake8 pyFAI mongoquery codecov attrs metadatastore filestore -c conda-forge -c lightsource2-tag -c soft-matter - source activate testenv - 'pip install https://github.com/NSLS-II/portable-mds/zipball/master#egg=portable_mds' - 'pip install https://github.com/NSLS-II/portable-fs/zipball/master#egg=portable_fs' + - 'pip install https://github.com/NSLS-II/bluesky/zipball/master#egg=bluesky' - python setup.py install # Need to clean the python build directory (and other cruft) or pytest is # going to find the build directory and get confused why there are two sets diff --git a/xpdsim/dets.py b/xpdsim/dets.py index b243426..e4e0c86 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -23,7 +23,6 @@ from cycler import cycler from pims import ImageSequence from pkg_resources import resource_filename as rs_fn -from bluesky.utils import new_uid DATA_DIR = rs_fn('xpdsim', 'data/') @@ -61,12 +60,13 @@ class SimulatedPE1C(be.ReaderWithFileStore): """ def __init__(self, name, read_fields, fs, shutter=None, - dark_fields=None, **kwargs): + dark_fields=None, filter_bank=None, **kwargs): self.images_per_set = PutGet() self.number_of_sets = PutGet() self.cam = SimulatedCam() self.shutter = shutter self._staged = False + self.filter_bank = filter_bank super().__init__(name, read_fields, fs=fs, **kwargs) self.ready = True # work around a hack in Reader if dark_fields: @@ -75,39 +75,18 @@ def __init__(self, name, read_fields, fs, shutter=None, else: self._dark_fields = None - def trigger(self): + def trigger_read(self): + rv = super().trigger_read() if self.shutter and self._dark_fields and \ - self.shutter.read()['rad']['value'] == 0: - read_v = {field: {'value': func(), 'timestamp': ttime.time()} - for field, func in self._dark_fields.items() - if field in self.read_attrs} - self._result.clear() - for idx, (name, reading) in enumerate(read_v.items()): - # Save the actual reading['value'] to disk and create a record - # in FileStore. - np.save('{}_{}.npy'.format(self._path_stem, idx), - reading['value']) - datum_id = new_uid() - self.fs.insert_datum(self._resource_id, datum_id, - dict(index=idx)) - # And now change the reading in place, replacing the value with - # a reference to FileStore. - reading['value'] = datum_id - self._result[name] = reading - - delay_time = self.exposure_time - if delay_time: - if self.loop.is_running(): - st = be.SimpleStatus() - self.loop.call_later(delay_time, st._finished) - return st - else: - ttime.sleep(delay_time) - - return be.NullStatus() - - else: - return super().trigger() + self.shutter.read()['rad']['value'] == 0: + rv = {field: {'value': func(), 'timestamp': ttime.time()} + for field, func in self._dark_fields.items() + if field in self.read_attrs} + read_v = dict(rv) + read_v['pe1_image']['value'] = read_v['pe1_image']['value'].copy() + if self.filter_bank: + read_v['pe1_image']['value'] *= self.filter_bank.get_attenuation() + return read_v def build_image_cycle(path): @@ -124,7 +103,10 @@ def build_image_cycle(path): Cycler: The iterable like object to cycle through the images """ - imgs = ImageSequence(os.path.join(path, '*.tif*')) + if isinstance(path, str): + imgs = ImageSequence(os.path.join(path, '*.tif*'), dtype=np.float64) + else: + imgs = [np.ones(path)] return cycler(pe1_image=[i for i in imgs]) @@ -133,7 +115,7 @@ def build_image_cycle(path): chess_path = os.path.join(DATA_DIR, 'chess/') -def det_factory(name, fs, path, shutter=None, **kwargs): +def det_factory(name, fs, path, shutter=None, filter_bank=None, **kwargs): """Build a detector using real images Parameters @@ -157,6 +139,8 @@ def det_factory(name, fs, path, shutter=None, **kwargs): def nexter(): return next(gen)['pe1_image'] + kwargs['read_fields'] = {'pe1_image': lambda: nexter()} + if shutter: stream_piece = next(gen) sample_img = stream_piece['pe1_image'] @@ -165,12 +149,10 @@ def nexter(): def dark_nexter(): return np.zeros(sample_img.shape) - return SimulatedPE1C(name, - {'pe1_image': lambda: nexter()}, fs=fs, - shutter=shutter, - dark_fields={'pe1_image': lambda: dark_nexter()}, - **kwargs) + kwargs.update(shutter=shutter, + dark_fields={'pe1_image': lambda: dark_nexter()}) + + if filter_bank: + kwargs.update(filter_bank=filter_bank) - return SimulatedPE1C(name, - {'pe1_image': lambda: nexter()}, fs=fs, - **kwargs) + return SimulatedPE1C(name, fs=fs, **kwargs) diff --git a/xpdsim/filter.py b/xpdsim/filter.py new file mode 100644 index 0000000..b528cc6 --- /dev/null +++ b/xpdsim/filter.py @@ -0,0 +1,37 @@ +import bluesky.examples as be + + +class FilterBank(): + def __init__(self, attenuations=None): + if attenuations is None: + attenuations = {'filter1': .5, 'filter2': .5, 'filter3': .5, + 'filter4': .5} + self.filter_list = [] + for k, v in attenuations.items(): + f = XRayFilter(k, {'rad': lambda x: x}, {'x': 0}, v) + self.filter_list.append(f) + setattr(self, k, f) + + def get_attenuation(self): + totalAttenuation = 1 + for i in self.filter_list: + totalAttenuation *= i.get_XRayFilter_attenuation() + return totalAttenuation + + +class XRayFilter(be.Mover): + def __init__(self, name, fields, initial_set, attenuation, **kwargs): + self.attenuation = attenuation + super().__init__(name, fields, initial_set, **kwargs) + + def get_XRayFilter_attenuation(self): + position_info = self.read() + position_info_subdict = position_info.get('rad') + position_info_sub_subdict = position_info_subdict.get('value') + if (position_info_sub_subdict == 0): + return 1 + else: + return self.attenuation + + +XRayFilterBankExample = FilterBank() diff --git a/xpdsim/tests/.test_dets.py.swp b/xpdsim/tests/.test_dets.py.swp new file mode 100644 index 0000000..a2c0fa9 Binary files /dev/null and b/xpdsim/tests/.test_dets.py.swp differ diff --git a/xpdsim/tests/test_dets.py b/xpdsim/tests/test_dets.py index d7c4f27..90ca6c6 100644 --- a/xpdsim/tests/test_dets.py +++ b/xpdsim/tests/test_dets.py @@ -1,4 +1,5 @@ -from ..dets import det_factory, build_image_cycle, nsls_ii_path, chess_path +from ..dets import (det_factory, build_image_cycle, # chess_path + ) from bluesky.plans import Count, abs_set from bluesky.tests.utils import setup_test_run_engine from numpy.testing import assert_array_equal @@ -6,8 +7,12 @@ from ..movers import shctl1 import numpy as np import bluesky.examples as be +from ..filter import XRayFilterBankExample -test_params = [('nslsii', nsls_ii_path), ('chess', chess_path)] +# Note the missing Chess data, there seems to be a de-syncing of the det and +# cycle which causes the tests to not pass, FIXME +# test_params = [('nslsii', nsls_ii_path), ('chess', chess_path)] +test_params = [('10x10', (10, 10))] @pytest.mark.parametrize(('name', 'fp'), test_params) @@ -15,15 +20,17 @@ def test_dets(db, tmp_dir, name, fp): det = det_factory(name, db.fs, fp, save_path=tmp_dir) RE = setup_test_run_engine() RE.subscribe('all', db.mds.insert) - scan = Count([det], ) - uid = RE(scan) db.fs.register_handler('RWFS_NPY', be.ReaderWithFSHandler) cycle2 = build_image_cycle(fp) cg = cycle2() - for n, d in db.restream(db[-1], fill=True): - if n == 'event': - assert_array_equal(d['data']['pe1_image'], next(cg)['pe1_image']) - assert uid is not None + for i in range(5): + scan = Count([det], ) + uid = RE(scan) + for n, d in db.restream(db[-1], fill=True): + if n == 'event': + img = next(cg)['pe1_image'] + assert_array_equal(d['data']['pe1_image'], img) + assert uid is not None @pytest.mark.parametrize(('name', 'fp'), test_params) @@ -41,13 +48,60 @@ def test_dets_shutter(db, tmp_dir, name, fp): for n, d in db.restream(db[-1], fill=True): if n == 'event': assert_array_equal(d['data']['pe1_image'], - np.zeros(next(cg)['pe1_image'].shape)) + np.zeros(d['data']['pe1_image'].shape)) assert uid is not None # With the shutter up RE(abs_set(shctl1, 1, wait=True)) uid = RE(scan) + next(cg) + for n, d in db.restream(db[-1], fill=True): + if n == 'event': + assert_array_equal(d['data']['pe1_image'], next(cg)['pe1_image']) + assert uid is not None + + +@pytest.mark.parametrize(('name', 'fp'), test_params) +def test_dets_XRayFilter(db, tmp_dir, name, fp): + det = det_factory(name, db.fs, fp, save_path=tmp_dir, + filter_bank=XRayFilterBankExample) + RE = setup_test_run_engine() + RE.subscribe('all', db.mds.insert) + scan = Count([det], ) + db.fs.register_handler('RWFS_NPY', be.ReaderWithFSHandler) + cycle2 = build_image_cycle(fp) + cg = cycle2() + # No filters + for f in XRayFilterBankExample.filter_list: + RE(abs_set(f, 0, wait=True)) + uid = RE(scan) for n, d in db.restream(db[-1], fill=True): if n == 'event': assert_array_equal(d['data']['pe1_image'], next(cg)['pe1_image']) assert uid is not None + + # Each filter + for f in XRayFilterBankExample.filter_list: + RE(abs_set(f, 0, wait=True)) + count = 0 + for f in XRayFilterBankExample.filter_list: + count += 1 + RE(abs_set(f, 1, wait=True)) + uid = RE(scan) + for n, d in db.restream(db[-1], fill=True): + if n == 'event': + assert_array_equal((d['data']['pe1_image']), + next(cg)['pe1_image'] * + f.get_XRayFilter_attenuation()) + assert uid is not None + RE(abs_set(f, 0, wait=True)) + + # All filters + for f in XRayFilterBankExample.filter_list: + RE(abs_set(f, 1, wait=True)) + uid = RE(scan) + for n, d in db.restream(db[-1], fill=True): + if n == 'event': + assert_array_equal(d['data']['pe1_image'], (next(cg)['pe1_image']) + * XRayFilterBankExample.get_attenuation()) + assert uid is not None diff --git a/xpdsim/tests/test_filter.py b/xpdsim/tests/test_filter.py new file mode 100644 index 0000000..2c5895a --- /dev/null +++ b/xpdsim/tests/test_filter.py @@ -0,0 +1,7 @@ +from ..filter import XRayFilter + + +def test_filter(): + f = XRayFilter('filter1', {'rad': lambda x: x}, {'x': 0}, 0.5) + + assert f.attenuation == .5