Full app files (slightly tweaked from @kenzo-aspuru-takata)
Browse files- .gitignore +2 -0
- app.py +59 -2
- documentation.py +89 -0
- download.py +21 -0
- gui_control.py +92 -0
- key_request.py +205 -0
- livestream.py +12 -0
- microscope_demo_client.py +182 -0
- requirements.txt +5 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
my_secrets.py
|
| 2 |
+
__pycache__/*
|
app.py
CHANGED
|
@@ -1,4 +1,61 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
|
| 3 |
+
|
| 4 |
+
def sidebar():
|
| 5 |
+
st.sidebar.title("Navigation")
|
| 6 |
+
selection = st.sidebar.radio(
|
| 7 |
+
"Go to",
|
| 8 |
+
[
|
| 9 |
+
"About",
|
| 10 |
+
"Request Key",
|
| 11 |
+
"Livestream",
|
| 12 |
+
"Download",
|
| 13 |
+
"GUI Control",
|
| 14 |
+
"Python Documentation",
|
| 15 |
+
],
|
| 16 |
+
)
|
| 17 |
+
return selection
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if "current_page" not in st.session_state:
|
| 21 |
+
st.session_state.current_page = "Home"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
selection = sidebar()
|
| 26 |
+
|
| 27 |
+
if st.session_state.current_page != selection:
|
| 28 |
+
st.session_state.current_page = selection
|
| 29 |
+
|
| 30 |
+
st.session_state.button_clicked = False
|
| 31 |
+
|
| 32 |
+
if selection == "About":
|
| 33 |
+
st.title("AC Microscope")
|
| 34 |
+
st.write(
|
| 35 |
+
"This is a request site for credentials to use remote access to Openflexure Microscopes in the AC lab. You can either control the microscopes over python or the GUI with the help of a temporary key. You can view the live camera feed on a livestream. One person can use a microscope at once. Currently only Microscope2 is functional, but they will all be functional in the future" # noqa: E501
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
elif selection == "Request Key":
|
| 39 |
+
import key_request
|
| 40 |
+
|
| 41 |
+
key_request.show()
|
| 42 |
+
elif selection == "Livestream":
|
| 43 |
+
import livestream
|
| 44 |
+
|
| 45 |
+
livestream.show()
|
| 46 |
+
elif selection == "Download":
|
| 47 |
+
import download
|
| 48 |
+
|
| 49 |
+
download.show()
|
| 50 |
+
elif selection == "GUI Control":
|
| 51 |
+
import gui_control
|
| 52 |
+
|
| 53 |
+
gui_control.show()
|
| 54 |
+
elif selection == "Python Documentation":
|
| 55 |
+
import documentation
|
| 56 |
+
|
| 57 |
+
documentation.show()
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
documentation.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def show():
|
| 2 |
+
import streamlit as st
|
| 3 |
+
|
| 4 |
+
st.title("AC Microscope Client Documentation")
|
| 5 |
+
|
| 6 |
+
st.subheader("Move")
|
| 7 |
+
st.write("Required parameters:")
|
| 8 |
+
st.code("move(x,y)")
|
| 9 |
+
st.write("Optional parameters:")
|
| 10 |
+
st.code("move(x,y,z,relative)")
|
| 11 |
+
st.write("Default values:")
|
| 12 |
+
st.code("z=False, relative=False")
|
| 13 |
+
st.write(
|
| 14 |
+
"The move() function moves to given coordinates x, y (and z if it is set to any integer value, if it is set to False which is the default setting, the z value wont change). If relative is True, then it will move relative to the current position instead of moving to the absolute coordinates. Coordinates arent based on screen percentage. The size of the screen is around 3600x2700." # noqa: E501
|
| 15 |
+
)
|
| 16 |
+
st.write("")
|
| 17 |
+
st.subheader("Take image")
|
| 18 |
+
st.code("take_image()")
|
| 19 |
+
st.write(
|
| 20 |
+
"The take_image() function takes an image on the microscope camera and returns an image object" # noqa: E501
|
| 21 |
+
)
|
| 22 |
+
st.write("")
|
| 23 |
+
st.subheader("Get position")
|
| 24 |
+
st.code("get_pos()")
|
| 25 |
+
st.write(
|
| 26 |
+
"The get_pos() returns a dictionary with x, y, and z coordinates eg. {'x':1,'y':2,'z':3}" # noqa: E501
|
| 27 |
+
)
|
| 28 |
+
st.write("")
|
| 29 |
+
st.subheader("Focus")
|
| 30 |
+
st.write("Required parameters:")
|
| 31 |
+
st.code("focus()")
|
| 32 |
+
st.write("Optional parameters:")
|
| 33 |
+
st.code("focus(amount)")
|
| 34 |
+
st.write("Default value:")
|
| 35 |
+
st.code('amount="fast"')
|
| 36 |
+
st.write(
|
| 37 |
+
'The focus() function auto focuses by different amounts: "huge", "fast", "medium", "fine", or any integer value' # noqa: E501
|
| 38 |
+
)
|
| 39 |
+
st.write("")
|
| 40 |
+
st.subheader("End connection")
|
| 41 |
+
st.code("end_connection()")
|
| 42 |
+
st.write(
|
| 43 |
+
"The end_connection() function ends the connection to the microscope at the end of your script. This is not required, but is reccomended to place at the end of your script." # noqa: E501
|
| 44 |
+
)
|
| 45 |
+
st.write("")
|
| 46 |
+
st.subheader("Scan")
|
| 47 |
+
st.write("Required parameters:")
|
| 48 |
+
st.code("scan(c1,c2)")
|
| 49 |
+
st.write("Optional parameters:")
|
| 50 |
+
st.code("scan(c1,c2,ov,foc)")
|
| 51 |
+
st.write("Default values:")
|
| 52 |
+
st.code("ov=1200,foc=0")
|
| 53 |
+
st.write(
|
| 54 |
+
'The scan() function returns a list of image objects. Takes images to scan an entire area specified by two corners. you can input the corner coordinates as "x1 y1", "x2 y2" or [x1, y1], [x2, y2]. ov refers to the overlap between the images and foc refers to how much the microscope should focus between images (0 to disable)' # noqa: E501
|
| 55 |
+
)
|
| 56 |
+
st.subheader("Scan and stitch")
|
| 57 |
+
st.write("Required parameters:")
|
| 58 |
+
st.code("scan_and_stitch(c1,c2,temp)")
|
| 59 |
+
st.write("Optional parameters:")
|
| 60 |
+
st.code("scan_and_stitch(c1,c2,temp,ov,foc,output)")
|
| 61 |
+
st.write("Default values:")
|
| 62 |
+
st.code('ov = 1200,foc = 0,output="Downloads/stitched.jpeg"')
|
| 63 |
+
st.write(
|
| 64 |
+
"The scan_and_stitch() function takes a scan with the same inputs + 2 more and outputs a stitched image. Output is the directory the stitched image will go to and temp is the temporary directory to stitch the image (make sure this is an empty directory (it will also be created automatically if it doesn't exist) the program will clear this directory) otherwise it works just like scan(). You need Openflexure Stitching installed and you need to define path_to_openflexure_stitching when instantiating your class for this to work" # noqa: E501
|
| 65 |
+
)
|
| 66 |
+
st.write("")
|
| 67 |
+
st.subheader("Instantiating the class")
|
| 68 |
+
st.code(
|
| 69 |
+
'Microscope = MicroscopeDemo(host, port, key, microscope, path_to_openflexure_stitching) #path_to_openflexure_stitching is optional, an example for the microscope field would be "microscope2"' # noqa: E501
|
| 70 |
+
)
|
| 71 |
+
st.write("")
|
| 72 |
+
st.subheader("Example code")
|
| 73 |
+
st.code(
|
| 74 |
+
"""
|
| 75 |
+
print(microscope.get_pos())
|
| 76 |
+
microscope.take_image().show()
|
| 77 |
+
microscope.scan([2000, 2000], "-2000 -2000")
|
| 78 |
+
for i in microscope.scan("2000 2000", "-2000 -2000", ov=1500, foc=1000):
|
| 79 |
+
i.show()
|
| 80 |
+
microscope.scan_and_stitch([2000, 2000], [-2000, -2000], temp="c:/Users/-/Downloads/scanimages", output="c:/Users/-/Downloads/scanstitch.jpg") # noqa: E501
|
| 81 |
+
microscope.move(0,0)
|
| 82 |
+
microscope.focus(700)
|
| 83 |
+
microscope.move(0,1000,relative=True)
|
| 84 |
+
microscope.focus("fast")
|
| 85 |
+
microscope.move(1000,0,z=3000)
|
| 86 |
+
microscope.focus()
|
| 87 |
+
microscope.end_connection()
|
| 88 |
+
"""
|
| 89 |
+
)
|
download.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def show():
|
| 2 |
+
import streamlit as st
|
| 3 |
+
|
| 4 |
+
st.title("Download")
|
| 5 |
+
st.write("You can install this program with")
|
| 6 |
+
st.code(
|
| 7 |
+
"pip install git+https://github.com... #PLACEHOLDER THIS METHOD OF INSTALLATION IS NOT READY YOU NEED TO DOWNLOAD IT MANUALLY FROM THE GITHUB AND ALSO PIP INSTALL ALL OF THE IMPORTS" # noqa: E501
|
| 8 |
+
)
|
| 9 |
+
st.write("")
|
| 10 |
+
st.write("or use the AC github")
|
| 11 |
+
st.link_button(
|
| 12 |
+
"AC github",
|
| 13 |
+
"https://github.com/AccelerationConsortium/ac-training-lab/tree/main/src%2Fac_training_lab%2Fopenflexure", # noqa: E501
|
| 14 |
+
)
|
| 15 |
+
st.write("")
|
| 16 |
+
st.write(
|
| 17 |
+
"You also need to install the Openflexure Stitching library if you want to stitch anything" # noqa: E501
|
| 18 |
+
)
|
| 19 |
+
st.link_button(
|
| 20 |
+
"Openflexure Stitching", "https://gitlab.com/openflexure/openflexure-stitching"
|
| 21 |
+
)
|
gui_control.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def show():
|
| 2 |
+
# note to self you can check for empty with if statement st.image(image,
|
| 3 |
+
# caption='PIL Image', use_column_width=True)
|
| 4 |
+
import streamlit as st
|
| 5 |
+
from microscope_demo_client import MicroscopeDemo
|
| 6 |
+
from my_secrets import HIVEMQ_BROKER
|
| 7 |
+
|
| 8 |
+
port = 8883
|
| 9 |
+
microscopes = [
|
| 10 |
+
"microscope",
|
| 11 |
+
"microscope2",
|
| 12 |
+
"deltastagetransmission",
|
| 13 |
+
"deltastagereflection",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
def get_pos_button():
|
| 17 |
+
microscope = MicroscopeDemo(
|
| 18 |
+
HIVEMQ_BROKER,
|
| 19 |
+
port,
|
| 20 |
+
microscopeselection + "clientuser",
|
| 21 |
+
access_key,
|
| 22 |
+
microscopeselection,
|
| 23 |
+
)
|
| 24 |
+
# "acmicroscopedemo" is a placeholder until access keys are implemented
|
| 25 |
+
pos = microscope.get_pos()
|
| 26 |
+
st.write("x: " + str(pos["x"]))
|
| 27 |
+
st.write("y: " + str(pos["y"]))
|
| 28 |
+
st.write("z: " + str(pos["z"]))
|
| 29 |
+
microscope.end_connection()
|
| 30 |
+
|
| 31 |
+
def take_image_button():
|
| 32 |
+
microscope = MicroscopeDemo(
|
| 33 |
+
HIVEMQ_BROKER,
|
| 34 |
+
port,
|
| 35 |
+
microscopeselection + "clientuser",
|
| 36 |
+
access_key,
|
| 37 |
+
microscopeselection,
|
| 38 |
+
)
|
| 39 |
+
# "acmicroscopedemo" is a placeholder until access keys are implemented
|
| 40 |
+
st.image(
|
| 41 |
+
microscope.take_image(),
|
| 42 |
+
caption="Taken from the microscope camera",
|
| 43 |
+
use_column_width=True,
|
| 44 |
+
)
|
| 45 |
+
microscope.end_connection()
|
| 46 |
+
|
| 47 |
+
def focus_button():
|
| 48 |
+
microscope = MicroscopeDemo(
|
| 49 |
+
HIVEMQ_BROKER,
|
| 50 |
+
port,
|
| 51 |
+
microscopeselection + "clientuser",
|
| 52 |
+
access_key,
|
| 53 |
+
microscopeselection,
|
| 54 |
+
)
|
| 55 |
+
# "acmicroscopedemo" is a placeholder until access keys are implemented
|
| 56 |
+
microscope.focus(focusamount)
|
| 57 |
+
st.write("Autofocus complete")
|
| 58 |
+
microscope.end_connection()
|
| 59 |
+
|
| 60 |
+
def move_button():
|
| 61 |
+
microscope = MicroscopeDemo(
|
| 62 |
+
HIVEMQ_BROKER,
|
| 63 |
+
port,
|
| 64 |
+
microscopeselection + "clientuser",
|
| 65 |
+
access_key,
|
| 66 |
+
microscopeselection,
|
| 67 |
+
)
|
| 68 |
+
# "acmicroscopedemo" is a placeholder until access keys are implemented
|
| 69 |
+
microscope.move(xmove, ymove)
|
| 70 |
+
st.write("Move complete")
|
| 71 |
+
microscope.end_connection()
|
| 72 |
+
|
| 73 |
+
st.title("GUI control")
|
| 74 |
+
|
| 75 |
+
microscopeselection = st.selectbox(
|
| 76 |
+
"Choose a microscope:", microscopes, index=microscopes.index("microscope2")
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
access_key = st.text_input(label="Enter your access key here:", max_chars=1000)
|
| 80 |
+
|
| 81 |
+
st.button("Get position", on_click=get_pos_button)
|
| 82 |
+
st.write("")
|
| 83 |
+
st.button("Take image", on_click=take_image_button)
|
| 84 |
+
st.write("")
|
| 85 |
+
focusamount = st.number_input(
|
| 86 |
+
"Autofocus amount 1-5000", min_value=1, max_value=5000, step=100, value=1000
|
| 87 |
+
)
|
| 88 |
+
st.button("Focus", on_click=focus_button)
|
| 89 |
+
st.write("")
|
| 90 |
+
xmove = st.number_input("X", min_value=-20000, max_value=20000, step=250, value=0)
|
| 91 |
+
ymove = st.number_input("Y", min_value=-20000, max_value=20000, step=250, value=0)
|
| 92 |
+
st.button("Move", on_click=move_button)
|
key_request.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def show():
|
| 2 |
+
import random
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
# import pymongo.mongo_client
|
| 6 |
+
import requests
|
| 7 |
+
import streamlit as st
|
| 8 |
+
from my_secrets import HIVEMQ_API_TOKEN, HIVEMQ_BASE_URL, HIVEMQ_BROKER, MONGODB_URI
|
| 9 |
+
from pymongo.mongo_client import MongoClient
|
| 10 |
+
|
| 11 |
+
# SET UP ON DATABASE you need to make a variable for the time called the
|
| 12 |
+
# same as the microscope prior to the running of the program running the
|
| 13 |
+
# function update_variable_test() will work
|
| 14 |
+
microscope = "microscope2"
|
| 15 |
+
access_time = 900
|
| 16 |
+
database_name = "openflexure-microscope"
|
| 17 |
+
collection_name = "Cluster0"
|
| 18 |
+
microscopes = [
|
| 19 |
+
"microscope",
|
| 20 |
+
"microscope2",
|
| 21 |
+
"deltastagetransmission",
|
| 22 |
+
"deltastagereflection",
|
| 23 |
+
]
|
| 24 |
+
brokerport = "8883"
|
| 25 |
+
|
| 26 |
+
client = MongoClient(MONGODB_URI)
|
| 27 |
+
db = client[database_name]
|
| 28 |
+
collection = db[collection_name]
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
client.admin.command("ping")
|
| 32 |
+
print("Pinged your deployment. You successfully connected to MongoDB!")
|
| 33 |
+
except Exception as e:
|
| 34 |
+
st.write(e)
|
| 35 |
+
|
| 36 |
+
def check_variable(variable_name):
|
| 37 |
+
try:
|
| 38 |
+
|
| 39 |
+
document = collection.find_one({"variable_name": variable_name})
|
| 40 |
+
if document:
|
| 41 |
+
return document.get("value", "Variable not found.")
|
| 42 |
+
else:
|
| 43 |
+
return "Variable not found in the collection."
|
| 44 |
+
except Exception as e:
|
| 45 |
+
return f"An error occurred: {e}"
|
| 46 |
+
|
| 47 |
+
def create_user(username, password):
|
| 48 |
+
api_url = HIVEMQ_BASE_URL + "/mqtt/credentials"
|
| 49 |
+
headers = {
|
| 50 |
+
"Authorization": f"Bearer {HIVEMQ_API_TOKEN}",
|
| 51 |
+
"Content-Type": "application/json",
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
new_user = {"credentials": {"username": username, "password": password}}
|
| 55 |
+
|
| 56 |
+
requests.post(api_url, json=new_user, headers=headers)
|
| 57 |
+
|
| 58 |
+
def delete_user(username):
|
| 59 |
+
headers = {
|
| 60 |
+
"Authorization": f"Bearer {HIVEMQ_API_TOKEN}",
|
| 61 |
+
"Content-Type": "application/json",
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
api_url = HIVEMQ_BASE_URL + "/mqtt/credentials/username/" + username
|
| 65 |
+
requests.delete(api_url, headers=headers)
|
| 66 |
+
|
| 67 |
+
def role_user(username, role):
|
| 68 |
+
headers = {
|
| 69 |
+
"Authorization": f"Bearer {HIVEMQ_API_TOKEN}",
|
| 70 |
+
"Content-Type": "application/json",
|
| 71 |
+
}
|
| 72 |
+
api_url = HIVEMQ_BASE_URL + "/user/" + username + "/roles/" + role + "/attach"
|
| 73 |
+
requests.put(api_url, headers=headers)
|
| 74 |
+
|
| 75 |
+
def update_variable(variable_name, new_value):
|
| 76 |
+
try:
|
| 77 |
+
result = collection.update_one(
|
| 78 |
+
{"variable_name": variable_name},
|
| 79 |
+
{"$set": {"value": new_value}},
|
| 80 |
+
upsert=True,
|
| 81 |
+
)
|
| 82 |
+
if result.matched_count > 0:
|
| 83 |
+
return "Variable updated successfully."
|
| 84 |
+
else:
|
| 85 |
+
return "Variable created and updated successfully."
|
| 86 |
+
except Exception as e:
|
| 87 |
+
return f"An error occurred: {e}"
|
| 88 |
+
|
| 89 |
+
def update_variable_test():
|
| 90 |
+
update_variable(microscope, random.randint(1, 10))
|
| 91 |
+
|
| 92 |
+
def check_variable_test():
|
| 93 |
+
st.write(check_variable(microscope))
|
| 94 |
+
|
| 95 |
+
def get_current_time():
|
| 96 |
+
# api_url = "http://worldtimeapi.org/api/timezone/Etc/UTC"
|
| 97 |
+
# try:
|
| 98 |
+
# response = requests.get(api_url)
|
| 99 |
+
# response.raise_for_status()
|
| 100 |
+
# data = response.json()
|
| 101 |
+
# return data['unixtime']
|
| 102 |
+
# except requests.RequestException as e:
|
| 103 |
+
# return f"Error: {e}"
|
| 104 |
+
unix_time = int(time.time())
|
| 105 |
+
return unix_time
|
| 106 |
+
|
| 107 |
+
def button():
|
| 108 |
+
st.session_state.button_clicked = True
|
| 109 |
+
|
| 110 |
+
if "button_clicked" not in st.session_state:
|
| 111 |
+
st.session_state.button_clicked = False
|
| 112 |
+
if "previous_selected_value" not in st.session_state:
|
| 113 |
+
st.session_state.previous_selected_value = microscopes[1]
|
| 114 |
+
|
| 115 |
+
st.write("Keys will last 30 minutes before being overridable")
|
| 116 |
+
st.write("Broker IP:")
|
| 117 |
+
st.code(HIVEMQ_BROKER)
|
| 118 |
+
st.write("Broker port:")
|
| 119 |
+
st.code(brokerport)
|
| 120 |
+
st.write("Usernames:")
|
| 121 |
+
st.code(
|
| 122 |
+
"""
|
| 123 |
+
microscope -> microscopeclientuser
|
| 124 |
+
microscope2 -> microscope2clientuser
|
| 125 |
+
deltastagereflection -> deltastagereflectionclientuser
|
| 126 |
+
deltastagetransmission -> deltastagetransmissionclientuser
|
| 127 |
+
"""
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
microscope = st.selectbox(
|
| 131 |
+
"Choose a microscope:", microscopes, index=microscopes.index("microscope2")
|
| 132 |
+
)
|
| 133 |
+
if microscope != st.session_state.get("previous_selected_value", microscope):
|
| 134 |
+
|
| 135 |
+
st.session_state.button_clicked = False
|
| 136 |
+
|
| 137 |
+
st.session_state["previous_selected_value"] = microscope
|
| 138 |
+
|
| 139 |
+
st.button(
|
| 140 |
+
"Request temporary access",
|
| 141 |
+
help="If somebody is using the microscope, you will need to wait",
|
| 142 |
+
on_click=button,
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
if st.session_state.button_clicked:
|
| 146 |
+
display_text = st.empty()
|
| 147 |
+
ctime = get_current_time()
|
| 148 |
+
var = check_variable(microscope)
|
| 149 |
+
if ctime >= var + access_time:
|
| 150 |
+
|
| 151 |
+
access_key = "Microscope" + str(random.randint(10000000, 99999999))
|
| 152 |
+
delete_user(microscope + "clientuser")
|
| 153 |
+
create_user(microscope + "clientuser", access_key)
|
| 154 |
+
if microscope == "microscope2":
|
| 155 |
+
role_user(microscope + "clientuser", "3")
|
| 156 |
+
elif microscope == "microscope":
|
| 157 |
+
role_user(microscope + "clientuser", "4")
|
| 158 |
+
elif microscope == "deltastagereflection":
|
| 159 |
+
role_user(microscope + "clientuser", "5")
|
| 160 |
+
elif microscope == "deltastagetransmission":
|
| 161 |
+
role_user(microscope + "clientuser", "6")
|
| 162 |
+
|
| 163 |
+
display_text.success(
|
| 164 |
+
"Access key: " + access_key
|
| 165 |
+
) # change placeholder to access_key
|
| 166 |
+
update_variable(microscope, ctime)
|
| 167 |
+
|
| 168 |
+
else:
|
| 169 |
+
while True:
|
| 170 |
+
if access_time - ctime + var <= 0:
|
| 171 |
+
display_text.success("Access key ready!")
|
| 172 |
+
break
|
| 173 |
+
if (access_time - ctime + var) % 60 < 10:
|
| 174 |
+
seconds = "0" + str((access_time - ctime + var) % 60)
|
| 175 |
+
else:
|
| 176 |
+
seconds = str((access_time - ctime + var) % 60)
|
| 177 |
+
display_text.error(
|
| 178 |
+
"Please wait "
|
| 179 |
+
+ str(
|
| 180 |
+
int(
|
| 181 |
+
(
|
| 182 |
+
access_time
|
| 183 |
+
- ctime
|
| 184 |
+
+ var
|
| 185 |
+
- (access_time - ctime + var) % 60
|
| 186 |
+
)
|
| 187 |
+
/ 60
|
| 188 |
+
)
|
| 189 |
+
)
|
| 190 |
+
+ ":"
|
| 191 |
+
+ seconds
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
ctime = ctime + 1
|
| 195 |
+
if ctime % 15 == 0:
|
| 196 |
+
ctime = get_current_time() + 1
|
| 197 |
+
time.sleep(1)
|
| 198 |
+
while True:
|
| 199 |
+
time.sleep(5)
|
| 200 |
+
cutime = get_current_time()
|
| 201 |
+
var = check_variable(microscope)
|
| 202 |
+
if cutime <= var + access_time:
|
| 203 |
+
display_text.error("The access key was taken!")
|
| 204 |
+
break
|
| 205 |
+
time.sleep(10)
|
livestream.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def show():
|
| 2 |
+
import streamlit as st
|
| 3 |
+
|
| 4 |
+
st.title("Livestream")
|
| 5 |
+
# livestreammicroscope1 = ""
|
| 6 |
+
livestreammicroscope2 = "https://www.youtube.com/live/xbWFNAgEIDQ"
|
| 7 |
+
# livestreamdeltastagetransmission = ""
|
| 8 |
+
# livestreamdeltastagereflection = ""
|
| 9 |
+
st.write("Here are the live microscope camera views of all the microscopes")
|
| 10 |
+
|
| 11 |
+
st.write("Microscope 2:")
|
| 12 |
+
st.link_button("Microscope 2 Livestream", livestreammicroscope2)
|
microscope_demo_client.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
import io
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import shutil
|
| 6 |
+
import time
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
from queue import Queue
|
| 9 |
+
|
| 10 |
+
import paho.mqtt.client as mqtt
|
| 11 |
+
from PIL import Image
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class MicroscopeDemo:
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
host,
|
| 18 |
+
port,
|
| 19 |
+
username,
|
| 20 |
+
password,
|
| 21 |
+
microscope,
|
| 22 |
+
path_to_openflexure_stitching="OPTIONAL",
|
| 23 |
+
):
|
| 24 |
+
self.host = host
|
| 25 |
+
self.port = port
|
| 26 |
+
self.username = username
|
| 27 |
+
self.password = password
|
| 28 |
+
self.microscope = microscope
|
| 29 |
+
self.path_to_openflexure_stitching = path_to_openflexure_stitching
|
| 30 |
+
|
| 31 |
+
self.client = mqtt.Client()
|
| 32 |
+
self.client.tls_set()
|
| 33 |
+
self.client.username_pw_set(self.username, self.password)
|
| 34 |
+
|
| 35 |
+
self.receiveq = Queue()
|
| 36 |
+
|
| 37 |
+
def on_message(client, userdata, message):
|
| 38 |
+
received = json.loads(message.payload.decode("utf-8"))
|
| 39 |
+
self.receiveq.put(received)
|
| 40 |
+
if len(json.dumps(received)) <= 300:
|
| 41 |
+
print(received)
|
| 42 |
+
else:
|
| 43 |
+
try:
|
| 44 |
+
print(json.dumps(received)[:300] + "...")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"Command printing error (program will continue) {e}")
|
| 47 |
+
|
| 48 |
+
self.client.on_message = on_message
|
| 49 |
+
|
| 50 |
+
self.client.connect(self.host, port=self.port, keepalive=60, bind_address="")
|
| 51 |
+
|
| 52 |
+
self.client.loop_start()
|
| 53 |
+
|
| 54 |
+
self.client.subscribe(self.microscope + "/return", qos=2)
|
| 55 |
+
|
| 56 |
+
def scan_and_stitch(
|
| 57 |
+
self, c1, c2, temp, ov=1200, foc=0, output="Downloads/stitched.jpeg"
|
| 58 |
+
):
|
| 59 |
+
"""takes a scan with the same inputs + 2 more and outputs a stitched
|
| 60 |
+
image. output is the directory the stitched image will go to and temp is
|
| 61 |
+
the temporary directory to stitch the image otherwise it works just like
|
| 62 |
+
scan()"""
|
| 63 |
+
command = json.dumps(
|
| 64 |
+
{"command": "scan", "c1": c1, "c2": c2, "ov": ov, "foc": foc}
|
| 65 |
+
)
|
| 66 |
+
self.client.publish(
|
| 67 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 68 |
+
)
|
| 69 |
+
while self.receiveq.empty():
|
| 70 |
+
time.sleep(0.05)
|
| 71 |
+
image = self.receiveq.get()
|
| 72 |
+
image_list = image["images"]
|
| 73 |
+
if os.path.isdir(temp):
|
| 74 |
+
shutil.rmtree(temp)
|
| 75 |
+
os.makedirs(temp)
|
| 76 |
+
for i in range(len(image_list)):
|
| 77 |
+
image_bytes = base64.b64decode(image_list[i])
|
| 78 |
+
with io.BytesIO(image_bytes) as buffer:
|
| 79 |
+
img = Image.open(buffer)
|
| 80 |
+
img.save(
|
| 81 |
+
temp + "/" + str(i) + ".jpeg",
|
| 82 |
+
format="JPEG",
|
| 83 |
+
exif=img.info.get("exif"),
|
| 84 |
+
)
|
| 85 |
+
os.system(
|
| 86 |
+
"cd "
|
| 87 |
+
+ self.path_to_openflexure_stitching
|
| 88 |
+
+ " && python -m venv .venv && .\\.venv\\Scripts\\activate && openflexure-stitch " # noqa: E501
|
| 89 |
+
+ temp
|
| 90 |
+
)
|
| 91 |
+
# edit the cd commands so it can find the stitching program on your
|
| 92 |
+
# machine and make sure that openflexure-stitching is installed
|
| 93 |
+
|
| 94 |
+
pos = len(temp)
|
| 95 |
+
for char in ("/", "\\"):
|
| 96 |
+
index = temp.rfind(char)
|
| 97 |
+
if index != -1 and index < pos:
|
| 98 |
+
pos = index
|
| 99 |
+
result = temp[pos + 1 :] if pos < len(temp) else temp
|
| 100 |
+
|
| 101 |
+
shutil.move(temp + "/" + result + "_stitched.jpg", output)
|
| 102 |
+
|
| 103 |
+
def move(self, x, y, z=False, relative=False):
|
| 104 |
+
"""moves to given coordinates x, y (and z if it is set to any integer
|
| 105 |
+
value, if it is set to False the z value wont change). If relative is
|
| 106 |
+
True, then it will move relative to the current position instead of
|
| 107 |
+
moving to the absolute coordinates"""
|
| 108 |
+
command = json.dumps(
|
| 109 |
+
{"command": "move", "x": x, "y": y, "z": z, "relative": relative}
|
| 110 |
+
)
|
| 111 |
+
self.client.publish(
|
| 112 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 113 |
+
)
|
| 114 |
+
while self.receiveq.empty():
|
| 115 |
+
time.sleep(0.05)
|
| 116 |
+
return self.receiveq.get()
|
| 117 |
+
|
| 118 |
+
def scan(self, c1, c2, ov=1200, foc=0):
|
| 119 |
+
"""returns a list of image objects. Takes images to scan an entire area
|
| 120 |
+
specified by two corners. you can input the corner coordinates as "x1
|
| 121 |
+
y1", "x2, y2" or [x1, y1], [x2, y2]. ov refers to the overlap between
|
| 122 |
+
the images (useful for stitching) and foc refers to how much the
|
| 123 |
+
microscope should focus between images (0 to disable)"""
|
| 124 |
+
command = json.dumps(
|
| 125 |
+
{"command": "scan", "c1": c1, "c2": c2, "ov": ov, "foc": foc}
|
| 126 |
+
)
|
| 127 |
+
self.client.publish(
|
| 128 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 129 |
+
)
|
| 130 |
+
while self.receiveq.empty():
|
| 131 |
+
time.sleep(0.05)
|
| 132 |
+
image_l = self.receiveq.get()
|
| 133 |
+
image_list = image_l["images"]
|
| 134 |
+
for i in range(len(image_list)):
|
| 135 |
+
image = image_list[i]
|
| 136 |
+
image_bytes = base64.b64decode(image)
|
| 137 |
+
image_object = Image.open(BytesIO(image_bytes))
|
| 138 |
+
image_list[i] = image_object
|
| 139 |
+
return image_list
|
| 140 |
+
|
| 141 |
+
def focus(self, amount="fast"):
|
| 142 |
+
"""focuses by different amounts: huge, fast, medium, fine, or any
|
| 143 |
+
integer value"""
|
| 144 |
+
command = json.dumps({"command": "focus", "amount": amount})
|
| 145 |
+
self.client.publish(
|
| 146 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 147 |
+
)
|
| 148 |
+
while self.receiveq.empty():
|
| 149 |
+
time.sleep(0.05)
|
| 150 |
+
return self.receiveq.get()
|
| 151 |
+
|
| 152 |
+
def get_pos(
|
| 153 |
+
self,
|
| 154 |
+
):
|
| 155 |
+
"""returns a dictionary with x, y, and z coordinates eg.
|
| 156 |
+
{'x':1,'y':2,'z':3}"""
|
| 157 |
+
command = json.dumps({"command": "get_pos"})
|
| 158 |
+
self.client.publish(
|
| 159 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 160 |
+
)
|
| 161 |
+
while self.receiveq.empty():
|
| 162 |
+
time.sleep(0.05)
|
| 163 |
+
pos = self.receiveq.get()
|
| 164 |
+
return pos["pos"]
|
| 165 |
+
|
| 166 |
+
def take_image(self):
|
| 167 |
+
"""returns an image object"""
|
| 168 |
+
command = json.dumps({"command": "take_image"})
|
| 169 |
+
self.client.publish(
|
| 170 |
+
self.microscope + "/command", payload=command, qos=2, retain=False
|
| 171 |
+
)
|
| 172 |
+
while self.receiveq.empty():
|
| 173 |
+
time.sleep(0.05)
|
| 174 |
+
image = self.receiveq.get()
|
| 175 |
+
image_string = image["image"]
|
| 176 |
+
image_bytes = base64.b64decode(image_string)
|
| 177 |
+
image_object = Image.open(BytesIO(image_bytes))
|
| 178 |
+
return image_object
|
| 179 |
+
|
| 180 |
+
def end_connection(self): # ends the connection
|
| 181 |
+
self.client.loop_stop()
|
| 182 |
+
self.client.disconnect()
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
paho-mqtt
|
| 2 |
+
Pillow
|
| 3 |
+
pymongo
|
| 4 |
+
requests
|
| 5 |
+
streamlit
|