Actor#
basecam
includes a default implementation of an SDSS-style actor
to provide an interface for the camera system. An actor is just a server of some kind (TCP/IP or other) that accepts commands directed to the camera system, performs the commanded action, and replies to the user. basecam
uses CLU to provide the actor functionality.
The default actor implementation uses a JSON actor
that receives commands that resemble Unix terminal line commands and replies with a JSON object. Creating an actor for a given camera system is easy
from basecam.actor import CameraActor
class Actor(CameraActor):
pass
host = 'localhost'
port = 8888
camera_system = CameraSystem()
actor = Actor(camera_system, host=host, port=port)
await actor.setup()
await actor.serve_forever()
camera_system
must be an instantiated camera system. At this point the actor will be running on port 8888 of localhost and a client can connect to it over telnet or open a socket and issue commands.
$ telnet 127.0.0.1 8888
status
{
"header": {
"command_id": 0,
"commander_id": "8a278303-19bc-4d3b-ba33-a0fc33fdb267",
"message_code": ">",
"sender": "flicamera"
},
"data": {}
}
{
"header": {
"command_id": 0,
"commander_id": "8a278303-19bc-4d3b-ba33-a0fc33fdb267",
"message_code": "i",
"sender": "flicamera"
},
"data": {
"status": {
"camera": "gfa0",
"model": "MicroLine ML4240",
"serial": "ML0112718",
"fwrev": 0,
"hwrev": 0,
"hbin": 1,
"vbin": 1,
"visible_area": [
0,
0,
2048,
2048
],
"image_area": [
0,
0,
2048,
2048
],
"temperature_ccd": -25.0,
"temperature_base": -10.0,
"exposure_time_left": 0,
"cooler_power": 60.0
}
}
}
{
"header": {
"command_id": 0,
"commander_id": "8a278303-19bc-4d3b-ba33-a0fc33fdb267",
"message_code": ":",
"sender": "flicamera"
},
"data": {}
}
Note that the replies include an empty message with code >
indicating that the command is running, and :
when the command is done. See the message codes for more details.
It’s possible to build a camera actor with the same functionality but a different CLU base actor, for example AMQPActor
or LegacyActor
. To create a camera actor class from a different base actor
from clu import AMQPActor
from basecam.actor import BaseCameraActor
class NewActor(BaseCameraActor, AQMPActor):
pass
The order of the imports is important, always subclass from BaseCameraActor
first, and then from the specific CLU base actor. Then initialise the new actor with the parameters necessary for the CLU base actor user.
Default commands#
The following commands are provided by default for any camera actor. They cover all the default functionality provided by basecam
. Some commands such as binning
are only available if the camera system includes the corresponding mixin. basecam
will automatically detect if that’s the case and add the command.
basecam#
basecam [OPTIONS] COMMAND [ARGS]...
area#
Controls the camera image area.
If called without an image area value, returns the current value. The image area must have the format (x0, x1, y0, y1) and be 1-indexed.
basecam area [OPTIONS] [CAMERAS]... [AREA]...
Options
- -r, --reset#
Restores the original image area.
- Default:
False
Arguments
- CAMERAS#
Optional argument(s)
- AREA#
Optional argument(s)
binning#
Controls the camera binning.
If called without a binning value, returns the current value.
basecam binning [OPTIONS] [CAMERAS]... [BINNING]...
Arguments
- CAMERAS#
Optional argument(s)
- BINNING#
Optional argument(s)
expose#
Exposes and writes an image to disk.
basecam expose [OPTIONS] [CAMERA_NAMES]... [EXPTIME]
Options
- --object#
Takes an object exposure.
- Default:
True
- --flat#
Takes a flat exposure.
- Default:
False
- --dark#
Takes a dark exposure.
- Default:
False
- --bias#
Takes a bias exposure.
- Default:
False
- -f, --filename <filename>#
Filename of the imaga to save.
- -n, --num <num>#
Sequence number for this exposure filename.
- -s, --stack <stack>#
Number of images to stack.
- Default:
1
- -c, --count <count>#
Number of exposures to take.
- --no-postprocess#
Skip the post-process step, if defined.
Arguments
- CAMERA_NAMES#
Optional argument(s)
- EXPTIME#
Optional argument
get-command-model#
Returns a dictionary representation of the command using unclick
.
basecam get-command-model [OPTIONS] [COMMAND_NAME]
Arguments
- COMMAND_NAME#
Optional argument
help#
Shows the help.
basecam help [OPTIONS] [PARSER_COMMAND]
Arguments
- PARSER_COMMAND#
Optional argument
list#
Lists cameras connected to the camera system.
basecam list [OPTIONS]
Options
- --available#
Lists available cameras.
ping#
Pings the actor.
basecam ping [OPTIONS]
reconnect#
Reconnects a camera.
basecam reconnect [OPTIONS] [CAMERAS]...
Options
- -t, --timeout <timeout>#
Seconds to wait until disconnect or reconnect command times out.
- Default:
5.0
Arguments
- CAMERAS#
Optional argument(s)
set-default#
Set default cameras.
basecam set-default [OPTIONS] [CAMERAS]...
Options
- -f, --force#
Forces a camera to be set as default even if it is not connected.
Arguments
- CAMERAS#
Optional argument(s)
shutter#
Controls the camera shutter.
If called without a shutter position flag, returns the current position of the shutter.
basecam shutter [OPTIONS] [CAMERAS]...
Options
- --open#
Open the shutter.
- --close#
Close the shutter.
Arguments
- CAMERAS#
Optional argument(s)
status#
Returns the status of a camera.
basecam status [OPTIONS] [CAMERAS]...
Arguments
- CAMERAS#
Optional argument(s)
temperature#
Controls the camera temperature.
If called without a temperature value, returns the current temperature.
basecam temperature [OPTIONS] [CAMERAS]... [TEMPERATURE]
Arguments
- CAMERAS#
Optional argument(s)
- TEMPERATURE#
Optional argument
Expose post-process hook#
In expose
sometimes one wants to perform a final post-process step after the image has been written to disk but before the command is finished. For example, we may want to analyse the image and report the mean value. The expose
command includes a post-process hook that allows to set a callback to call when the the exposure has been written
async def report_median(command, exposure):
mean = exposure.data.mean()
command.info(text=f"Image mean value is {mean:.2f}")
class MyActor(CameraActor):
def __init__(self, *args, **kwargs):
...
self.context_obj['post_process_callback'] = report_median
Here we use the context_obj
dictionary that CLU passes to all the commands and we set the post_process_callback
parameter with the coroutine we want to call. The hook in expose
will then invoke the callback with the command and the Exposure
object.
Adding new commands#
To add new commands to the actor command parser import the camera_parser
and define new commands
import asyncio
import click
from basecam.actor.commands import camera_parser
@camera_parser.command()
@click.option('--now', is_flag=True, help='Reboot without delay')
async def reboot(command, cameras, now):
if not now:
asyncio.sleep(1.0)
for camera in cameras:
await camera.reboot()
command.info(reboot={'camera': camera.name, 'text': 'Reboot started'})
command.finish()
The new actor command always receives a CLU Command
as the first argument and a list of connected cameras as the second argument. It’s possible to access the actor instance as command.actor
and the camera system as command.actor.camera_system
. For more details, refer to CLU’s parser documentation.
Schema#
basecam
defines a data model for the actor replies as a JSONSchema file. A summary of the schema is given below. When a command issues a reply, the contents are validated against the schema and an error will be generated if the validation fails. The message is not output in that case.
It’s possible to opt out of the schema validation by instantiating the CameraActor
(or any other subclass of BaseCameraActor
) with schema=None
.
When adding new commands, you will need to extend the schema and pass it to the camera actor. To do so, first download the default schema and extend it. For our reboot example we would need to add the following text
"reboot": {
"type": "object",
"properties": {
"camera": { "type": "string" },
"text": { "type" "string" },
"additionalProperties": false
}
Then do
actor = CameraActor(camera_system, schema='schema.json', host=..., port=...)
Alternatively it’s also possible to use get_schema
to get the basecam schema and then append to it
from basecam.actor import get_schema
schema = get_schema()
schema['properties']['reboot'] = {
"type": "object",
"properties": {
"camera": { "type": "string" },
"text": { "type" "string" },
"additionalProperties": false
}
actor = CameraActor(camera_system, schema=schema, ...)
An actor command can also manually opt out of validating a specific message by passing validate=False
command.info(reboot={'camera': camera.name, 'text': 'Reboot started'}, validate=False)
Default schema#
type |
object |
||||
properties |
|||||
|
An error message from the system or one of the cameras |
||||
oneOf |
type |
object |
|||
properties |
|||||
|
type |
string |
|||
|
type |
string |
|||
additionalProperties |
False |
||||
type |
string |
||||
|
Current default cameras |
||||
type |
array |
||||
items |
type |
string |
|||
|
Connected cameras |
||||
type |
array |
||||
items |
type |
string |
|||
|
Name or UID of a newly connected camera |
||||
type |
string |
||||
|
Name or UID of a newly disconnected camera |
||||
type |
string |
||||
|
Status parameters for the given camera |
||||
type |
object |
||||
properties |
|||||
|
type |
string |
|||
additionalProperties |
True |
||||
|
Status of current exposure |
||||
type |
object |
||||
properties |
|||||
|
type |
string |
|||
|
Current stage of the exposure |
||||
type |
string |
||||
enum |
idle, integrating, reading, done, aborted, failed, post_processing, post_process_failed |
||||
|
type |
string |
|||
|
Exposure time for a single exposure (0 if none, short, or unknown) |
||||
type |
number |
||||
|
Remaining time for the current exposure (0 if none, short, or unknown) |
||||
type |
number |
||||
|
Current exposure in the stack (0 if not exposing or value not updated) |
||||
type |
integer |
||||
|
Total number of exposures in the stack (0 if not exposing or value not updated) |
||||
type |
integer |
||||
|
Description of an error while exposing or post-processing |
||||
type |
string |
||||
additionalProperties |
False |
||||
|
type |
object |
|||
properties |
|||||
|
type |
string |
|||
|
Temperature of the CCD |
||||
type |
number |
||||
additionalProperties |
False |
||||
|
Last written file |
||||
type |
object |
||||
properties |
|||||
|
type |
string |
|||
|
type |
string |
|||
additionalProperties |
False |
||||
|
Binning status in pixels |
||||
type |
object |
||||
properties |
|||||
|
type |
string |
|||
|
type |
integer |
|||
|
type |
integer |
|||
additionalProperties |
False |
||||
|
Area read from the CCD chip, in pixels |
||||
oneOf |
type |
object |
|||
properties |
|||||
|
type |
string |
|||
|
type |
array |
|||
maxLength |
4 |
||||
minLength |
4 |
||||
items |
type |
integer |
|||
additionalProperties |
False |
||||
type |
array |
||||
maxLength |
5 |
||||
minLength |
5 |
||||
items |
|||||
type |
string |
||||
type |
integer |
||||
type |
integer |
||||
type |
integer |
||||
type |
integer |
||||
|
Status of the shutter |
||||
properties |
|||||
|
type |
string |
|||
|
type |
string |
|||
enum |
open, closed |
||||
additionalProperties |
False |
||||
additionalProperties |
False |