Considerations and Justifications for Choice
The automated cell-counting microscope should be designed with the end-user in mind. In this case, they are likely non-technical people such as biological sciences researchers. An interface would allow them to interact and use the prototype without having to handle code directly.
Prototyping Process: Cycle of Test, Critique and Re-Design
V1
The file tree structure is as shown below. Flask was chosen as it is a lightweight Python web framework that enables quick building.
Design and Layout of Interface
For simplicity, the web application comprises a single HTML page and the user moves the microscope by specifying coordinates forms and clicking buttons. The code for the HTML page is as follows:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>I love CY2003!</title> <script type="text/javascript" src="{{ url_for('static', filename='jquery-3.6.0.min.js') }}"></script> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <!--Import materialize.css--> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/materialize.min.css') }}" media="screen,projection"/> <style> body{ background-color: #2C3E50;get line-height: 140%; } #main{ padding: 2em; margin: 0 auto; width: 60%; background-color: #FAFAFA } button{ font-size: 140%; border: 0.5em auto; padding: 0.25em; margin: 0.25em; } </style> <script> internal_state = { "move_unit": "0.1", "move_axis": "x", "move_direction":"+" } console.log(internal_state) $(function(){ $('#get_pos').on('click', function(e) { e.preventDefault() console.log("GET POSITION") $.ajax({ url:'/get_position', //data:{"test": "test"}, method : 'POST', success: function(data){ console.log("ASFSFDFDS") $("#x_pos").html(data['pos'][0]) $("#y_pos").html(data['pos'][1]) $("#z_pos").html(data['pos'][2]) } }) }) }) $(function(){ $('#home').on('click', function(e) { e.preventDefault() $.ajax({ url:'/move_home', //data:{"test": "test"}, method : 'POST', success: function(data){ console.log("homed") $("#x_pos").html(data['pos'][0]) $("#y_pos").html(data['pos'][1]) $("#z_pos").html(data['pos'][2]) } }) }) }) $(function(){ $("#image").on('click', function(e){ e.preventDefault() $.ajax({ url:'/get_image', method : 'POST', success:function(data){ console.log(data) document.getElementById("test_image").src = data["source"]; document.getElementById("test_image").display = "block" document.getElementById("test_image").width = 1080 document.getElementById("test_image").height = 608 } }) }) }) $(function(){ $("#count").on('click', function(e){ e.preventDefault() $.ajax({ url:'/capture_and_count', method : 'POST', success:function(data){ document.getElementById("test_image").src = data["source"]; document.getElementById("test_image").display = "block" document.getElementById("count_show").html = data["count"] document.getElementById("test_image").width = 600 document.getElementById("test_image").height = 600 } }) }) }) $(function(){ $("#set_1").on('click', function(e){ internal_state["move_unit"] = "0.1" $.ajax({ url: '/set_units', method:'POST', data: { 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ } }) }) }) $(function(){ $("#set_2").on('click', function(e){ internal_state["move_unit"] = "1.0" $.ajax({ url: '/set_units', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#set_3").on('click', function(e){ internal_state["move_unit"] = "10" $.ajax({ url: '/set_units', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#set_X").on('click', function(e){ internal_state["move_axis"] = "x" $.ajax({ url: '/set_axis', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#set_Y").on('click', function(e){ internal_state["move_axis"] = "y" $.ajax({ url: '/set_axis', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#set_Z").on('click', function(e){ internal_state["move_axis"] = "z" $.ajax({ url: '/set_axis', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#flip_direction").on('click', function(e){ if(internal_state["move_direction"] == "+"){ internal_state["move_direction"] = "-" } else{ internal_state["move_direction"] = "+" } $.ajax({ url: '/set_direction', method:'POST', data:{ 'move_unit': internal_state['move_unit'], 'move_axis': internal_state['move_axis'], 'move_direction': internal_state['move_direction'], }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#move_1").on('click', function(e){ $.ajax({ url: '/move_by', method:'POST', data:{ 'move_by': 1 }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#move_2").on('click', function(e){ $.ajax({ url: '/move_by', method:'POST', data:{ 'move_by': 2 }, success:function(data){ console.log(data) } }) }) }) $(function(){ $("#move_5").on('click', function(e){ $.ajax({ url: '/move_by', method:'POST', data:{ 'move_by': 5 }, success:function(data){ console.log(data) } }) }) }) </script> </head> <body> <div id = "main"> <h1>Designing a Cell Counting microscope</h1> <h2>Movement Control</h2> <button type = "button" id = "get_pos">Get Current Position</button> <button type = "button" id = "home">Home motor</button> <button type = "button" id = "image">Send image</button> <button type = "button" id = "count">Take picture and count</button> <br/> <button type = "button" id = "set_1">Set 0.1mm</button> <button type = "button" id = "set_2">Set 1.0mm</button> <button type = "button" id = "set_3">Set 10mm</button> <br /> <button type = "button" id = "set_X">Set X</button> <button type = "button" id = "set_Y">Set Y</button> <button type = "button" id = "set_Z">Set Z</button> <br /> <button type = "button" id = "switch_direction">Flip Direction</button> <br /> <button type = "button" id = "move_1">Move 1</button> <button type = "button" id = "move_2">Move 2</button> <button type = "button" id = "move_5">Move 5</button> <br /> Counts: <span id ="count_show"></span> <br /> Current position: <br /> x: <span id = "x_pos"> </span> <br /> y: <span id = "y_pos"> </span> <br /> z: <span id = "z_pos"> </span> <br /> <hr /> <form action = "{{ url_for('move_abs') }}" method = "post"> x: <input type="text" id="set_x_pos" name="set_x_pos"><br /> y: <input type="text" id="set_y_pos" name="set_y_pos"><br /> z: <input type="text" id="set_z_pos" name="set_z_pos"><br /> <input type="submit" value = "Move to position!"> </form> <br /> <img id = "test_image" width = 1 height = 1 display = "none" src = ""> </div> </body> </html>
The routes are called for the relevant buttons and form submissions to relay the requests to the server. The server comprises controller and routes.
V2
Identified Issues with Previous Prototype:
- Basic design
Modifications Made:
- Added Tailwind CSS for a cleaner and sleeker UI.
V3
Identified Issues with Previous Prototype:
- Nil; experimenting with different designs.
Modifications Made:
- Add more colours
- Add boxes for clearer organisation
Here is the code for the single page application:
<!DOCTYPE html> <html lang="en"> <style> body {background-color: #EFFBFC;} h1 {font-family: Georgia;font-style: italic;} .h1-border { border-color: #C2B5E5; border-width: 10px; border-style: solid; border-radius: 5px; } .h1-background {background-color:#E9E9FC} .center {text-align: center;} .background-border { border-color: #EFFBFC; border-width: 30px; border-style: solid; border-radius: 20px; } .background-border-bottom { border-color: #76EAEA; border-width: 5px; border-style: solid; border-radius: 20px; } #leftsect {width: 580px;height: 600px;background-color: white;float: left;position: relative; } #rightsect {width:600px;height: 630px;background-color: #5F81D9;float: right;position: relative} #hiddensect {width: 1280px; height: 630px; background-color: #EFFBFC;position: relative} #bottomsect {width: 1280px; height: 300px; background-color: #CBF7EA;position: absolute; top: 700px;bottom:0px; } .label-box {border-style: solid; border-color: #7255C1; border-width: 3px; text-align: center; border-radius: 5px} .microscope-movement {background-color: #A3B5FD; padding: 5px; font-family: Georgia; position: relative;} .movement-buttons-set001 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-set001:hover {background-color: #9DCAE7;} .movement-buttons-set001:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-set01 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 50px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-set01:hover {background-color: #9DCAE7;} .movement-buttons-set01:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-set1 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 10px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-set1:hover {background-color: #9DCAE7;} .movement-buttons-set1:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-set10 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 50px; top: 10px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-set10:hover {background-color: #9DCAE7;} .movement-buttons-set10:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-setx { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 20px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-setx:hover {background-color: #9DCAE7;} .movement-buttons-setx:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-sety { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 15px; top: 20px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-sety:hover {background-color: #9DCAE7;} .movement-buttons-sety:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-setz { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 25px; top: 20px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-setz:hover {background-color: #9DCAE7;} .movement-buttons-setz:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-move1 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 30px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-move1:hover {background-color: #9DCAE7;} .movement-buttons-move1:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-move2 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 15px; top: 30px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-move2:hover {background-color: #9DCAE7;} .movement-buttons-move2:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-move3 { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 180px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 25px; top: 30px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-move3:hover {background-color: #9DCAE7;} .movement-buttons-move3:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-flip { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 40px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-flip:hover {background-color: #9DCAE7;} .movement-buttons-flip:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-home { background-color: #A5E8FF; border: 1px solid #5F81D9; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 50px; top: 40px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-home:hover {background-color: #9DCAE7;} .movement-buttons-home:active {background-color: #9DCAE7; box-shadow: 0 1px #666; transform: translateY(3px);} .focus-box {border-style: solid; border-color: #FF256D; border-width: 3px; text-align: center; top: 40px; border-radius: 5px} .microscope-focus {background-color: #FE72BB; padding: 5px; font-family: Georgia; position: relative;} .movement-buttons-autofocus { background-color: #FFC3D3; border: 1px solid #FE72BB; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 40px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-autofocus:hover {background-color: #E49AAB;} .movement-buttons-autofocus:active {background-color: #E49AAB; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-autofocus2 { background-color: #FFC3D3; border: 1px solid #FE72BB; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 50px; top: 40px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-autofocus2:hover {background-color: #E49AAB;} .movement-buttons-autofocus2:active {background-color: #E49AAB; box-shadow: 0 1px #666; transform: translateY(3px);} .movement-buttons-check { background-color: #FFC3D3; border: 1px solid #FE72BB; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 50px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .movement-buttons-check:hover {background-color: #E49AAB;} .movement-buttons-check:active {background-color: #E49AAB; box-shadow: 0 1px #666; transform: translateY(3px);} .count-buttons-send { background-color: #FFABDB; border: 1px solid #FE72BB; color: black; height: 50px; width: 260px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 5px; top: 410px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .count-buttons-send:hover {background-color: #E49AAB;} .count-buttons-send:active {background-color: #E49AAB; box-shadow: 0 1px #666; transform: translateY(3px);} .count-buttons-takepic { background-color: #FFABDB; border: 1px solid #FE72BB; color: black; height: 50px; width: 310px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 20px; top: 410px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .count-buttons-takepic:hover {background-color: #E49AAB;} .count-buttons-takepic:active {background-color: #E49AAB; box-shadow: 0 1px #666; transform: translateY(3px);} .cell-count {border-style: solid; border-color: #C3C5F8; border-width: 3px; text-align: center; top: 530px; border-radius: 5px} .cell-count-content {background-color: #FFEEED; padding: 5px; font-family: Georgia; position: relative;} .manual-box {border-style: solid; border-color: #7EC2AD; border-width: 3px; text-align: center; border-radius: 5px; height: 50px; left: 5px; top:5px; overflow-wrap: break-word;} .adjustment {background-color: #76EAEA; font-family: Georgia; position: relative;padding:0%} .position-current { background-color: #73E5CB; border: 1px solid #6DAA67; color: black; height: 50px; width: 310px; text-align: center; font-size: 30px; font-family: Georgia; position: relative; left: 20px; top: 30px; display: inline-block; cursor: pointer; box-shadow: 0 4px #999; } .position-current:hover {background-color: #5DB6B4;} .position-current:active {background-color: #5DB6B4; box-shadow: 0 1px #666; transform: translateY(3px);} </style> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MnT-Designing A Cell Counting Microscope</title> </head> <body> <div class="h1-border h1-background center"> <h1 style="color:black">CY2003 MnT Designing A Cell Counting Microscope</h1> </div> <div id="parent"> <div id="leftsect" class="background-border"> <h2 class="label-box microscope-movement">Microscope Movement</h2> <button class="movement-buttons-set001">Set 0.01mm</button> <button class="movement-buttons-set01">Set 0.1mm</button> <button class="movement-buttons-set1">Set 1mm</button> <button class="movement-buttons-set10">Set 10mm</button> <button class="movement-buttons-setx">Set X</button> <button class="movement-buttons-sety">Set Y</button> <button class="movement-buttons-setz">Set Z</button> <button class="movement-buttons-move1">Move 1</button> <button class="movement-buttons-move2">Move 2</button> <button class="movement-buttons-move3">Move 3</button> <button class="movement-buttons-flip">Flip Direction</button> <button class="movement-buttons-home">Home Motor</button> <h2 class="focus-box microscope-focus">Microscope Focus</h2> <button class="movement-buttons-autofocus">Autofocus</button> <button class="movement-buttons-autofocus2">Autofocus2</button> <button class="movement-buttons-check">Check Repeat</button> </div> <div id="rightsect" class="background-border"> Display of camera view <h2 class="cell-count cell-count-content">Cell Count:</h2> <button class="count-buttons-send">Send Image</button> <button class="count-buttons-takepic">Take Picture&Count</button> </div> <dic id="hiddensect"> <div id="bottomsect" class="background-border-bottom"> <h2 class="adjustment">Manually Set Position</h2> <form> <input type="number" placeholder="enter x position" required> <input type="number" placeholder="enter y position" required> <input type="number" placeholder="enter z position" required> </form> <button class="position-current">Get Current Position</button> </div> </dic> </div> </body> </html>
———————————————————————————-
Controller and Routes
The controller defines the methods/ functions to obtain requested data from the microscope and to move the microscope accordingly [1].
HTTP requests are made to the routes defined and the routes call on the relevant controller methods to handle the request [1]. Ajax [2] handles HTTP requests from the client-side and sends them to server-side in this case.
Library Imports |
import base64 import time try: from app.printrun.printcore import printcore except: from printrun.printcore import printcore import datetime import subprocess import os import numpy as np import contour_detector from pathlib import Path import cv2 |
Controller |
class MicroscopeController(): def __init__(self, offline = False): self.offline = offline if not offline: try: ports = [str(p) for p in Path("/dev").glob("ttyUSB*")] self.port = ports[0] except: print("device not connected") if not offline: self.p = printcore(ports[0], 250000) self.p.connect() self.pos = {'x':0, 'y':0, 'z':0} self.internal_state = { "move_units": "0.1", "move_axis": "x", "move_direction":"+" } #warm up camera #command = f"fswebcam -r 1920x1080 --jpeg 90 -D 4 -F 2 --no-banner" #process = subprocess.call(command.split(" ")) def disconnect(self): self.disconnect() return 0 def acquire(self, undistort = False): """ :param n: no. of images to take? :type n: { type_description } """ DEFAULT_FOLDER = "app/static/raw" PROCESSED_FOLDER = "app/static/capture" if not os.path.exists(DEFAULT_FOLDER): os.makedirs(DEFAULT_FOLDER) if not os.path.exists(PROCESSED_FOLDER): os.makedirs(PROCESSED_FOLDER) file_id = datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d_%H%M%S") _filename = os.path.join(DEFAULT_FOLDER, file_id + ".jpg") self.cap = cv2.VideoCapture(0) if (not self.cap.isOpened()): print("Camera not found") self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) width = 1920 height = 1080 self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) retval, image = self.cap.read() if retval: cv2.imwrite(_filename, image) else: print("camera not available") self.cap.release() # if undistort: # mtx = np.load("intrinisic_matrix.npy") # dist = np.load("distortion_coeff.npy") # img = cv.imread('raw/{}.jpg'.format(file_id)) # h, w = img.shape[:2] # newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) # dst = cv.undistort(img, mtx, dist, None, newcameramtx) # # crop the image # x, y, w, h = roi # dst = dst[y:y+h, x:x+w] # _filename = 'capture/{}.png'.format(file_id) # cv.imwrite(_filename, dst) return _filename def count_cells(self,fp): count, img = contour_detector.contourdetector(fp) return (count, img) def move_rel(self, distance): if self.internal_state["move_units"] == "0.1": distance *= 0.1 elif self.internal_state["move_units"] == "1.0": distance *= 1 elif self.internal_state["move_units"] == "10": distance *= 10 print("distance to move", distance) if self.internal_state["move_direction"] == "-": distance *= -1 target_x = self.pos['x'] target_y = self.pos['y'] target_z = self.pos['z'] if self.internal_state["move_axis"] == "x": target_x += distance elif self.internal_state["move_axis"] == "y": target_y += distance elif self.internal_state["move_axis"] == "z": target_z += distance self.move(abs_x = target_x, abs_y = target_y, abs_z = target_z) def move(self, abs_x = 0, abs_y = 0, abs_z = 0): print(abs_x, abs_y, abs_z) try: move_str = "" if abs_x == "": abs_x = 0 if abs_y == "": abs_y = 0 if abs_z == "": abs_z = 0 abs_x = float(abs_x) abs_y = float(abs_y) abs_z = float(abs_z) if abs_x: if 0 <= abs_x <= 150: # hard coded limits move_str += "X{:.2f} ".format(abs_x) self.pos["x"] = abs_x if abs_y: if 0 <= abs_y <= 180: # hard coded limits move_str += "Y{:.2f} ".format(abs_y) self.pos["y"] = abs_y if abs_z: if 0 <= abs_z <= 45: # hard coded limits move_str += "Z{:.2f} ".format(abs_z) self.pos["z"] = abs_z if not move_str == "": print("Moving with str G0 ", move_str) if not self.offline: self.p.send(f"G0 {move_str}") except: print("failed for some reason") def get_pos(self): position = "" if not self.offline: self.p.send("M114") for i in range(3): test_str = self.p.printer.readline() print(i, test_str) if len(test_str) > 4: position = test_str position = str(position) if position == "": return [0,0,0] try: coords = (position.split(" ")) coords = [coords[0], coords[1], coords[2]] coords = list(map(lambda x: float(x.split(":")[1]), coords)) self.pos["x"] = coords[0] self.pos["y"] = coords[1] self.pos["z"] = coords[2] print("@@@@", coords) return coords except: return [self.pos["x"], self.pos["y"], self.pos["z"]] return [self.pos["x"], self.pos["y"], self.pos["z"]] def home(self): if not self.offline: self.p.send("G28") time.sleep(5) return 0 |
Default Route to Initialise MicroscopeController class and Render HTML page |
from app import app from flask import jsonify, render_template, request, send_file, make_response @app.route('/') @app.route('/index') def index(): global controller controller = MicroscopeController(offline = True) return render_template('index.html') |
Route for Obtaining Cell Images from Webcam |
@app.route('/get_image', methods = ['GET', 'POST']) def get_image(): fp = controller.acquire() print(fp) fp = "/".join(fp.split("/")[1:]) data = {"source": fp} data = jsonify(data) return data |
Route for Obtaining Cell Image from Webcam and Predicted Cell Count |
@app.route('/capture_and_count', methods = ['GET', 'POST']) def capture_and_count(): fp = controller.acquire() count, fp = controller.count_cells(fp) fp = "/".join(fp.split("/")[1:]) data = {"source": fp, "count": count} data = jsonify(data) return data |
Route for Getting Position of Microscope (X, Y and Z coordinates) |
@app.route('/get_position', methods = ['GET', 'POST']) def get_position(): data = {"pos": controller.get_pos()} data = jsonify(data) return data |
Route to Terminate Connection to Server when No Longer in Use |
def shutdown_server(): func = request.environ.get('werkzeug.server.shutdown') if func is None: raise RuntimeError('Not running with the Werkzeug Server') func() @app.route('/shutdown', methods=['GET']) def shutdown(): shutdown_server() controller.cap.release() return 'Server shutting down...' |
Route to Move Microscope to Specified X, Y and Z Coordinates Submitted via Form on HTML Page |
@app.route('/move_abs', methods = ['POST',]) def move_abs(): controller.move(request.form['set_x_pos'], request.form['set_y_pos'], request.form['set_z_pos']) time.sleep(5) print(request.form['set_x_pos']) print(request.form['set_y_pos']) print(request.form['set_z_pos']) data = {"pos": [0,0,0]} data = jsonify(data) return render_template('index.html', data = data) |
Route to Move Microscope Back to Initial Position (Home) / Reset Microscope Position |
@app.route('/move_home', methods = ['GET', 'POST']) def move_home(): controller.home() # keep querying data = {"pos": controller.get_pos()} data = jsonify(data) return data |
Route to Set Units for Microscope |
@app.route('/set_units', methods = ['POST']) def set_units(): controller.internal_state["move_units"] = request.values.get("move_unit") data = jsonify(data) return data |
Route to Set Axis for Microscope |
@app.route('/set_axis', methods = ['POST']) def set_axis(): controller.internal_state["move_axis"] = request.values.get("move_axis") print(controller.internal_state) data = {"hi": "hi"} data = jsonify(data) return data |
Route to Set Direction for Microscope |
@app.route('/set_direction', methods = ['POST']) def set_direction(): controller.internal_state["move_axis"] = request.values.get("move_direction") #print(controller.internal_state) data = {"hi": "hi"} data = jsonify(data) return data |
Route for Set Coordinates relative to Current Microscope Position |
@app.route('/move_by', methods = ['POST']) def move_by(): distance = request.values.get('move_by') #print("distance returned:",distance) controller.move_rel(float(distance)) time.sleep(1) data = {"hi": "hi"} data = jsonify(data) return data |
Focusing Algorithm and Route for Autofocusing |
def getfocus(fp): img = cv2.imread(fp, 0) centre_y = img.shape[0]//2 centre_x = img.shape[1]//2 dx = 900 dy = 900 img = img[centre_y - dy//2: centre_y + dy//2, centre_x - dx//2: centre_x + dx//2] return (cv2.Laplacian(img, cv2.CV_32F).var()) @app.route('/autofocus', methods = ['POST']) def getfocus(fp): img = cv2.imread(fp, 0) centre_y = img.shape[0]//2 centre_x = img.shape[1]//2 dx = 900 dy = 900 img = img[centre_y - dy//2: centre_y + dy//2, centre_x - dx//2: centre_x + dx//2] return (cv2.Laplacian(img, cv2.CV_32F).var()) @app.route('/autofocus', methods = ['POST']) def autofocus(): scan_distance = float(request.values.get('scan_direction')) scan_direction = request.values.get('scan_direction') scan_step = float(request.values.get('scan_step')) # steps per mm # scan direction # scan step # scan distance # input from ^ # # # # acquire # # threshold for i in range(N_STEP): pass |
References
[1] “Express Tutorial Part 4: Routes and controllers,” Mozilla.org. [Online]. Available: https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes. [Accessed: 13-Jul-2021].
[2] “Ajax,” in Beginning JavaScript®, Hoboken, NJ, USA: John Wiley & Sons, Inc., 2015, pp. 435–462.