-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clean-up Experiment dependencies inside Stimulus #72
base: master
Are you sure you want to change the base?
Conversation
new init_external get's also a calibrator object
stytra/stimulation/__init__.py
Outdated
stimulus.initialise_external(self.experiment) | ||
try: | ||
stimulus.initialise_external(self.experiment, self.experiment.calibrator) | ||
except TypeError: | ||
stimulus.initialise_external(self.experiment) | ||
warnings.warn("Warning: 'initialise_external' will require a calibrator input from the new update!", FutureWarning) | ||
warnings.warn("Warning: 'initialise_external' will require a calibrator input from the new update!", DeprecationWarning) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FIX 1
|
||
if calibrator == -999: | ||
self._calibrator = self._experiment.calibrator | ||
warnings.warn("Warning: 'initialise_external' will require a calibrator input from the new update!", FutureWarning) | ||
warnings.warn("Warning: 'initialise_external' will require a calibrator input from the new update!", DeprecationWarning) | ||
else: | ||
self._calibrator = calibrator | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good start on the decoupling! I am thinking now with about an alternative implementation would be to pass the ProtocolRunner object that would have the _calibrator
(and _estimator_log
) parameters (maybe not even directly but under as subfield named something like environment_state
that would be a dataclass with calibrator, estimator log and perhaps other things passed from the main process (window dimensions, trigger signal). Then you don't need to stuff many _fields in _stimulus, just a reference to the environment_state
that the protocol runner would construct.
and changed calibrator init for stimulus
printout error for feedback; moved initialization of environmentstate var
added class init
Update:
The last two are still unused. The way it works is the same as before: in the rest of the stimulus files the calls to calibrator and estimator go through the |
@@ -91,7 +93,9 @@ def __init__(self, experiment=None, target_dt=0, log_print=True): | |||
self.current_stimulus = None # current stimulus object | |||
self.past_stimuli_elapsed = None # time elapsed in previous stimuli | |||
self.dynamic_log = None # dynamic log for stimuli | |||
|
|||
|
|||
self.environment_state = EnvironmentState(calibrator = self.experiment.calibrator,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialize the env state variable with only the calibrator
if hasattr(self.experiment, 'estimator'): | ||
self.environment_state.estimator = self.experiment.estimator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the experiment class has initialized the estimator pass it to the env_state variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more "pythonic" way is try/catch an AttributeError
.
In general but this whole logic should be isolated from the ProtocolRunner and handled by either by Experiemnt.get_environment_state()
. Also think about how after separating the stimulation in another process, there will be an initialize
and update
(from queue) methods for the EnvironmentState
stytra/stimulation/__init__.py
Outdated
try: | ||
stimulus.initialise_external(self.experiment, self.environment_state,) | ||
except TypeError as e: | ||
print("Error: {}".format(e)) | ||
stimulus.initialise_external(self.experiment) | ||
warnings.warn("Warning: 'initialise_external' will use the environment_state variable which holds the calibrator and the estimator object!", FutureWarning) | ||
warnings.warn("Warning: 'initialise_external' will use the environment_state variable which holds the calibrator and the estimator object!", DeprecationWarning) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tries to call the updated function, if it can't it will only pass the experiment object -> further handling is done inside the function
@dataclass | ||
class EnvironmentState: | ||
def __init__(self, calibrator = None, estimator = None, height:int = 600, width:int = 800): | ||
""" | ||
Holds Environment variables to pass from the protocol runner to the stimulus | ||
""" | ||
self.calibrator = calibrator | ||
self.estimator = estimator | ||
self.height = height | ||
self.width = width |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New dataclass to hold all the variables and objects to pass down to the stimulus
if isinstance(environment_state, EnvironmentState): | ||
self._environment_state = environment_state | ||
else: | ||
self._environment_state = experiment | ||
warnings.warn("Warning: 'initialise_external' will use the environment_state variable which holds the calibrator object!", FutureWarning) | ||
warnings.warn("Warning: 'initialise_external' will use the environment_state variable which holds the calibrator object!", DeprecationWarning) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's an instance of the EnvironmentState class then initialize the variable correctly, otherwise use the new variable as a place holder for the experiment object
(only in place to allow backwards compatibility)
asset_dir, arduino board and logger
access env state var
if hasattr(self.experiment, 'estimator'): | ||
self.environment_state.estimator = self.experiment.estimator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more "pythonic" way is try/catch an AttributeError
.
In general but this whole logic should be isolated from the ProtocolRunner and handled by either by Experiemnt.get_environment_state()
. Also think about how after separating the stimulation in another process, there will be an initialize
and update
(from queue) methods for the EnvironmentState
@@ -111,7 +135,7 @@ def stop(self): | |||
""" | |||
pass | |||
|
|||
def initialise_external(self, experiment): | |||
def initialise_external(self, experiment, environment_state: EnvironmentState = None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think keeping both arguments is a good idea, at some point we anyway need to pass only the environment_state. We can consider having a dev
branch to merge such PRs into, and then bump the version to 0.9 after mergining this. Though I don't think custom code relies much on modifying intialise_external
(you can check for usage in stytra_config
With this PR the protocol runner passes the calibrator object from the method
initialise_external()
.To avoid problems with existing custom stimuli which may have rewritten the method I added two warnings.
More specifically I expect two cases to throw errors:
initialise_external()
function doesn't accept thecalibrator
input.initialise_external()
is used without passing thecalibrator
input.To fix 1:
in the protocol runner there's a
try{} except{}
block that tries to feed the calibrator object to the stimulus initialise_external() function. If this doesn't work then the protocol runner raises a warning and then calls the function without thecalibrator
input.To fix 2:
the
initialise_external()
has an optional argumentcalibrator
which has default value -999, if the default value is detected then theself._calibrator
is initialized with theself._experiment
and a warning is given to the user.else the
self._calibrator
is initialized with thecalibrator
inputFinally:
In all the others default stimuli the
_calibrator
object is used instead of_experiment.calibrator