-
Notifications
You must be signed in to change notification settings - Fork 0
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
Tickets/DM-40213 #137
Tickets/DM-40213 #137
Conversation
63291fb
to
9b17217
Compare
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.
Here is an initial round of comments.
M2_B20 = 49 | ||
|
||
|
||
class ClosedLoop(salobj.BaseScript, metaclass=abc.ABCMeta): |
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.
Not sure this is an appropriate name for the script. I think usually we want to reflect an action performed by the control system. Maybe drop the "d" and call it CloseLoop
? However, I think the intention here is to create a base script to perform close loop with both ComCam and LSSTCam. If this is the case, I'd call this BaseCloseLoop
.
I see that you prepared the class to do it (by adding the metaclass
), however there's nothing in the implementation on that regard. I think you want to split this one in 2 scripts; a base and a ComCam implementation, leaving the LSSTCam implementation for later. Or maybe even doing the LSSTCam already after we merge the PR in ts_observatory_control.
FAM = 1 | ||
|
||
|
||
class DOFComponent(enum.IntEnum): |
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.
Do you want to move this enum to ts_observatory_control as we spoke?
from lsst.ts import salobj | ||
from lsst.ts.observatory.control import RemoteGroup | ||
|
||
# TO-DO: Remove this import once the new LSSTCam is available |
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 you are making a generic implementation here, then, you don't want to import these here now. See my comment about splitting this into 2 scripts.
descr=descr, | ||
) | ||
|
||
self.mtaos = RemoteGroup( |
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.
MTAOS is part of MTCS
|
||
# The following attributes are set via the configuration: | ||
self.filter = None | ||
self.grating = 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.
The Main Telescope instruments have no grating
description: De-focus to apply when acquiring the intra/extra focal images (mm). | ||
type: number | ||
default: 0.8 | ||
extra_focal_offset: |
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 the MT has need of this
the change in magnification from moving the secondary mirror. | ||
type: number | ||
default: 0.0011 | ||
threshold: |
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.
Maybe instead if having a parameter for threshold and one for selecting the DoF, you could merge these two and make the DoF be a "dictionary" where users can specify the threshold. something like this:
dof:
type: object
description: >-
Defines the DoF to use and their associated properties.
Set the threshold to zero to disable a dof.
properties:
M2_dz:
type: object
properties:
threshold:
type: number
M2_dx:
type: object
properties:
threshold:
type: number
M2_dy:
type: object
properties:
threshold:
type: number
M2_rx:
type: object
properties:
threshold:
type: number
M2_ry:
type: object
properties:
threshold:
type: number
.
.
.
Now, there's a question about how to handle defaults. Maybe we should talk about this in a video conf.
await self.checkpoint(f"[{i + 1}/{self.max_iter}]: Taking image...") | ||
|
||
# Setting visit_id's to none so run_cwfs will take a new dataset. | ||
if self.mode == ClosedLoopMode.CWFS: |
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.
Instead of having each option in a if/else statement like this, I'd suggest you extract each "mode" into a function, then make a dictionary with the mode as key and the function as value. Then here you just do:
await self.operation_model_handlers[self.mode]()
where you would define self.operation_model_handlers
in the `init method as something like:
self.operation_model_handlers = {
ClosedLoopMode.CWFS: self.handle_cwfs_mode,
ClosedLoopMode.FAM: self.handle_fam_mode,
}
Then self.handle_cwfs_mode
and self.handle_fam_mode
will have the appropriate code extracted from here.
self.mtaos.rem.mtaos.cmd_runWEP.set_start( | ||
visitId=intra_visit_id, extraId=extra_visit_id | ||
) | ||
|
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.
Please, extract the following code into methods that explain what each step does.
5ab267b
to
93cfb52
Compare
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.
Here some additional comments.
doc/version_history.rst
Outdated
@@ -6,7 +6,10 @@ | |||
Version History | |||
=============== | |||
|
|||
.. towncrier release notes start | |||
v1.26.0 |
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.
We are now using towncrier to manage the release notes. See: https://github.com/lsst-ts/ts_standardscripts/blob/develop/doc/news/README.rst
In short, you should not update this file anymore. Instead create a news fragment in the doc/news directory following the format suggested in the README above.
type: string | ||
enum: ["cwfs", "fam"] | ||
default: cwfs | ||
rotation_angle: |
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 think you can just remove the rotator from the configuration. The software that needs to know about the rotator angle will get it from the correct source inside the MTAOS.
f"[{i + 1}/{self.max_iter}]: Closed Loop converged." | ||
) | ||
|
||
self.log.info("Closed Loop completed successfully!\n") |
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.
remove new line at the end
|
||
# If we haven't converged and apply_corrections is true, | ||
# then we apply the corrections | ||
elif self.apply_corrections: |
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 think you always want to apply the correction, even if you already converged. This way we don't trow away any iteration. I'd move this check to be before the convergence criteria check and make them independent if
statements, e.g.;
if self.apply_corrections:
...
if all(abs(dof_offset) < self.threshold):
...
you don't need the else
step.
Also, if we are not applying the correction, do we want to continue taking data? we might want to break or return in that case, e.g.;
if self.apply_corrections:
...
else:
return
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 think we do want to keep taking data if we are not applying the correction. The reason being that in that way we can see how the closed loop corrections evolve without applying them. We might be repositioning the telescope or checking the LUT while doing this
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.
Ok, that's a good point. In this case, please, add a note in the max_iter
description explaining that, if apply_corrections
is False
it will take this many pairs of images.
self.log.debug("Camera already defined, skipping.") | ||
|
||
async def assert_mode_compatibility(self) -> None: | ||
"""Assert that the mode is compatible with ComCam.""" |
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.
Please, document that is raises an exception.
|
||
@property | ||
@abc.abstractmethod | ||
def camera(self): |
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.
Can you add type annotation to these method? I notice below that you did ComCam | None
and LSSTCam | None
. Type-checking wise this will make our life harder. We currently don't have type checking on in this repo but looking forward I would like to add support for it.
I'd recommend you only return the camera type and raise an exception if the camera is None
. Something like this:
@property
@abc.abstractmethod
def camera(self) -> BaseCamera:
raise NotImplementedError()
Then for ComCam and LSSTCam you would do something like this:
@property
def camera(self) -> BaseCamera:
if self._comcam is not None:
return self._comcam
else:
raise RuntimeError("Camera not defined.")
You can either use BaseCamera
or ComCam
as the return type in this case.
self._comcam = None | ||
|
||
@property | ||
def camera(self) -> ComCam | 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.
See my comment above about the type and how to handle this
self._lsstcam = None | ||
|
||
@property | ||
def camera(self) -> LSSTCam | 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.
See my comment above about the type
tests/test_base_close_loop.py
Outdated
import numpy as np | ||
from lsst.ts import standardscripts | ||
from lsst.ts.observatory.control.utils.enums import ClosedLoopMode | ||
from lsst.ts.standardscripts import BaseCloseLoop |
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.
BaseCloseLoop
is an abstract class, I don't think you can unit test it. You will have to test the concrete implementations.
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.
Cool, do I need to add two unit tests? lsstcam and comcam are basically the same, is it okay if I just have an lsstcam unit test?
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.
only one is fine. However, you may want to check the executable for both.
This script is used to perform measurements of the wavefront error, then | ||
propose dof offsets based on ts_ofc. The offsets are not applied | ||
automatically and must be turned on by the user through | ||
apply_corrections attribute. |
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.
Please, add a note here that if apply_corrections
is off, the script will take a series of intra/extra focal data instead, and the number of pairs is the number of maximum iterations.
doc/version_history.rst
Outdated
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.
Please, make sure you reset all changes to this file.
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.
Here some additional comments. I think once you address those we can probably close this one.
you may want to run
git checkout develop -- doc/version_history.rst
to make sure all your changes to this file are reset. Also, please, expand a bit the news fragment.
doc/news/DM-40213.feature.rst
Outdated
@@ -0,0 +1,3 @@ | |||
Add new base_close_loop.py script, and executable. |
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.
Please, explaining what this script does.
- type: array | ||
items: | ||
type: string | ||
enum: [ |
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 think you could dynamically generate this by doing something like:
enum: {[dof_name.name for dof_name in DOFName]}
and covert this to an f-string, by adding a "f" at the top, e.g.:
schema_yaml = f"""
...
|
||
assert all(self.state_0 == np.zeros(50)) | ||
|
||
async def test_executable(self): |
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.
Please, split this test in 2, e.g.;
async def test_executable_close_loop_lsstcam(self):
...
async def test_executable_close_loop_comcam(self):
...
bfe0495
to
04be8cb
Compare
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.
Looking good. Here some additional comments!
doc/news/DM-40213.feature.rst
Outdated
@@ -0,0 +1,4 @@ | |||
Add new base_close_loop.py script, and executable. This script allows to run the closed loop, |
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.
Please, make sure every phrase is in a single line, e.g. merge lines 1 and 2.
"""Handle creating MTCS object and waiting for remote to start.""" | ||
if self.mtcs is None: | ||
self.log.debug("Creating MTCS.") | ||
mtcs_usage = None if self.add_remotes else MTCSUsages.DryTest |
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 think you can remove the mtcs_usage
part here.
of pairs is the number of maximum iterations. | ||
""" | ||
|
||
def __init__(self, index=1, add_remotes=True, descr="") -> 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 think you can remove the add_remotes
parameter.
maxItems: 50 | ||
default: {[0.004]*50} | ||
max_iter: | ||
description: Maximum number of iterations. |
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 think multiline description should be like:
description: >-
Maximum number of iterations.
Note, if apply_corrections is False, the script
will take [max_iter] pairs of images.
See, for instance, how it is done in program
below.
"""Handle creating Camera object and waiting for remote to start.""" | ||
if self.camera is None: | ||
self.log.debug("Creating Camera.") | ||
comcam_usage = None if self.add_remotes else ComCamUsages.StateTransition |
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.
See my previous message about removing add_remotes
"""Handle creating Camera object and waiting for remote to start.""" | ||
if self.camera is None: | ||
self.log.debug("Creating Camera.") | ||
camera_usage = None if self.add_remotes else LSSTCamUsages.StateTransition |
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.
See my previous comment about removing add_remotes
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 think this module can be inside maintel
since it is maintel specific.
standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase | ||
): | ||
async def basic_make_script(self, index): | ||
self.script = CloseLoopLSSTCam(index=index, add_remotes=False) |
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.
Instead of using add_remotes
, set the value of the camera and TCS classes here with a DryTest
usage, e.g.;
self.script = CloseLoopLSSTCam(index=index)
self.script.mtcs = MTCS(
domain=self.script.domain,
intended_usage=MTCSUsages.DryTest,
log=self.script.log,
)
self.script._lsstcam = LSSTCam(
domain=self.script.domain,
intended_usage=LSSTCam.DryTest,
log=self.script.log,
)
002baff
to
f7f7209
Compare
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.
Alright! this is looking pretty good. I left one inline comment in the news fragment.
One general comment, please, make sure all your command executions have a timeout, e.g. check that all start
and set_start
calls pass a timeout=<something>
. Otherwise they may lock for ever if there is a communication issue. I think your STD_TIMEOUT
might be short for some commands. I suggest you create a CMD_TIMEOUT=60
and use in all commands.. however the runWEP
command might require something larger, like 120
.
doc/news/DM-40213.feature.rst
Outdated
@@ -0,0 +1,3 @@ | |||
Add new base_close_loop.py script, and executable. This script allows to run the closed loop, that is, taking images, processing them, and apply ts_ofc corrections. |
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.
Make sure each phrase takes one line.
e.g.;
Add new base_close_loop.py script, and executable.
This script allows to run the closed loop, that is, taking images, processing them, and apply ts_ofc corrections.
I think you may also want to add an empty line between each "feature", e.g.;
Add new base_close_loop.py script, and executable.
This script allows to run the closed loop, that is, taking images, processing them, and apply ts_ofc corrections.
Add new maintel/close_loop_comcam.py script, unit test, and executable.
Add new maintel/close_loop_lsstcam.py script, unit test, and executable.
89eb532
to
0a4386d
Compare
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.
Looks good! Thanks!
No description provided.