Skip to content
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

SmarAct UI overhaul #1320

Merged
merged 24 commits into from
Feb 19, 2025
Merged

Conversation

aberges-SLAC
Copy link
Contributor

Description

Adding new SmarAct screens to help with day-to-day ops:

  • Detailed screen for closed loop SmarActs (includes PicoScale enabled stages, excludes SmarPod).
  • Open-loop tip-tilt embedded screen with minimal footprint to improve user experience during alignment (have this open as a floating window with camViewer)

I made the bulk of the UI in designer and (mostly) manually labeled the signals 🤮. Noticed in #430 that the discussion for naming signals with human-readable text has come up a few times. I think Ken had a good point that I should probably put the dotted_name signals back in as a tooltip for hutch-python friendliness

Motivation and Context

While fixing #1275 we realized it was time to update the SmarAct screens to improve user readability.

How Has This Been Tested?

I've tested this on some live devices for linear & tip-tilt (open-loop) in the LRL as well as the picoscale devices in the FTL (with permission). Everything appears to work as intended with the live devices.

One small hang-up is that launching the SmarAct.detailed.py screen from typhos using CLI triggers some complaints:

ERROR - Unable to find signal for <PyDMChannel (sig://${name}_motor_egu)> in signal registry.Use typhos.plugins.register_signal()
ERROR - Unable to find signal for <PyDMChannel (sig://${name}_description)> in signal registry.Use typhos.plugins.register_signal()

Since the ui file isn't having the macros expanded before it opens. I have to wait until add_device processes for the macros to be expanded, so none of the other signals appear to trigger it.

Both the EGU and DESC fields read/write regardless of this complaint.

Now I could fix this by emptying out all the channel slots on all the elements in the ui file then just slot the signals in the .py but it would be super annoying and messy, I think. Also, the ui works by itself for any non-picoscale related system, so I'd hate to break it.

Where Has This Been Documented?

This PR

Screenshots (if appropriate):

Some vids showing off the GUI

smaract_embedded_tip_tilt.mp4
smaract_picoscale_screen.mp4

Pre-merge checklist

  • Code works interactively
  • Code contains descriptive docstrings, including context and API
  • New/changed functions and methods are covered in the test suite where possible
  • Test suite passes locally
  • Test suite passes on GitHub Actions
  • Ran docs/pre-release-notes.sh and created a pre-release documentation page
  • Pre-release docs include context, functional descriptions, and contributors as appropriate

@aberges-SLAC
Copy link
Contributor Author

I know this is a really large PR due to the size of the pyqt scripts/uis, so feel free to break it up (perhaps I should've submitted 2 different PRs, honestly)

def __init__(self, parent=None, **kwargs):
super().__init__(parent=parent)

self.ui = typing.cast(_SmarActDetailedUI, uic.loadUi(self.ui_template, self))
Copy link
Member

Choose a reason for hiding this comment

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

I'm a little surprised you'd need to invoke the uic loader manually here. Naively I'd expect PyDM to do this for you in the super call:
https://github.com/slaclab/pydm/blob/ba4125d479a2e6adb033e9ab1a05ab2bab2fdf84/pydm/display.py#L315-L316

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps this is why your macros aren't being expanded?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely why the macro isn't being expanded, but also I can't omit this line or else self.ui never populates. Not sure why the super() call doesn't proc it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay figured out why pydm.Display wasn't loading the UI correctly. Got that remedied, but now I can't quite grab my object names. Might need to fenagle a bit more of the code to fix that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This sadly isn't why the macro isn't being expanded. Got the super call to work with:

class SmarActDetailedWidget(Display, utils.TyphosBase):
    """
    Custom widget for managing the SmarAct detailed screen
    """
    ui: _SmarActDetailedUI
    ui_template = path.join(path.dirname(path.realpath(__file__)), 'SmarAct.detailed.ui')

    def __init__(self, parent=None, ui_filename=ui_template, **kwargs):
        super().__init__(parent=parent, ui_filename=ui_filename)

but still get greeted with the complaints when the expert screen opens:

[2025-01-30 16:17:39] - INFO - Loading Tools ...
[2025-01-30 16:17:39] - INFO - Adding devices ...
[2025-01-30 16:17:42] - INFO - Launching application ...
[2025-01-30 16:17:45] - INFO - Loading Tools ...
[2025-01-30 16:17:45] - INFO - Adding devices ...
[2025-01-30 16:17:45] - ERROR - Unable to find signal for <PyDMChannel (sig://${name}_motor_egu)> in signal registry.Use typhos.plugins.register_signal()
[2025-01-30 16:17:45] - ERROR - Unable to find signal for <PyDMChannel (sig://${name}_description)> in signal registry.Use typhos.plugins.register_signal()

:sadge:

Copy link
Contributor

@slactjohnson slactjohnson left a comment

Choose a reason for hiding this comment

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

This is pretty cool. Some of this Typhos stuff is a bit beyond me, but reading through this, things generally make sense. I'd be interested to understand if this is a good template to base other, similar screens off of. I imagine there's a lot of nice things that we could do with many of our devices if we put in a little time.

class _SmarActDetailedUI(QtWidgets.QWidget):
"""Annotations helper for SmarAct.detailed.ui. Do not instantiate."""
# Status bar
calibrated_bool: pydm.widgets.byte.PyDMByteIndicator
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally we don't add the type to the signal name; is there a reason you're doing that here?

Copy link
Member

Choose a reason for hiding this comment

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

If you make a stub class with annotations and apply it like this, you get really good autocomplete/api lookup help in e.g. vscode because it knows that self.ui.calibrated_bool is a PyDMByteIndicator


def add_device(self, device):
"""Typhos hook for adding a new device."""
super().add_device(device)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this occur after the check for None?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that will always kill the screen, because I have to wait for typhos to grab add_device and link the ophyd.device back to the display. Let me try it out at the very least!

Copy link
Member

Choose a reason for hiding this comment

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

I checked a bit and I think the ordering doesn't matter (based on the code for TyphosBase)


def add_device(self, device):
"""Typhos hook for adding a new device."""
super().add_device(device)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as above.

@ZLLentz
Copy link
Member

ZLLentz commented Jan 30, 2025

To my quick-ish readthrough of the python file everything looks kosher, I can go again later with a more critical eye if you'd like to make sure there aren't any potential smaller issues. The results look good! I can try to nitpick ui layout stuff too (resize behavior, whitespace) if that's desirable.

@aberges-SLAC
Copy link
Contributor Author

I'd be interested to understand if this is a good template to base other, similar screens off of. I imagine there's a lot of nice things that we could do with many of our devices if we put in a little time.

I think some of the maybe_add_signal variation and metadata shenanigans I did are scalable, if a bit meticulous. I really ought to play with typhos.variety module and see if I can generalize this approach and make it blend. That way we can do a lot of this magic through our pcdsdevices classes and leverage typhos more.

As long as the method hooks the typhos magic and you get the self.device access, you can do most of the pyqt stuff we'd be doing in custom IOC screens. PyDM has honestly improved a lot so I imagine it fits most of our wants now.

@aberges-SLAC aberges-SLAC self-assigned this Feb 4, 2025
Copy link
Contributor

@christina-pino christina-pino left a comment

Choose a reason for hiding this comment

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

So far looks good! Albeit that it has been a while since I have had to work with typhos/pydm and never got this far. Two questions/comments.

  1. when released, will the *.ui and *.py "screens" join to be just *.ui like the rest?
Screenshot 2025-02-03 at 7 01 39 PM
  1. what command did you use to open the detailed screen with the pico scale check? I tried running

typhos "pcdsdevices.epics_motor.SmarAct[{'prefix':'LAS:FTL:MCS:01:m1', 'name':'LAS:FTL:MCS:01:m1'}]"
but it never shows the pico scale tab and has the EGU/description error

@aberges-SLAC
Copy link
Contributor Author

  1. when released, will the *.ui and *.py "screens" join to be just *.ui like the rest?

Both the .ui and .py will show up as optional screens due to how typhos backend works, but will prioritize .py scripts over .ui. Due to the dynamic nature of picoscale signals, it necessitates having the .py file to handle the optional PVs and macro expansion

  1. what command did you use to open the detailed screen with the pico scale check? I tried running
    typhos "pcdsdevices.epics_motor.SmarAct[{'prefix':'LAS:FTL:MCS:01:m1', 'name':'LAS:FTL:MCS:01:m1'}]"
    but it never shows the pico scale tab and has the EGU/description error

That's expected! A pcdsdevices.epics_mot.SmarAct device has no such pico_exists device signal, so the .py script will never populate the tab. You must use pcdsdevices.epics_motor.SmarActPicoscale to get that to work.

The egu and description signal error seems to be tied to how the .py script populates the UI display without expanding the macros. I can't get it to change that behavior, sadly, since pydm.Display doesn't appear to be expanding macros in UI file since it is opened before typhos adds the device, so it can't extend the macro. 🤷

@aberges-SLAC aberges-SLAC mentioned this pull request Feb 6, 2025
8 tasks
@aberges-SLAC
Copy link
Contributor Author

I might consider refactoring these screens given the ease of pcdshub/typhos#627.

I think I'd still need to do something janky to get the ordering the way I want for the tabs, but it does cut down on a lot of hard coding since I can just add things like:

def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Long name shenanigans
        # Motor Record long name overrides
        self.velocity.long_name = 'Velocity'
        self.velocity_base.long_name = 'Velocity Min'
        self.velocity_max.long_name = 'Velocity Max'
        self.acceleration.long_name = 'Acceleration'
        self.motor_stop.long_name = 'Stop Motor'
        self.motor_is_moving.long_name = 'Actively Moving'
        self.dial_position.long_name = 'Dial Position'
        self.direction_of_travel.long_name = 'Direction of Travel'
        self.home_forward.long_name = 'Home Forward'
        self.home_reverse.long_name = 'Home Backward'

To the pcdsdevices.epics_motor.SmarAct class and get a lot of pretty formatting for free from typhos.

@ZLLentz
Copy link
Member

ZLLentz commented Feb 6, 2025

Both the .ui and .py will show up as optional screens due to how typhos backend works

I wonder if this is worth a typhos patch itself, or if there are cases where one might want to avoid the py file.

@aberges-SLAC
Copy link
Contributor Author

aberges-SLAC commented Feb 12, 2025

Okay, refactored my pyqt approach based on the changes in typhos for long_name. Also added tooltip support to be consistent with how TyphosSignalPanel would be handling things.

image

image

@ZLLentz
Copy link
Member

ZLLentz commented Feb 14, 2025

These screenshots look really great!
I'm going to review once more from a coding perspective

@tangkong
Copy link
Contributor

This does look amazing. I will mention that this seems to have a merge conflict, hopefully that's not too painful

ZLLentz
ZLLentz previously approved these changes Feb 14, 2025
Copy link
Member

@ZLLentz ZLLentz left a comment

Choose a reason for hiding this comment

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

Mostly nitpicks! Can ignore if you like!
I think this is pretty great.

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Long name shenanigans
self.step_voltage.long_name = 'Step Voltage'
Copy link
Member

Choose a reason for hiding this comment

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

side note: the long_name ophyd PR churned a little bit recently (rebase?) so these are pretty safe for the future.

Comment on lines 100 to 101
def __init__(self, parent=None, ui_filename=ui_template, **kwargs):
super().__init__(parent=parent, ui_filename=ui_filename)
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: this might be redundant now, unless you intend to block the kwargs pass-through

Suggested change
def __init__(self, parent=None, ui_filename=ui_template, **kwargs):
super().__init__(parent=parent, ui_filename=ui_filename)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It breaks if I change it 🤷

I think if I just manually call out the template anyway it's superfluous, but I deleted that and went the super().__init__ route. I remember it only being able to catch my template if I did it this way (still doesn't expand my macros) but let me tinker

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I don't really gain or lose anything by blocking the **kwargs or not, least as far as I can tell. Still need to super().__init__ though. Maintainer's choice on whether or not I should pass the **kwargs 🤷

"""
ui: _SmarActDetailedUI
# Seems confusing, but really just cleans up the call to pydm for setting self.ui
ui_template = path.join(path.dirname(path.realpath(__file__)), 'SmarAct.detailed.ui')
Copy link
Member

Choose a reason for hiding this comment

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

Question: In the past I was able to just give e.g. SmarAct.detailed.ui without any path handling and pydm knew to check the same dir. Does it behave differently here? (same for the embedded widget)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right! This is probably a relic of previous fenagling I was doing. I can update this across the .py files


def add_device(self, device):
"""Typhos hook for adding a new device."""
super().add_device(device)
Copy link
Member

Choose a reason for hiding this comment

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

I checked a bit and I think the ordering doesn't matter (based on the code for TyphosBase)

def fix_pvs(self):
"""
Fix all the channel access and signal linking to various pydm objects in the screen,
since the macros aren't expanded when the UI is initialized.
Copy link
Member

Choose a reason for hiding this comment

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

nit: it'd be nice to figure out and clean the root cause here but it's not strictly required

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah the lack of macro expansion is very annoying considering the tedium of adding it in designer in the first place, but c'est la vie. I can't figure out why pydm isn't expanding the ui before opening the .py screen.

A little hacky for me to just handle the egu and desc signals this way, but it got rid of the terminal spam on start-up complaining about how the sig//${name}_egu etc. didn't exist

self.maybe_add_pico()
self.add_tool_tips()
# Only start this timer if PicoScale exists
if hasattr(self.device, 'pico_exists'):
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: possibly this belongs in maybe_add_pico

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! Good point, I'll move this there, not sense in having it out here if the check is already done in maybe_add_pico

Copy link
Member

@ZLLentz ZLLentz left a comment

Choose a reason for hiding this comment

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

Still happy, still on-board

@aberges-SLAC aberges-SLAC merged commit 77112c3 into pcdshub:master Feb 19, 2025
11 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.

5 participants