Skip to content

SPMMicroscope class and SPM_Scan sub-device - #131

Open
Slautin wants to merge 10 commits into
mainfrom
boris_instrument
Open

SPMMicroscope class and SPM_Scan sub-device#131
Slautin wants to merge 10 commits into
mainfrom
boris_instrument

Conversation

@Slautin

@Slautin Slautin commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

SPM: abstract microscope facade and scan device

  • scanning_probe_microscope.py — rewritten SPMMicroscope as a thin
    orchestrator. It connects to the sub-devices (scan, feedback, approach,
    stage, spectroscopy) via DeviceProxy and delegates single-subsystem
    commands to them.
    Only instrument-global state stays abstract (_get_spm_mode, _get_meter_values). All commands return JSON strings confirming the resulting state; missing or unreachable sub-devices raise a clear DevFailed via the _get_proxy helper.
    get_microscope_state reports NOT_CONFIGURED / UNREACHABLE instead of
    failing.

  • hardware/scan.py — new abstract SPM_SCAN device. Unlike the EM SCAN
    (settings holder only), it owns both the scan frame parameters and scan
    execution. Vendor-specific behaviour is isolated in four hooks:
    _hw_read_scan_params, _hw_write_scan_param, _hw_acquire_scan,
    _hw_stop_scan.

Attribute writes are pushed to hardware and all
parameters are read back, so clients always see hardware-accepted values.
acquire_scan saves via the existing save_acquisition (scan parameters
attached as dataset attributes) and returns the DATA/Tiled key;
stop_scan and refresh_params complete the command set.

Not included yet

Concrete Jupiter classes, remaining sub-devices (feedback, approach,
stage, spectroscopy), tests, and the SPM config yaml — follow-up PRs.

Removes the old spm_devices/spm_scan.py draft.

@gduscher

Copy link
Copy Markdown
Member

Hi Boris,
yes that looks good.
SPMs do not seem to have as many detectors as the STEM.
I would suggest to rename get_meter. Might be confused with get_centimeter:-)
Better would be to read_meter, or to get_meter-value.

Gerd

Comment on lines +136 to +155
def _get_proxy(self, name: str) -> tango.DeviceProxy:
"""Return the sub-device proxy or raise a clear DevFailed."""
proxy = self._device_proxies.get(name)
if proxy is None:
tango.Except.throw_exception(
'DeviceNotConfigured',
f"No '{name}' device is configured. "
f"Set {name}_device_address in the Tango DB / config yaml.",
f'{name}',
)
try:
proxy.ping() # type: ignore
except tango.DevFailed:
tango.Except.throw_exception(
'DeviceNotAccessible',
f"The '{name}' device at '{proxy.dev_name()}' is not responding. " # type: ignore
f"Check that its server is running.",
f'{name}',
)
return proxy

@utkarshp1161 utkarshp1161 Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just trying to understand, why would you need _get_proxy if you already have get_device_proxies .

@tango.server.command(dtype_out=str)
def acquire_spectrum(self) -> str:
"""Acquire a spectrum using SPECTROSCOPY device settings; returns a DATA/Tiled uid."""
return self._get_proxy('spectroscopy').acquire_spectrum()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see, you call the _get_proxy here based on naming in _connect_device_proxies

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I guess Jupiter_api will inherit the class SPMMicroscope

Comment on lines +91 to +94
# ------------------------------------------------------------------
# Abstract methods — vendor-specific
# ------------------------------------------------------------------

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder if this vendor specific methods should live here? Can we architect in a way that it is vendor neutral?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Or maybe there should be a spm_stage_jupiter_api which inherits from spm_stage and fills in the Vendor parts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hi, Utkarsh! It is not vendor specific methods - they are abstract "placeholders" that should be realized in the jupyter_api or bruker_api

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah right!

Comment on lines +85 to +87
# ------------------------------------------------------------------
# Abstract methods — vendor-specific
# ------------------------------------------------------------------

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment on lines +107 to +109
# ------------------------------------------------------------------
# Abstract methods — vendor-specific
# ------------------------------------------------------------------

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment on lines +209 to +211
# ------------------------------------------------------------------
# Abstract methods — vendor-specific
# ------------------------------------------------------------------

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@utkarshp1161

Copy link
Copy Markdown
Collaborator

Overall great PR. Looking forward to see jupiter_api.py.

Extra suggestions:

  • Having docs so folks can understand and say someone has a bruker afm can contribute with something like afm_bruker.py
  • Some minimal tests

"""Approach the tip to the surface; returns resulting approach state as JSON."""
proxy = self._get_proxy('approach')
proxy.approach()
return json.dumps({'approached': bool(proxy.approached)})

@utkarshp1161 utkarshp1161 Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would suggest to avoid json.dump where it can be avoided and directly use PyTango dtype. As PyTango commands internally handles serialization and desiralization.

This reference can be useful:
https://tango-controls.readthedocs.io/projects/pytango/en/latest/api/data_types.html

@Slautin
Slautin requested a review from utkarshp1161 August 10, 2026 15:56
@Slautin

Slautin commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

@utkarshp1161 thanks again for the review. Could you please take a look on the changes I made. Thank you very much!

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.

3 participants