diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 33314d3..ea94b50 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -6,7 +6,7 @@ API Reference :maxdepth: 2 lyse_functions - run + shot sequence dataframe_utilities analysis_subprocess diff --git a/docs/source/api/lyse_functions.rst b/docs/source/api/lyse_functions.rst index 8601831..c8d06b5 100644 --- a/docs/source/api/lyse_functions.rst +++ b/docs/source/api/lyse_functions.rst @@ -4,4 +4,4 @@ Lyse Helper Functions .. automodule:: lyse :members: :undoc-members: - :exclude-members: Run, Sequence + :exclude-members: Shot, Run, Sequence diff --git a/docs/source/api/run.rst b/docs/source/api/shot.rst similarity index 55% rename from docs/source/api/run.rst rename to docs/source/api/shot.rst index 2d2f5e8..5bfb1b3 100644 --- a/docs/source/api/run.rst +++ b/docs/source/api/shot.rst @@ -1,10 +1,10 @@ -Run +Shot ========== -.. autoclass:: lyse.Run +.. autoclass:: lyse.Shot :members: :undoc-members: :inherited-members: :show-inheritance: - .. automethod:: lyse.Run.__init__ \ No newline at end of file + .. automethod:: lyse.Shot.__init__ \ No newline at end of file diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 90d5770..924eed1 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -24,11 +24,11 @@ An analysis on a single shot # Image attributes are also stored in this series: w_x2 = ser['side','absorption','OD','Gaussian_XW'] - # If we want actual measurement data, we'll have to instantiate a Run object: - run = Run(path) + # If we want actual measurement data, we'll have to instantiate a Shot object: + shot = Shot(path) # Obtaining a trace: - t, mot_fluorecence = run.get_trace('mot fluorecence') + t, mot_fluorecence = shot.get_trace('mot fluorecence') # Now we might do some analysis on this data. Say we've written a # linear fit function (or we're calling some other libaries linear @@ -51,7 +51,7 @@ An analysis on a single shot # We might wish to save this result so that we can compare it across # shots in a multishot analysis: - run.save_result('mot loadrate', c) + shot.save_result('mot loadrate', c) An analysis on multiple shots diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index eac0dce..05b5524 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -3,7 +3,7 @@ Introduction **Lyse** is a data analysis system which gets *your code* running on experimental data as it is acquired. It is fundamenally based around the ideas of experimental *shots* and analysis *routines*. A shot is one trial of an experiment, and a routine is a ``Python`` script, written by you, that does something with the measurement data from one or more shots. -Analysis routines can be either *single-shot* or *multi-shot*. This determines what data and functions are available to your code when it runs. A single-shot routine has access to the data from only one shot, and functions available for saving results only to the hdf5 file for that shot. A a multi-shot routine has access to the entire dataset from all the runs that are currently loaded into **lyse**, and has functions available for saving results to an hdf5 file which does not belong to any of the shots---it's a file that exists only to save the "meta results". +Analysis routines can be either *single-shot* or *multi-shot*. This determines what data and functions are available to your code when it runs. A single-shot routine has access to the data from only one shot, and functions available for saving results only to the hdf5 file for that shot. A a multi-shot routine has access to the entire dataset from all the shots that are currently loaded into **lyse**, and has functions available for saving results to an hdf5 file which does not belong to any of the shots---it's a file that exists only to save the "meta results". Actually things are far less magical than that. The only enforced difference between a single shot routine and a multi-shot routine is a single variable provided to your code when **lyse** runs it. Your code runs in a perfectly clean ``Python`` environment with this one exception: a variable in the global namespace called ``path``, which is a path to an hdf5 file. If you have told **lyse** that your routine is a singleshot one, then this path will point to the hdf5 file for the current shot being analysed. On the other hand, if you've told **lyse** that your routine is a multishot one, then it will be the path to an h5 file that has been selected in **lyse** for saving results to. diff --git a/lyse/__init__.py b/lyse/__init__.py index 2a30b8c..97956b2 100644 --- a/lyse/__init__.py +++ b/lyse/__init__.py @@ -13,12 +13,14 @@ from lyse.dataframe_utilities import get_series_from_shot as _get_singleshot from labscript_utils.dict_diff import dict_diff +import copy import os import socket import pickle as pickle import inspect import sys import threading +import warnings import labscript_utils.h5_lock, h5py from labscript_utils.labconfig import LabConfig @@ -79,7 +81,7 @@ class _RoutineStorage(object): def data(filepath=None, host='localhost', port=_lyse_port, timeout=5, n_sequences=None, filter_kwargs=None): """Get data from the lyse dataframe or a file. - This function allows for either extracting information from a run's hdf5 + This function allows for either extracting information from a shots's hdf5 file, or retrieving data from lyse's dataframe. If `filepath` is provided then data will be read from that file and returned as a pandas series. If `filepath` is not provided then the dataframe in lyse, or a portion of it, @@ -97,11 +99,11 @@ def data(filepath=None, host='localhost', port=_lyse_port, timeout=5, n_sequence that method. Args: - filepath (str, optional): The path to a run's hdf5 file. If a value + filepath (str, optional): The path to a shot's hdf5 file. If a value other than `None` is provided, then this function will return a - pandas series containing data associated with the run. In particular + pandas series containing data associated with the shot. In particular it will contain the globals, singleshot results, multishot results, - etc. that would appear in the run's row in the Lyse dataframe, but + etc. that would appear in the shot's row in the Lyse dataframe, but the values will be read from the file rather than extracted from the lyse dataframe. If `filepath` is `None, then this function will instead return a section of the lyse dataframe. Note that if @@ -203,10 +205,10 @@ def _rangeindex_to_multiindex(df, inplace): df.sort_index(inplace=True) return df -def globals_diff(run1, run2, group=None): - return dict_diff(run1.get_globals(group), run2.get_globals(group)) +def globals_diff(shot1, shot2, group=None): + return dict_diff(shot1.get_globals(group), shot2.get_globals(group)) -class Run(object): +class Shot(object): """A class for saving/retrieving data to/from a shot's hdf5 file. This class implements methods that allow the user to retrieve data from a @@ -229,9 +231,9 @@ def __init__(self,h5_path,no_write=False): try: if not self.no_write: - # The group where this run's results will be stored in the h5 + # The group where this shot's results will be stored in the h5 # file will be the name of the python script which is - # instantiating this Run object. Iterate from innermost caller + # instantiating this Shot object. Iterate from innermost caller # to outermost. The name of the script will be one frame in # from analysis_subprocess.py. analysis_subprocess_path = os.path.join( @@ -256,7 +258,7 @@ def __init__(self,h5_path,no_write=False): self.set_group(group) except KeyError: # sys.stderr.write('Warning: to write results, call ' - # 'Run.set_group(groupname), specifying the name of the group ' + # 'Shot.set_group(groupname), specifying the name of the group ' # 'you would like to save results to. This normally comes from ' # 'the filename of your script, but since you\'re in interactive ' # 'mode, there is no script name.\n') @@ -284,7 +286,7 @@ def no_write(self): def group(self): """str: The group in the hdf5 file in which results are saved by default. - When a `Run` instance is created from within a lyse singleshot or + When a `Shot` instance is created from within a lyse singleshot or multishot routine, `group` will be set to the name of the running routine. If created from outside a lyse script it will be set to `None`. To change the default group for saving results, use the `set_group()` @@ -313,7 +315,7 @@ def _create_group_if_not_exists(self, h5_path, location, groupname): create_group = True if create_group: if self.no_write: - msg = "Cannot create group; this run is read-only." + msg = "Cannot create group; this shot is read-only." raise PermissionError(msg) with h5py.File(h5_path, 'r+') as h5_file: # Catch the ValueError raised if the group was created by @@ -566,10 +568,10 @@ def save_results(self, *args, **kwargs): for the optional arguments of `self.save_result()`. Examples: - >>> run = Run('path/to/an/hdf5/file.h5') # doctest: +SKIP + >>> shot = Shot('path/to/an/hdf5/file.h5') # doctest: +SKIP >>> a = 5 >>> b = 2.48 - >>> run.save_results('result', a, 'other_result', b, overwrite=False) # doctest: +SKIP + >>> shot.save_results('result', a, 'other_result', b, overwrite=False) # doctest: +SKIP """ names = args[::2] values = args[1::2] @@ -703,12 +705,25 @@ def globals_groups(self): except KeyError: return [] - def globals_diff(self, other_run, group=None): - return globals_diff(self, other_run, group) + def globals_diff(self, other_shot, group=None): + return globals_diff(self, other_shot, group) - -class Sequence(Run): - def __init__(self, h5_path, run_paths, no_write=False): + +class Run(Shot): + def __init__(self, *args, **kwargs): + msg = """The 'Run' class has been renamed to 'Shot' (but is otherwise + compatible). 'Run' will be removed in lyse v4.0. Please update your + analysis code to use the 'Shot' class. + """ + warnings.warn(dedent(msg), DeprecationWarning) + super().__init__(*args, **kwargs) + + def globals_diff(self, other_run, group=None): + return globals_diff(self, other_run, group) + + +class Sequence(Shot): + def __init__(self, h5_path, shot_paths, no_write=False): # Ensure file exists without affecting its last modification time if it # already exists. try: @@ -724,15 +739,36 @@ def __init__(self, h5_path, run_paths, no_write=False): super().__init__(h5_path, no_write=no_write) - if isinstance(run_paths, pandas.DataFrame): - run_paths = run_paths['filepath'] - self.runs = {path: Run(path,no_write=True) for path in run_paths} + if isinstance(shot_paths, pandas.DataFrame): + shot_paths = shot_paths['filepath'] + self.__shots = {path: Shot(path,no_write=True) for path in shot_paths} + + @property + def runs(self): + """This property is deprecated and will be removed in lyse v4.0 + + Use the :attr:`shots` attribute instead. + """ + msg = """The 'runs' attribute has been renamed to 'shots'. 'runs' will be + removed in lyse v4.0. Please update your analysis code to use the 'shots' + attribute. + """ + warnings.warn(dedent(msg), DeprecationWarning) + return copy.copy(self.__shots) + + @property + def shots(self): + """A dictionary containing :class:`Shot` instances. + + The dictionary is keyed by filepaths specified in the :code:`shot_paths` + argument at instantiation time.""" + return copy.copy(self.__shots) def get_trace(self,*args): - return {path:run.get_trace(*args) for path,run in self.runs.items()} + return {path:shot.get_trace(*args) for path,shot in self.shots.items()} def get_result_array(self,*args): - return {path:run.get_result_array(*args) for path,run in self.runs.items()} + return {path:shot.get_result_array(*args) for path,shot in self.shots.items()} def get_traces(self,*args): raise NotImplementedError('If you want to use this feature please ask me to implement it! -Chris')