-
Notifications
You must be signed in to change notification settings - Fork 9
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
Add support for distributed Cameras #80
Add support for distributed Cameras #80
Conversation
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.
Whew! That was a long review which means that was even longer for you!
Thanks for getting this moved over. Submitting just as comment for now but can approve if you would rather merge before vacation.
Seems like there is a bit we could do to eliminate code duplication between here and POCS, but I'm happy to do that after the fact.
conf_files/pyro_camera.yaml
Outdated
# Contains a default configuration with a simulated camera and focuser for testing | ||
# and example configurations for other camera and focusers (commented out). | ||
# | ||
# Do not edit this file, instead copy one of the examples to pyro_camera_local.yaml |
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 almost want this at the top and in all caps. And it will probably still happen. :)
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.
A compromise, I've made it all caps but didn't move it :-)
huntsman/camera/__init__.py
Outdated
from pocs.utils import logger as logger_module | ||
|
||
|
||
def list_connected_cameras(): |
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.
This isn't different, right? I realize you were probably just copying straying over, but let's just remove from here as the other is still available to us via POCS.
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.
Yes, this entire file is just copied over verbatim from the branch at panoptes/POCS
. A fair bit of unnecessary code duplication that I hadn't made any attempt to reduce yet. Replacing this with an import now.
huntsman/camera/__init__.py
Outdated
host (str, optional): hostname or IP address of the name server host. If not given | ||
will attempt to locate the name server via UDP network broadcast. | ||
logger (logging.Logger, optional): logger to use for messages, if not given will | ||
ise the root logger. |
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.
s/ise/use/
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.
Yep.
huntsman/camera/__init__.py
Outdated
ise the root logger. | ||
|
||
Returns: | ||
dict: Dictionary of distributed camera name, URI pairs |
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 the word "detected" in here?
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.
Yes.
huntsman/camera/__init__.py
Outdated
|
||
cameras[cam_name] = cam | ||
|
||
# Distributed camera creation |
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.
As far as I can tell lines 220-228 are the only lines different from what would be in POCS. Would it work to move these lines into create_distributed_cameras
(maybe with rename to create_distributed_cameras_from_config
) and then call the the functions sequentially? Would have to deal with the primary camera preference in some fashion but you already assume here that the distributed should override the local.
Would just be nice to reduce all this to only what we need separately.
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.
Well, these few lines plus a small change about how the default primary camera gets chosen (I can't assume that there is a camera named Cam00
so I sort the camera names and choose the first one). But otherwise it is identical to the POCS version.
Yes, it was the awkwardness of keeping track of whether a primary camera has been selected yet that stopped me from making these two entirely separate functions in the first place. I'll have a think about how to work around that.
It doesn't really override a local primary camera with a distributed one, as there will be at most one primary camera specified in the config file so the distributed camera creation will only set the primary camera is the local camera creation hasn't.
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, reduced code duplication a fair bit by importing pocs.camera.create_cameras_from_config
to handle the local camera creation.
except errors.NamingError: | ||
if not host: | ||
# Not given an hostname or IP address. Will attempt to work it out. | ||
host = get_own_ip(verbose=True) |
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 we just run on the localhost if none is given? Or is auto-detection the assumed usage?
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.
Running on localhost is 'special' for Pyro servers (including the name server), it disable the broadcast responder so they can't be reached from other machines on the network. Good for testing with simulators, but not a very useful default otherwise.
I see the auto detection as a convenience, really. It's probably not 100% reliable for complicated networking configurations where the machine has multiple IP address. For operational systems with fixed IPs it would be better to set the name server host manually via config file.
huntsman/utils/pyro/__init__.py
Outdated
|
||
if port: | ||
port = int(port) | ||
else: |
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.
else
seems unnecessary.
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.
It does. The only effect here is to convert a value of port
that evaluates to False
(e.g. 0
) into None
. I'm not sure why I would want that to happen, though.
huntsman/utils/pyro/__init__.py
Outdated
camera servers should be started after the name server, but before POCS. | ||
|
||
Args: | ||
ignore_local (bool, optional): If True use the default $POCS/conf_files/pyro_camera.yaml |
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.
s/$POCS/$HUNTSMAN_POCS/
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.
Yes.
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 see this change.
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.
Fixed another instance of the same thing and missed this one!
print('Starting request loop... (Control-C/Command-C to exit)') | ||
try: | ||
daemon.requestLoop() | ||
finally: |
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 need to catch the KeyboardInterrupt
?
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 tried doing that, but it didn't work as I expected. The except
block didn't get executed. So I just put all the cleanup in finally
and that did work.
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:
try:
daemon...
except KeyboardInterrupt:
logger.info('Stopping name server')
finally:
# Cleanup
Not necessary though.
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.
That would only be so that we could add an additional except Exception
so we could capture when it fails for some other reason. But again, not necessary for this PR.
Thanks for the detailed and quick review! I don't want to push through the merge just yet, though. At the very least want to get tests passing, and ideally remove the overlap and inconsistencies between |
Looking at it a bit more I don't think I actually need anything from |
Codecov Report
@@ Coverage Diff @@
## develop #80 +/- ##
============================================
+ Coverage 43.09% 53.94% +10.85%
============================================
Files 29 34 +5
Lines 1332 2039 +707
Branches 152 220 +68
============================================
+ Hits 574 1100 +526
- Misses 743 889 +146
- Partials 15 50 +35
Continue to review full report at Codecov.
|
OK, reduced duplication of code from POCS in Haven't done anything for those three unchecked items in the the PR description yet, but I think it's OK for them to be separate Issues & PRs. |
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.
Great! Looks good. Thanks for a ton of work to get this done in the last few days. Have a good vacation!
|
||
# If no camera was specified as primary use the first | ||
if primary_camera is None: | ||
camera_names = sorted(cameras.keys()) |
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.
Minor point, but since it's an OrderedDict do you want to skip the sorted
and use the first one placed in the dict? I think it comes out the same anyway.
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 guess it depends on whether we want to default to the first camera added (which will be Cam00
if there are any local cameras) or to the first camera alphabetically...
blocking (bool, optional): If False (default) returns immediately after starting | ||
the exposure, if True will block until it completes. | ||
timeout (u.second, optional): Length of time beyond the length the exposure to wait | ||
for exposures to complete. If not given will wait indefinitely. |
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'm still on the fence about ever allowing indefinite waits but it's fine for now. Maybe we need an issue for it.
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.
At the least we should be enabling these timeouts in the testing and in the default config files to encourage their use. Will revisit that as part of the general issue of timeouts for all the cameras' threaded actions.
'make_plots': make_plots} | ||
autofocus_kwargs.update(kwargs) | ||
|
||
focus_dir = os.path.join(os.path.abspath(self.config['directories']['images']), 'focus/') |
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 assume the trailing /
is for rsync
?
""" | ||
Clean up empty directories left behind by rsysc. | ||
""" | ||
user_at_host, path = source.split(':') |
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.
There's some weirdness with the path going on. Maybe an example of what the source
input is here.
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.
Ah, yes, the directory cleanup follows the file transfer in the call chain, which just passes on the same source
argument that the file transfer uses, which is in user@host:/directory/subdirectory/filename
format ready for rysnc.
@@ -0,0 +1,347 @@ | |||
import pytest |
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 just name this test_pyro_camera.py
and then we can leave it for only Pyro items (if we ever add other camera types just to Huntsman)?
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.
Inclined to leave as is to emphasise that this is a lightly edited copy of pocs/tetsts/test_camera.py
, and is supposed to be kept in sync with that. In fact I'll add a comment about that.
simulator = hardware.get_all_names(without=['camera']) | ||
conf['simulator'] = simulator | ||
cameras = create_cameras_from_config(conf) | ||
obs = Observatory(cameras=cameras, |
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.
Note panoptes/POCS#679 and the comments.
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'll have to return to that one. Not sure I understand the underlying Iissue fully.
huntsman/utils/pyro/__init__.py
Outdated
camera servers should be started after the name server, but before POCS. | ||
|
||
Args: | ||
ignore_local (bool, optional): If True use the default $POCS/conf_files/pyro_camera.yaml |
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 see this change.
print('Starting request loop... (Control-C/Command-C to exit)') | ||
try: | ||
daemon.requestLoop() | ||
finally: |
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:
try:
daemon...
except KeyboardInterrupt:
logger.info('Stopping name server')
finally:
# Cleanup
Not necessary though.
print('Starting request loop... (Control-C/Command-C to exit)') | ||
try: | ||
daemon.requestLoop() | ||
finally: |
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.
That would only be so that we could add an additional except Exception
so we could capture when it fails for some other reason. But again, not necessary for this PR.
Script to run a Pyro camera server. This should be run on the camera control computer for a | ||
distributed camera. The configuration for the camera is read from the pyro_camera.yaml/ | ||
pyro_camera_local.yaml config file. The camera servers should be started after the name server, | ||
but before POCS. |
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.
Could we have this script check for a running name server and refuse to start if so? (Can be separate PR)
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 run_camera_server
function that the script calls does do exactly that. Might be neater to have the script do it instead, though.
Moved from panoptes/POCS#547
Addressing Issue panoptes/POCS#546 by adding scripts and wrappers to enable distributed control of camera arrays using the Pyro4 library.
take_observation
using remote cameras