Skip to content

Commit

Permalink
Improve focus based on temperature and relative adjustment
Browse files Browse the repository at this point in the history
  • Loading branch information
albireox committed Feb 6, 2024
1 parent 135bd2d commit 74670e5
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 10 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fast = true

[tool.ruff]
line-length = 88
target-version = 'py311'
target-version = 'py312'
select = ["E", "F", "I"]
unfixable = ["F841"]

Expand Down
14 changes: 13 additions & 1 deletion src/lvmguider/actor/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import asyncio

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, NamedTuple

from clu.actor import AMQPActor

Expand All @@ -26,6 +26,17 @@
__all__ = ["LVMGuiderActor"]


class ReferenceFocus(NamedTuple):
"""A named tuple to store the reference focus."""

focus: float
temperature: float
timestamp: float

def __str__(self):
return f"focus={self.focus:.2f}, temperature={self.temperature:.2f}"


class LVMGuiderActor(AMQPActor):
"""The ``lvmguider`` actor."""

Expand Down Expand Up @@ -58,6 +69,7 @@ def __init__(self, *args, **kwargs):
self.cameras = Cameras(self.telescope)

self._status = GuiderStatus.IDLE
self._reference_focus: ReferenceFocus | None = None

self.guider: Guider | None = None
self.guide_task: asyncio.Task | None = None
Expand Down
63 changes: 61 additions & 2 deletions src/lvmguider/actor/commands/focus.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

from __future__ import annotations

from time import time

from typing import TYPE_CHECKING

import click

from lvmguider.actor import lvmguider_parser
from lvmguider.actor.actor import ReferenceFocus
from lvmguider.focus import Focuser
from lvmguider.tools import wait_until_cameras_are_idle

Expand All @@ -36,13 +39,14 @@
"-g",
"--guess",
type=float,
help="Initial focus guess, in DT. If not provied, uses current focuser.",
help="Initial focus guess, in DT. If not provied, "
"uses a temperature-based estimate.",
)
@click.option(
"-s",
"--step-size",
type=float,
default=0.5,
default=0.2,
help="Step size, in DT.",
)
@click.option(
Expand Down Expand Up @@ -76,6 +80,9 @@ async def focus(

focuser = Focuser(command.actor.telescope)

if guess is None:
guess = await focuser.get_from_temperature(command)

try:
await focuser.focus(
command,
Expand All @@ -89,3 +96,55 @@ async def focus(
return command.fail(err)

return command.finish()


@lvmguider_parser.command("adjust-focus")
@click.argument("FOCUS_VALUE", type=float)
@click.option(
"--relative",
is_flag=True,
help="Adjusts the focus relative to the current value.",
)
@click.option(
"--reference",
is_flag=True,
help="Set as reference focus position.",
)
async def adjust_focus(
command: GuiderCommand,
focus_value: float,
relative: bool = False,
reference: bool = False,
):
"""Adjusts the focus to a specific value.
If FOCUS_VALUE is not provided, the focus will be adjusted to the
temperature-estimated best focus.
"""

focuser = Focuser(command.actor.telescope)

c_temp = await focuser.get_bench_temperature(command)
c_focus = await focuser.get_focus_position(command)

if focus_value is None:
if command.actor._reference_focus is None:
focus_value = await focuser.get_from_temperature(command, c_temp)
reference = True
if relative:
command.warning("No reference focus found. Using bench temperature.")
relative = False
else:
delta_t = c_temp - command.actor._reference_focus.temperature
focus_value = delta_t * command.actor.config["focus.model.a"]
relative = True # Always relative to the reference focus.

if relative:
focus_value = c_focus + focus_value

await focuser.goto_focus_position(command, focus_value)
if reference:
command.actor._reference_focus = ReferenceFocus(focus_value, c_temp, time())

return command.finish(f"Focus adjusted to {focus:.2f}.")
68 changes: 62 additions & 6 deletions src/lvmguider/focus.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import asyncio
import pathlib
from time import time

from typing import TYPE_CHECKING

Expand All @@ -22,6 +23,7 @@

from sdsstools.logger import get_logger

from lvmguider.actor.actor import ReferenceFocus
from lvmguider.tools import run_in_executor


Expand Down Expand Up @@ -72,7 +74,7 @@ async def focus(
self,
command: GuiderCommand,
initial_guess: float | None = None,
step_size: float = 0.5,
step_size: float = 0.2,
steps: int = 7,
exposure_time: float = 5.0,
fit_method="spline",
Expand All @@ -87,7 +89,8 @@ async def focus(
command
The actor command to use to talk to other actors.
initial_guess
An initial guess of the focus position, in DT.
An initial guess of the focus position, in DT. If not provided,
uses the current focuser position.
step_size
The resolution of the focus sweep in DT steps.
steps
Expand Down Expand Up @@ -121,10 +124,7 @@ async def focus(
steps += 1

if initial_guess is None:
cmd = await command.send_command(self.foc_actor, "getPosition")
if cmd.status.did_fail:
raise RuntimeError("Failed retrieving position from focuser.")
initial_guess = cmd.replies.get("Position")
initial_guess = await self.get_focus_position(command)
command.debug(f"Using focuser position: {initial_guess} DT")

assert initial_guess is not None
Expand Down Expand Up @@ -231,8 +231,64 @@ async def focus(

await self.goto_focus_position(command, numpy.round(fit_data["xmin"], 2))

current_temperature = await self.get_bench_temperature(command)
command.actor._reference_focus = ReferenceFocus(
focus=fit_data["xmin"],
temperature=current_temperature,
timestamp=time(),
)

return sources, fit_data

async def get_from_temperature(
self,
command: GuiderCommand,
temperature: float | None = None,
offset: float = 0.0,
) -> float:
"""Returns the estimated focus position for a given temperature.
Parameters
----------
command
The actor command to use to talk to other actors.
temperature
The temperature for which to calculate the focus position. If :obj:`None`,
the current bench internal temperature will be used.
offset
An additive offset to the resulting focus position.
"""

if temperature is None:
temperature = await self.get_bench_temperature(command)

assert temperature is not None

focus_model = command.actor.config["focus.model"]
new_focus = focus_model["a"] * temperature + focus_model["b"]

return new_focus + offset

async def get_bench_temperature(self, command: GuiderCommand) -> float:
"""Returns the current bench temperature."""

telem_actor = f"lvm.{self.telescope}.telem"
telem_command = await command.send_command(telem_actor, "status")
if telem_command.status.did_fail:
raise RuntimeError("Failed retrieving temperature from telemetry.")

return telem_command.replies.get("sensor1")["temperature"]

async def get_focus_position(self, command: GuiderCommand) -> float:
"""Returns the current focus position."""

cmd = await command.send_command(self.foc_actor, "getPosition")
if cmd.status.did_fail:
raise RuntimeError("Failed retrieving position from focuser.")

return cmd.replies.get("Position")

async def goto_focus_position(self, command: GuiderCommand, focus_position: float):
"""Moves the focuser to a position."""

Expand Down

0 comments on commit 74670e5

Please sign in to comment.