-
Notifications
You must be signed in to change notification settings - Fork 62
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
Improve Qmini spectrometer GUI #1314
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like what you've done here in the python file and I mostly have nitpicks and questions
I'll peek at the ui files next
pcdsdevices/lasers/qmini.py
Outdated
@@ -83,6 +87,47 @@ class QminiSpectrometer(Device): | |||
fit_stdev = Cpt(EpicsSignalRO, ':STDEV', kind='config') | |||
fit_chisq = Cpt(EpicsSignalRO, ':CHISQ', kind='config') | |||
|
|||
# Save spectra functions | |||
def save_data(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick, can ignore:
Is there any utility in making this more useful in non-gui contexts?
def save_data(self, filename: str = ""):
if not filename:
filename = self.file_dest.get().strip()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What did you have in mind?
In a non-gui context you could just do the shell script method of:
# save-spectrum.sh or whatever we wanna call it
file_dest=#2
prefix=#1
caget -t $prefix:WAVELENGTHS $prefix:SPECTRUM >> file_dest.txt
With a call like save-spectrum SOME:BASE:PV:SPECTROMETER save_me_to_some_file.txt
However there is this weird bug with the caget -t
call were the first element of the array is actually the array size, so you always get 2018
as the first element in both x & y. 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm imagining me having a qmini object in hutch-python for some reason
Then, I want to save the data
I call
qmini.save_data()
But then since I don't have the GUI open it's not obvious that I'm supposed to put to a string signal.
So maybe it would be nice if I could also do:
qmini.save_data("some/path/of/mine")
pcdsdevices/lasers/qmini.py
Outdated
""" | ||
if len(self.file_dest.get().strip()) == 0: | ||
# set a default destination for the file if you're lazy or give it whitespace | ||
_file = os.getcwd() + '/' + self.name + time.strftime("_%Y-%m-%d_%H%M%S") + '.txt' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: I noticed that you prepend a lot of function-scoped variables with a leading underscore. I'd previously only seen this as a convention for semi-private class members and function names that are intended not to be exported via import as per https://peps.python.org/pep-0008/.
Are there other reasons to name variables like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm doing for (paranoid) privacy reasons on the calls. I'm probably being overly cautious, honestly, but it's a habit I've kept. I don't think there's going to be any conflicts (aside from the signal attribute name shenanigans that need it — as I found out the hard way)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand a bit on privacy reasons? My question is mostly to learn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, strictly speaking, _var
is functionally a stylistic convention for saying "this var
is private-ish, but I'm not gonna enforce it. Remember that you need to be janky to get here" vs. __var
getting the built-in string mangling.
Mostly useful for me in contexts where there are pretty commonly recycled var names (i.e. result
, data
, output
, etc.) and I want to be explicit about which one I am messing with. That way I can have a parent function return a result
dependent on an inner function call with output _result
without getting too weird. Idk maybe I inherited some weird jank convention from C
that I haven't unlearned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, I do the same but only for data attributes e.g. self._var
. I can see how it'd also be nice to indicate which variables are meant to persist through the function and which are e.g. loop-only. Thanks for humoring me.
pcdsdevices/lasers/qmini.py
Outdated
# set a default destination for the file if you're lazy or give it whitespace | ||
_file = os.getcwd() + '/' + self.name + time.strftime("_%Y-%m-%d_%H%M%S") + '.txt' | ||
else: | ||
_file = self.file_dest.get() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few lines above you strip the text contents from this signal. Maybe you should strip here too to help users avoid having leading/trailing spaces in their filenames.
pcdsdevices/lasers/qmini.py
Outdated
'exposure (us)': str(self.exposure.get()), | ||
'averages': str(self.exposures_to_average.get()), | ||
# Lets do some sneaky conversion to bool from int | ||
'settings': {f"{sig}": str(eval(f"{self}.{sig}.get() > 0")).lower() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: maybe simpler without an eval
so the intent is clearer and the next maintainer doesn't get confused.
I probably botched this because I also got confused. I left it as an int because ints are allowed in json.
'settings': {f"{sig}": str(eval(f"{self}.{sig}.get() > 0")).lower() | |
'settings': {f"{sig}": int(getattr(self, sig).get()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I neglect getattr
a little too often for my own good, that's much cleaner. int
are allowed but I preferred true
and false
outputs for the states to match their respective PVs as bo
records.
This is maybe just convenience for the consumer of the dump, since it saves them a comparison when checking settings in the object.
I might just be enforcing my own preferences, tbh, so it isn't strictly necessary. Maybe I'll ask the scientists what they prefer 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, I guess since the point is for the end-user to read there could a lot of ways to do it!
You can also put boolean literals into json and it looks like true
or false
without the quotes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I could've just cast to bool
anyway, right? Not sure why I compared to get there in the first place lol. Very good point, I like that much better and will do that
pcdsdevices/lasers/qmini.py
Outdated
'normalize_exposure', 'adjust_offset', 'subtract_dark', | ||
'remove_bad_pixels', 'remove_temp_bad_pixels'] | ||
_data = {'timestamp': time.strftime("%Y-%m-%d %H:%M:%S"), | ||
'exposure (us)': str(self.exposure.get()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand why these need to be cast to strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json.dumps
was being weird for a second and I think I cast everything to a str
when sorting out the problem and left that in there. These can probably live as the int
s that they are
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, but the wavelength & spectra must be cast to str since Float32
is not json
compat, but that's on the consumer to handle turning back into a number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI files look good!
Note that there's one small discrepancy: the embedded has a button with the text "Start Single Exposure" and the detailed has the same button with the text "Force Single Exposure". I don't have any preference but maybe the text should be the same if they do the same thing!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooooh very good catch! I meant for these to both be "Force" to be consistent with the typhos default detailed screen that the folks are used to.
I think this is mergeable with consideration or rejection of my comments + a pre-release docs page |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should bring this in!
I'm not sure if you're online Adam, so I'm going to merge this myself to include it in the imminent tag! |
Description
Gave a face-lift to the Qmini embedded & (new) detailed screen(s) to better suit the needs of SRD in MODS.
Motivation and Context
Closes #1313
Adds a shiny new save-spectrum feature so that scientists aren't tempted to disconnect the spectrometers and plug it into their laptops ;]
How Has This Been Tested?
I've tested this in the pcds-5.9.1 environment and pretty extensively:
https://github.com/user-attachments/assets/fb2ba094-2165-40a2-9e9b-bd6075e3bb4e
Where Has This Been Documented?
In this PR and in the code!
Pre-merge checklist