Skip to content

add camera images#70

Merged
AustinHouston merged 4 commits intomainfrom
PyTango-dev-ACH
Apr 29, 2026
Merged

add camera images#70
AustinHouston merged 4 commits intomainfrom
PyTango-dev-ACH

Conversation

@AustinHouston
Copy link
Copy Markdown
Collaborator

camera images now, done with advanced acquisition settings

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the Tango device stack to support acquiring camera images using “advanced acquisition settings”, wiring a CAMERA settings device into registration and the microscope acquisition path, and updating an aberrations notebook to use the DB-registered devices.

Changes:

  • Register and link a new CAMERA device (and CORRECTOR) in the Tango DB registration script.
  • Add a readout_area setting to the CAMERA settings device and implement a get_camera_image microscope command.
  • Add an AutoScript advanced camera acquisition helper to ThermoMicroscope and update the aberrations notebook setup/connection flow.

Reviewed changes

Copilot reviewed 5 out of 10 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
scripts/2_register_devices.py Registers CAMERA/CORRECTOR devices and sets microscope device-address properties.
notebooks/2_Aberrations.ipynb Adds setup/launch logic for Tango DB + servers; updates device addressing and shows new runtime output.
asyncroscopy/detectors/CAMERA.py Adds readout_area acquisition preset as a read/write attribute with defaults.
asyncroscopy/ThermoMicroscope.py Connects a camera proxy and adds AutoScript “advanced” camera acquisition helper; adjusts stage move and shift behavior.
asyncroscopy/Microscope.py Adds camera_device_address property and implements get_camera_image command + updated metadata for get_scanned_image.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +227 to 235
"ename": "KeyError",
"evalue": "'result'",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mKeyError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m ab_msg = corrector_proxy.acquire_tableau(\u001b[33m'Enhanced 40'\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m ab = json.loads(ab_msg)[\u001b[33m'result'\u001b[39m][\u001b[33m'aberrations'\u001b[39m]\n\u001b[32m 3\u001b[39m \n\u001b[32m 4\u001b[39m ab[\u001b[33m'acceleration_voltage'\u001b[39m] = \u001b[32m60e3\u001b[39m \u001b[38;5;66;03m# eV\u001b[39;00m\n\u001b[32m 5\u001b[39m ab[\u001b[33m'FOV'\u001b[39m] = \u001b[32m100\u001b[39m /\u001b[32m10\u001b[39m \u001b[38;5;66;03m# nm\u001b[39;00m\n",
"\u001b[31mKeyError\u001b[39m: 'result'"
]
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notebook output shows KeyError: 'result' when doing json.loads(ab_msg)['result']['aberrations'], which suggests the acquire_tableau() response schema doesn't match what this cell expects. Please adjust the parsing (or add a schema check with a clearer error) so the cell can run successfully without raising.

Copilot uses AI. Check for mistakes.
this is the advanced version
"""

detector = 'BM-Ceta' # or "Flucam"
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_acquire_camera_image ignores the detector argument by overwriting it with a hard-coded value ('BM-Ceta'). This makes the method signature misleading and prevents selecting other camera detectors. Either remove the parameter and document the fixed detector, or use the passed-in detector value (ideally validating it against supported detectors).

Suggested change
detector = 'BM-Ceta' # or "Flucam"
detector = detector or 'BM-Ceta' # default to BM-Ceta if no detector is provided

Copilot uses AI. Check for mistakes.
Comment on lines +166 to 175

detector = 'BM-Ceta' # or "Flucam"

settings = CameraAcquisitionSettings(camera_detector=detector, size=imsize,
exposure_time=exposure_time, fixed_readout_area=readout_area, frame_combining=1)
adorned = self._microscope.acquisition.acquire_camera_image_advanced(settings)
image = adorned.data
return image


Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_acquire_camera_image uses self._microscope.acquisition... without checking self._microscope is not None (unlike _acquire_stem_image). In testing mode / when AutoScript is unavailable, this will raise an AttributeError instead of falling back or returning a controlled Tango error. Please add the same guard and either simulate a camera image or throw a Tango exception with a clear message.

Suggested change
detector = 'BM-Ceta' # or "Flucam"
settings = CameraAcquisitionSettings(camera_detector=detector, size=imsize,
exposure_time=exposure_time, fixed_readout_area=readout_area, frame_combining=1)
adorned = self._microscope.acquisition.acquire_camera_image_advanced(settings)
image = adorned.data
return image
if self._microscope is not None:
detector = 'BM-Ceta' # or "Flucam"
settings = CameraAcquisitionSettings(camera_detector=detector, size=imsize,
exposure_time=exposure_time, fixed_readout_area=readout_area, frame_combining=1)
adorned = self._microscope.acquisition.acquire_camera_image_advanced(settings)
image = adorned.data
return image
# Simulation fallback
self.warn_stream(
f"Simulating camera acquisition for detector={detector}, "
f"readout_area={readout_area}, size={imsize}"
)
rng = np.random.default_rng()
return rng.integers(0, 65535, size=(imsize, imsize), dtype=np.uint16)

Copilot uses AI. Check for mistakes.
"""Apply image shift in meters."""
x_shift = float(shift[0])
y_shift = float(shift[1])
print(shift)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print(shift) is left in _set_image_shift, which will spam stdout in a running Tango server. Please remove it or replace it with self.debug_stream(...)/info_stream(...) depending on desired verbosity.

Suggested change
print(shift)
self.debug_stream(f"Applying image shift: {shift}")

Copilot uses AI. Check for mistakes.
Comment on lines +232 to +244
image = self._acquire_stem_image(imsize, dwell_time, ['haadf'])

metadata = {
"detector": 'haadf',
"shape": [imsize, imsize],
"dtype": str(adorned_image.dtype),
"dtype": str(image.dtype),
"dwell_time": dwell_time,
"timestamp": time.time(),
"fov": self._get_fov(),
# TODO: add metadata from adorned_image.metadata when using real AutoScript
}

return json.dumps(metadata), adorned_image.tobytes()
return json.dumps(metadata), image.tobytes()
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_scanned_image now unconditionally adds "fov": self._get_fov(). In ThermoMicroscope testing mode / when AutoScript is unavailable, _microscope stays None and _get_fov() currently dereferences self._microscope, which will raise and break existing tests/CI. Please guard this (e.g., only include fov when connected, or have _get_fov() return a safe default in simulation/testing mode).

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +279
@command(dtype_out=DevEncoded)
def get_camera_image(self) -> tuple[str, bytes]:
"""
Acquire a single camera image from the named detector.

Parameters
----------
detector_name:
Name of the detector, e.g. "BM-Ceta". Must match a key in
self._detector_proxies.

Returns
-------
DevEncoded = (json_metadata, raw_bytes)
json_metadata includes: shape, dtype, dwell_time, detector,
timestamp, and any other relevant metadata.
raw_bytes is the flat numpy array bytes; reshape using shape from metadata.
"""

# check active detectors
camera = self._detector_proxies.get("camera")

# Read settings from the detector
exposure_time=camera.exposure_time
imsize=camera.imsize
readout_area=camera.readout_area

image = self._acquire_camera_image(imsize=imsize, exposure_time=exposure_time,
detector='BM-Ceta', readout_area=readout_area)

metadata = {
"detector": 'Ceta',
"shape": [imsize, imsize],
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_camera_image's docstring describes a detector_name parameter, but the command signature takes no inputs and hard-codes the detector ('BM-Ceta' / 'Ceta'). This is confusing for API consumers; either accept a detector name as dtype_in (and pass it through to acquisition + metadata) or update the docstring/metadata to reflect the fixed detector behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +272
camera = self._detector_proxies.get("camera")

# Read settings from the detector
exposure_time=camera.exposure_time
imsize=camera.imsize
readout_area=camera.readout_area
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_camera_image assumes the camera settings proxy exists and immediately reads camera.exposure_time/imsize/readout_area. If camera_device_address isn't configured or the proxy connection failed, this will raise an AttributeError instead of a controlled Tango error. Please add a None check (similar to get_spectrum) and throw a tango.Except.throw_exception(...) with available detectors / configuration hints.

Suggested change
camera = self._detector_proxies.get("camera")
# Read settings from the detector
exposure_time=camera.exposure_time
imsize=camera.imsize
readout_area=camera.readout_area
detector_proxies = getattr(self, "_detector_proxies", {}) or {}
camera = detector_proxies.get("camera")
if camera is None:
available_detectors = ", ".join(sorted(detector_proxies.keys())) or "none"
tango.Except.throw_exception(
"Detector unavailable",
"Camera detector proxy is not configured or could not be connected. "
f"Available detectors: {available_detectors}. "
"Please configure 'camera_device_address' and ensure the camera "
"device proxy can be created successfully.",
"Microscope.get_camera_image",
)
# Read settings from the detector
exposure_time = camera.exposure_time
imsize = camera.imsize
readout_area = camera.readout_area

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +290
@command(dtype_out=DevEncoded)
def get_camera_image(self) -> tuple[str, bytes]:
"""
Acquire a single camera image from the named detector.

Parameters
----------
detector_name:
Name of the detector, e.g. "BM-Ceta". Must match a key in
self._detector_proxies.

Returns
-------
DevEncoded = (json_metadata, raw_bytes)
json_metadata includes: shape, dtype, dwell_time, detector,
timestamp, and any other relevant metadata.
raw_bytes is the flat numpy array bytes; reshape using shape from metadata.
"""

# check active detectors
camera = self._detector_proxies.get("camera")

# Read settings from the detector
exposure_time=camera.exposure_time
imsize=camera.imsize
readout_area=camera.readout_area

image = self._acquire_camera_image(imsize=imsize, exposure_time=exposure_time,
detector='BM-Ceta', readout_area=readout_area)

metadata = {
"detector": 'Ceta',
"shape": [imsize, imsize],
"dtype": str(image.dtype),
"exposure_time": exposure_time,
"timestamp": time.time(),
"fov": self._get_fov(),
"readout_area": readout_area,
# TODO: move this metadata packing into the _acquire_camera_image method
# when usingreal AutoScript,to include metadata from adorned_image.metadata
}

return json.dumps(metadata), image.tobytes()

Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New camera acquisition behavior is introduced via get_camera_image, but there are existing tests for get_scanned_image in tests/test_thermo_microscope.py and no corresponding coverage for get_camera_image. Adding a test that provisions a CAMERA settings device in MultiDeviceTestContext and patches _acquire_camera_image (similar to patched_single_image) would help prevent regressions in metadata packing and settings propagation.

Copilot uses AI. Check for mistakes.
def _acquire_stem_image_advanced():
print("Get image with more flexible settings")
pass
def _acquire_camera_image():
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base Microscope class adds a _acquire_camera_image hook, but it is missing a self parameter and isn't marked @abstractmethod like the other acquisition helpers. This makes it easy for subclasses to accidentally not override it (or to mismatch signatures). Consider defining it as @abstractmethod def _acquire_camera_image(self, ...): ... with the expected parameters.

Suggested change
def _acquire_camera_image():
@abstractmethod
def _acquire_camera_image(self):

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +25
"import os\n",
"import tango\n",
"import json\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports cell now includes several duplicate imports (tango, json, numpy, matplotlib.pyplot). This makes the notebook harder to maintain and can mask version/namespace issues; please de-duplicate the imports in this cell.

Suggested change
"import os\n",
"import tango\n",
"import json\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
"import os"

Copilot uses AI. Check for mistakes.
@AustinHouston AustinHouston merged commit 9bb0c5d into main Apr 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants