Posting an image to Slack#
The user experience at the beamline benefits a lot by having text messages regularly posted to Slack. Adding images makes that experience much richer. Being able to see the result of a measurement on Slack makes it much easier to walk away from the beamline and have confidence that things are proceeding apace.
Obtain an uploader token#
The process to enabling image posts is rather similar to posting text. Here is a handy page that I followed when I was setting this up: https://hamzaafridi.com/sending-a-file-to-a-slack-channel-using-api/
The interface is similar in that you need to establish a App and associate with a specific channel on a specific workspace. But then you need to use the Slack python interface to post a file. There is likely a way to stream an image directly to Slack, but I have implemented it as posting a file. The images I post to Slack are things I also write to disk and include in the data product that the user takes home from the beamline. Since I am writing files in any case, it seemed sensible to implement this as a file posting.
Code for posting an image#
Setting up the App results in being given a token for uploading files. In lines 4 to 10 of the code below, I am reading this token from the beamline NAS. This token is also a thing that must not be committed to the beamline Github repo. Again, keeping it on a NAS or on Lustre, while not actually secure, is adequate.
Here is the code for posting an image stored in a file to a Slack channel.
1 ## Simple but useful guide to configuring a slack app:
2 ## https://hamzaafridi.com/sending-a-file-to-a-slack-channel-using-api/
3 def img_to_slack(imagefile):
4 token_file = os.path.join(startup_dir, 'BMM', 'image_uploader_token')
5 try:
6 with open(token_file, "r") as f:
7 token = f.read().replace('\n','')
8 except:
9 post_to_slack(f'failed to post image: {imagefile}')
10 return()
11 client = WebClient(token=token)
12 #client = WebClient(token=os.environ['SLACK_API_TOKEN'])
13 try:
14 response = client.files_upload(channels='#beamtime', file=imagefile)
15 assert response["file"] # the uploaded file
16 except SlackApiError as e:
17 post_to_slack('failed to post image: {imagefile}')
18 # You will get a SlackApiError if "ok" is False
19 assert e.response["ok"] is False
20 assert e.response["error"] # str like 'invalid_auth', 'channel_not_found'
21 print(f"Got an error: {e.response['error']}")
22 except Exception as em:
23 print("EXCEPTION: " + str(em))
24 print(f'failed to post image: {imagefile}')
25 post_to_slack(f'failed to post image: {imagefile}')
This can be used at the bsui
command line or in a Bluesky plan
like so:
from beamline_slack import img_to_slack
img_to_slack('/path/to/data/plot.png')
The file posted does not need to be a PNG image. You can post all kinds of files in this manner.
Here is what it looks like for BMM. At the end of a sequence of repetitions on the same sample, the data are merged and lightly reduced. Matplotlib is used to make a fun representation of the reduced data, which is saved to a PNG file. That PNG file is then posted to the Slack channel.