Graphical User Interface

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.

File Tree of Flask Web Application

 

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:

 Version 1 of GUI (Taken from Phone Camera and Stitched Together Hence the Chunkiness)
<!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.
 Version 2 of GUI with Tailwind CSS

V3

Identified Issues with Previous Prototype:

  • Nil; experimenting with different designs.

Modifications Made:

  • Add more colours
  • Add boxes for clearer organisation
  Version 3 of GUI 

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 &lt;= abs_x &lt;= 150: # hard coded limits
					move_str += "X{:.2f} ".format(abs_x)
					self.pos["x"] = abs_x
			if abs_y:
				if 0 &lt;= abs_y &lt;= 180: # hard coded limits
					move_str += "Y{:.2f} ".format(abs_y)
					self.pos["y"] = abs_y
			if abs_z:
				if 0 &lt;= abs_z &lt;= 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) &gt; 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.

Leave a Reply