├── 3D-design-files ├── FreeCAD │ ├── 1-RackGear.FCStd │ ├── 10-LEDfixture.FCStd │ ├── 2-MotorGear.FCStd │ ├── 3-MotorHousing.FCStd │ ├── 4-CameraAdapter.FCStd │ ├── 5-LensAdapter.FCStd │ ├── 6-ShaftAdapter.FCStd │ ├── 7-RotationGear.FCStd │ ├── 8-TiltGear.FCStd │ ├── 9-LEDhousing.FCStd │ ├── Joystick Controller │ │ ├── Controller housing.FCStd │ │ └── Controller knob.FCStd │ └── Legacy designs │ │ ├── MotorGear_legacy.FCStd │ │ ├── RackGear_legacy.FCStd │ │ ├── RotationGear_legacy.FCStd │ │ └── TiltGear_legacy.FCStd └── STL │ ├── 1-RackGear.stl │ ├── 10-LEDfixture.stl │ ├── 11-ControllerHousing.stl │ ├── 12-ControllerKnob.stl │ ├── 2-MotorGear.stl │ ├── 3-MotorHousing.stl │ ├── 4-CameraAdapter.stl │ ├── 5-LensAdapter.stl │ ├── 6-ShaftAdapter.stl │ ├── 7-RotationGear.stl │ ├── 8-TiltGear.stl │ └── 9-LEDhousing.stl ├── Arduino ├── Controller │ └── Controller.ino └── MainBoard │ └── MainBoard.ino ├── Bill of Materials (BOM).pdf ├── Circuit ├── CircuitAssembly.pdf ├── Controller.pcb ├── Controller_Gerber.zip ├── DesignSpark_how to generate Gerber.pdf ├── MainBoard.pcb └── MainBoard_Gerber.zip ├── HQ-camera ├── CameraAdapter_HQ.stl ├── HQ-camera.gif ├── Instructions_HQ.pdf ├── MicroscoPy_HQ.py └── README.md ├── Image-examples ├── Droplets.jpg ├── Fruitfly.jpg ├── Integrated-Circuit.jpg ├── Microfluidic-chip.jpg ├── Microfluidic-clock_and_paper-clip.jpg ├── Microfluidic-structures.jpg ├── Microfluidic_clock.jpg ├── Microstructures_and_resistor.jpg └── RGB-LED.jpg ├── Instructions.pdf ├── LICENSE ├── List-of-Lego-bricks.pdf ├── Python └── MicroscoPy.py ├── README.md └── docs ├── 3Dprinting.jpg ├── Animation.gif ├── Arduino.jpg ├── Assembly.gif ├── Circuit.jpg ├── Diagram.jpg ├── Electronics.jpg ├── HQ-camera.jpg ├── Images.jpg ├── Instructions.jpg ├── Modes.jpg ├── Operation.jpg ├── Program.jpg ├── Stop-motion.gif └── YouTube.jpg /3D-design-files/FreeCAD/1-RackGear.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/1-RackGear.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/10-LEDfixture.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/10-LEDfixture.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/2-MotorGear.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/2-MotorGear.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/3-MotorHousing.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/3-MotorHousing.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/4-CameraAdapter.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/4-CameraAdapter.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/5-LensAdapter.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/5-LensAdapter.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/6-ShaftAdapter.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/6-ShaftAdapter.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/7-RotationGear.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/7-RotationGear.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/8-TiltGear.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/8-TiltGear.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/9-LEDhousing.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/9-LEDhousing.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Joystick Controller/Controller housing.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Joystick Controller/Controller housing.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Joystick Controller/Controller knob.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Joystick Controller/Controller knob.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Legacy designs/MotorGear_legacy.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Legacy designs/MotorGear_legacy.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Legacy designs/RackGear_legacy.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Legacy designs/RackGear_legacy.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Legacy designs/RotationGear_legacy.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Legacy designs/RotationGear_legacy.FCStd -------------------------------------------------------------------------------- /3D-design-files/FreeCAD/Legacy designs/TiltGear_legacy.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/FreeCAD/Legacy designs/TiltGear_legacy.FCStd -------------------------------------------------------------------------------- /3D-design-files/STL/1-RackGear.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/1-RackGear.stl -------------------------------------------------------------------------------- /3D-design-files/STL/10-LEDfixture.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/10-LEDfixture.stl -------------------------------------------------------------------------------- /3D-design-files/STL/11-ControllerHousing.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/11-ControllerHousing.stl -------------------------------------------------------------------------------- /3D-design-files/STL/12-ControllerKnob.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/12-ControllerKnob.stl -------------------------------------------------------------------------------- /3D-design-files/STL/2-MotorGear.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/2-MotorGear.stl -------------------------------------------------------------------------------- /3D-design-files/STL/3-MotorHousing.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/3-MotorHousing.stl -------------------------------------------------------------------------------- /3D-design-files/STL/4-CameraAdapter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/4-CameraAdapter.stl -------------------------------------------------------------------------------- /3D-design-files/STL/5-LensAdapter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/5-LensAdapter.stl -------------------------------------------------------------------------------- /3D-design-files/STL/6-ShaftAdapter.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/6-ShaftAdapter.stl -------------------------------------------------------------------------------- /3D-design-files/STL/7-RotationGear.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/7-RotationGear.stl -------------------------------------------------------------------------------- /3D-design-files/STL/8-TiltGear.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/8-TiltGear.stl -------------------------------------------------------------------------------- /3D-design-files/STL/9-LEDhousing.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/3D-design-files/STL/9-LEDhousing.stl -------------------------------------------------------------------------------- /Arduino/Controller/Controller.ino: -------------------------------------------------------------------------------- 1 | /* This code sends data via UART from one Arduino board (controller) to another (MainBoard) to control the stepper motors and the LED illumination 2 | * Arduino sends the data only if there is a change in the value of the joysticks or the LED potentiometer 3 | * Therefore, there is no continuous data communication between the mainboard and the controller 4 | * Arduino board: Adafruit ItsyBitsy 32u4 5V 5 | */ 6 | 7 | #include // needed for the I2C communication of the OLED, already installed in Arduino IDE 8 | #include // needed for the OLED // Copyright (c) 2012 Adafruit Industries. All rights reserved. 9 | #include // needed for the OLED // Copyright (c) 2012 Adafruit Industries. All rights reserved. 10 | 11 | // Arduiono pins for the joysticks 12 | #define joystickX A0 13 | #define joystickY A1 14 | #define joystickC A3 15 | #define joystickZ A2 16 | #define joystickR A5 17 | #define joystickT A4 18 | 19 | // potentiometer for the LED intensity 20 | #define potLED 8 21 | 22 | 23 | // change these values to set the direction of the movement with respect to the joystick 24 | // the direction can also be changed by simply rotating the motor 180-deg 25 | 26 | #define directionX -1 27 | #define directionY 1 28 | #define directionZ 1 29 | #define directionC 1 30 | #define directionR -1 31 | #define directionT 1 32 | 33 | // the joystick has a variable speed control at 3 predefined speeds 34 | // change these values to change the speed of the motor 35 | 36 | #define fast 500 37 | #define medium 50 38 | #define slow 10 39 | 40 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 41 | #define SCREEN_HEIGHT 32 // OLED display height, in pixels 42 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); 43 | 44 | int readX; 45 | int readY; 46 | int readC; 47 | int readZ; 48 | int readR; 49 | int readT; 50 | int readL; 51 | 52 | int readXlast = 0; 53 | int readYlast = 0; 54 | int readClast = 0; 55 | int readZlast = 0; 56 | int readRlast = 0; 57 | int readTlast = 0; 58 | int readLlast = 0; 59 | 60 | String stepper; 61 | 62 | void setup() { 63 | 64 | display.begin(SSD1306_SWITCHCAPVCC, 0x3c); //initialize the display 65 | Serial1.begin(57600); //start the serial communication, Serial1: UART 66 | 67 | pinMode(joystickX, INPUT); 68 | pinMode(joystickY, INPUT); 69 | pinMode(joystickZ, INPUT); 70 | pinMode(joystickC, INPUT); 71 | pinMode(joystickR, INPUT); 72 | pinMode(joystickT, INPUT); 73 | pinMode(potLED, INPUT); 74 | 75 | // show a start-up text on the OLED display 76 | 77 | display.clearDisplay(); 78 | display.setRotation(2); 79 | display.setTextColor(WHITE); 80 | display.setCursor(0, 10); 81 | display.setTextSize(2); 82 | display.println("Welcome!"); 83 | display.display(); 84 | 85 | delay(2000); //optional delay for the start-up message 86 | 87 | // set the LED illumination to the potentiometer value at the start-up 88 | 89 | readLlast = map((analogRead(potLED)), 0, 1023, 0, 20); // map to 20 steps (5% steps) 90 | Serial1.print(readL); Serial1.print("L"); Serial1.print(","); // send the data to the MainBoard to update the LED intensity 91 | 92 | // show the LED intensity on the OLED display 93 | 94 | display.clearDisplay(); 95 | display.setCursor(0, 10); 96 | display.setTextSize(2); 97 | display.print("LED:"); display.print(100 - (readLlast * 5)); display.print("%"); // PWM of the LED driver used in the prototype works in the opposite direction 98 | display.display(); 99 | 100 | } 101 | 102 | void loop() { 103 | 104 | readX = map((analogRead(joystickX)), 0, 1023, -3, 3); // map the measured value to 3 positive and 3 negative speed values 105 | if ((readX != readXlast) ) { //check if it has changed 106 | if (readX == -3) { 107 | Serial1.print(fast * -1 * directionX); Serial1.print("X"); Serial1.print(","); 108 | readXlast = readX; 109 | } 110 | if (readX == -2) { 111 | Serial1.print(medium * -1 * directionX); Serial1.print("X"); Serial1.print(","); 112 | readXlast = readX; 113 | } 114 | if (readX == -1) { 115 | Serial1.print(slow * -1 * directionX); Serial1.print("X"); Serial1.print(","); 116 | readXlast = readX; 117 | } 118 | if (readX == 0) { 119 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); //stop the stepper motor by sending "0O," 120 | readXlast = readX; 121 | } 122 | if (readX == 1) { 123 | Serial1.print(slow * directionX); Serial1.print("X"); Serial1.print(","); 124 | readXlast = readX; 125 | } 126 | 127 | if (readX == 2) { 128 | Serial1.print(medium * directionX); Serial1.print("X"); Serial1.print(","); 129 | readXlast = readX; 130 | } 131 | 132 | if (readX == 3) { 133 | Serial1.print(fast * directionX); Serial1.print("X"); Serial1.print(","); 134 | readXlast = readX; 135 | } 136 | 137 | } 138 | 139 | // REPEAT THE SAME FOR ALL OTHER JOYSTICK POTENTIOMETERS 140 | 141 | readY = map((analogRead(joystickY)), 0, 1023, -3, 3); 142 | if (readY != readYlast) { 143 | if (readY == -3) { 144 | Serial1.print(fast * -1 * directionY); Serial1.print("Y"); Serial1.print(","); 145 | readYlast = readY; 146 | } 147 | if (readY == -2) { 148 | Serial1.print(medium * -1 * directionY); Serial1.print("Y"); Serial1.print(","); 149 | readYlast = readY; 150 | } 151 | if (readY == -1) { 152 | Serial1.print(slow * -1 * directionY); Serial1.print("Y"); Serial1.print(","); 153 | readYlast = readY; 154 | } 155 | if (readY == 0) { 156 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); 157 | readYlast = readY; 158 | } 159 | if (readY == 1) { 160 | Serial1.print(slow * directionY); Serial1.print("Y"); Serial1.print(","); 161 | readYlast = readY; 162 | } 163 | 164 | if (readY == 2) { 165 | Serial1.print(medium * directionY); Serial1.print("Y"); Serial1.print(","); 166 | readYlast = readY; 167 | } 168 | 169 | if (readY == 3) { 170 | Serial1.print(fast * directionY); Serial1.print("Y"); Serial1.print(","); 171 | readYlast = readY; 172 | } 173 | 174 | } 175 | 176 | readC = map((analogRead(joystickC)), 0, 1023, -3, 3); 177 | if (readC != readClast) { 178 | if (readC == -3) { 179 | Serial1.print(fast * -1 * directionC); Serial1.print("C"); Serial1.print(","); 180 | readClast = readC; 181 | } 182 | if (readC == -2) { 183 | Serial1.print(medium * -1 * directionC); Serial1.print("C"); Serial1.print(","); 184 | readClast = readC; 185 | } 186 | if (readC == -1) { 187 | Serial1.print(slow * -1 * directionC); Serial1.print("C"); Serial1.print(","); 188 | readClast = readC; 189 | } 190 | if (readC == 0) { 191 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); 192 | readClast = readC; 193 | } 194 | if (readC == 1) { 195 | Serial1.print(slow * directionC); Serial1.print("C"); Serial1.print(","); 196 | readClast = readC; 197 | } 198 | 199 | if (readC == 2) { 200 | Serial1.print(medium * directionC); Serial1.print("C"); Serial1.print(","); 201 | readClast = readC; 202 | } 203 | 204 | if (readC == 3) { 205 | Serial1.print(fast * directionC); Serial1.print("C"); Serial1.print(","); 206 | readClast = readC; 207 | } 208 | 209 | } 210 | 211 | readZ = map((analogRead(joystickZ)), 0, 1023, -3, 3); 212 | if (readZ != readZlast) { 213 | if (readZ == -3) { 214 | Serial1.print(fast * -1 * directionZ); Serial1.print("Z"); Serial1.print(","); 215 | readZlast = readZ; 216 | } 217 | if (readZ == -2) { 218 | Serial1.print(medium * -1 * directionZ); Serial1.print("Z"); Serial1.print(","); 219 | readZlast = readZ; 220 | } 221 | if (readZ == -1) { 222 | Serial1.print(slow * -1 * directionZ); Serial1.print("Z"); Serial1.print(","); 223 | readZlast = readZ; 224 | } 225 | if (readZ == 0) { 226 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); 227 | readZlast = readZ; 228 | } 229 | if (readZ == 1) { 230 | Serial1.print(slow * directionZ); Serial1.print("Z"); Serial1.print(","); 231 | readZlast = readZ; 232 | } 233 | 234 | if (readZ == 2) { 235 | Serial1.print(medium * directionZ); Serial1.print("Z"); Serial1.print(","); 236 | readZlast = readZ; 237 | } 238 | 239 | if (readZ == 3) { 240 | Serial1.print(fast * directionZ); Serial1.print("Z"); Serial1.print(","); 241 | readZlast = readZ; 242 | } 243 | 244 | } 245 | 246 | readR = map((analogRead(joystickR)), 0, 1023, -3, 3); 247 | if (readR != readRlast) { 248 | if (readR == -3) { 249 | Serial1.print(fast * -1 * directionR); Serial1.print("R"); Serial1.print(","); 250 | readRlast = readR; 251 | } 252 | if (readR == -2) { 253 | Serial1.print(medium * -1 * directionR); Serial1.print("R"); Serial1.print(","); 254 | readRlast = readR; 255 | } 256 | if (readR == -1) { 257 | Serial1.print(slow * -1 * directionR); Serial1.print("R"); Serial1.print(","); 258 | readRlast = readR; 259 | } 260 | if (readR == 0) { 261 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); 262 | readRlast = readR; 263 | } 264 | if (readR == 1) { 265 | Serial1.print(slow * directionR); Serial1.print("R"); Serial1.print(","); 266 | readRlast = readR; 267 | } 268 | 269 | if (readR == 2) { 270 | Serial1.print(medium * directionR); Serial1.print("R"); Serial1.print(","); 271 | readRlast = readR; 272 | } 273 | 274 | if (readR == 3) { 275 | Serial1.print(fast * directionR); Serial1.print("R"); Serial1.print(","); 276 | readRlast = readR; 277 | } 278 | 279 | } 280 | 281 | readT = map((analogRead(joystickT)), 0, 1023, -3, 3); 282 | if (readT != readTlast) { 283 | if (readT == -3) { 284 | Serial1.print(fast * -1 * directionT); Serial1.print("T"); Serial1.print(","); 285 | readTlast = readT; 286 | } 287 | if (readT == -2) { 288 | Serial1.print(medium * -1 * directionT); Serial1.print("T"); Serial1.print(","); 289 | readTlast = readT; 290 | } 291 | if (readT == -1) { 292 | Serial1.print(slow * -1 * directionT); Serial1.print("T"); Serial1.print(","); 293 | readTlast = readT; 294 | } 295 | if (readT == 0) { 296 | Serial1.print(0); Serial1.print("O"); Serial1.print(","); 297 | readTlast = readT; 298 | } 299 | if (readT == 1) { 300 | Serial1.print(slow * directionT); Serial1.print("T"); Serial1.print(","); 301 | readTlast = readT; 302 | } 303 | 304 | if (readT == 2) { 305 | Serial1.print(medium * directionT); Serial1.print("T"); Serial1.print(","); 306 | readTlast = readT; 307 | } 308 | 309 | if (readT == 3) { 310 | Serial1.print(fast * directionT); Serial1.print("T"); Serial1.print(","); 311 | readTlast = readT; 312 | } 313 | 314 | } 315 | 316 | // LED INTENSITY 317 | 318 | readL = map((analogRead(potLED)), 0, 1023, 0, 20); 319 | if (readL != readLlast) { 320 | Serial1.print(readL); Serial1.print("L"); Serial1.print(","); 321 | display.clearDisplay(); 322 | display.setCursor(0, 10); 323 | display.setTextSize(2); 324 | display.print("LED:"); display.print(100 - (readL * 5)); display.print("%"); 325 | display.display(); 326 | readLlast = readL; 327 | } 328 | 329 | } 330 | -------------------------------------------------------------------------------- /Arduino/MainBoard/MainBoard.ino: -------------------------------------------------------------------------------- 1 | /* This code controls the stepper motors and the LED intensity 2 | * according to the data received from the UART (RX, TX) (Controller) or the USB (e.g. Raspberry Pi) 3 | * All 6 motors share the same stepper motor pins, but only one 4 | * is activated depending on the status of the Enable pin of the driver 5 | * Data format: "50X," where 50 is the speed and X is the axis 6 | * X: X axis, Y: Y axis, Z: Z axis, C: camera stage, R: rotation, T: tilt 7 | * The data is executed after a "," (comma) is received 8 | */ 9 | 10 | // Arduino board: Adafruit ItsyBitsy32u4 5V 11 | 12 | #include // Library to control stepper motors // Copyright (C) 2010-2018 Mike McCauley 13 | 14 | AccelStepper myStepper(AccelStepper::FULL4WIRE, 12, 9, 11, 7); // stepper motor pins 15 | 16 | #include // Library to control the optional status LED 17 | 18 | ////////////////// Arduino pins connected to the EN of each stepper motor driver 19 | 20 | #define EnableX 10 21 | #define EnableY A3 22 | #define EnableZ 5 23 | #define EnableC A4 24 | #define EnableR 4 25 | #define EnableT A5 26 | 27 | ///////////////// 28 | 29 | #define LEDPWM 6 // High-power illumination LED PWM control pin 30 | #define NeopixelPin 8 // # Status LED (optional, not used) 31 | 32 | const int stepsPerRevolution = 2048; //set according to the specs of the stepper motor 33 | String receivedString; //incoming data from the serial ports (USB or UART) 34 | int stepperSpeed; 35 | char state; //variable to define the active stepper motor, X, Y, Z, C, R, T 36 | int LEDintensity; 37 | 38 | Adafruit_NeoPixel pixel(1, NeopixelPin, NEO_GRB + NEO_KHZ800); 39 | 40 | 41 | void setup() { 42 | 43 | Serial1.begin(57600); // UART with the jostick controller 44 | Serial.begin(57600); // USB, Raspberry Pi 45 | 46 | pinMode(EnableX, OUTPUT); 47 | pinMode(EnableY, OUTPUT); 48 | pinMode(EnableC, OUTPUT); 49 | pinMode(EnableZ, OUTPUT); 50 | pinMode(EnableR, OUTPUT); 51 | pinMode(EnableT, OUTPUT); 52 | pinMode(LEDPWM, OUTPUT); 53 | 54 | analogWrite(LEDPWM, 255); // initialize the LED intensity to 0% 55 | /* PWM of the LED driver (RCD-24-0.70/PL/B, Recom) used in this project 56 | * works in the opposite direction, i.e. 255 is for 0% and 0 for 100% intensity 57 | */ 58 | 59 | pixel.begin(); 60 | pixel.setPixelColor(0, pixel.Color(0, 0, 50)); 61 | pixel.show(); 62 | 63 | myStepper.setMaxSpeed(1000); 64 | } 65 | 66 | void loop() { 67 | 68 | ////// data from UART: joystick controller 69 | 70 | if (Serial1.available() > 0) { 71 | char data = Serial1.read(); 72 | if (data == ',') { // receive data until "," is received 73 | 74 | if (receivedString.length() > 1) { 75 | int stepperSpeed = receivedString.toInt(); // convert the motor speed information to an integer 76 | myStepper.setSpeed(stepperSpeed); // set the speed 77 | 78 | if (receivedString.indexOf('X') > 0) { //X axis 79 | state = 'X'; 80 | } 81 | if (receivedString.indexOf('Y') > 0) { //Y axis 82 | state = 'Y'; 83 | } 84 | if (receivedString.indexOf('C') > 0) { //Camera stage 85 | state = 'C'; 86 | } 87 | 88 | if (receivedString.indexOf('Z') > 0) { //Z axis 89 | state = 'Z'; 90 | } 91 | 92 | if (receivedString.indexOf('R') > 0) { //Rotation stage 93 | state = 'R'; 94 | } 95 | 96 | if (receivedString.indexOf('T') > 0) { //Tilting mechanism 97 | state = 'T'; 98 | } 99 | 100 | if (receivedString.indexOf('L') > 0) { //LED 101 | LEDintensity = map(stepperSpeed, 0, 20, 0, 255); //map to a number from 0 to 255 102 | analogWrite(LEDPWM, LEDintensity); 103 | } 104 | 105 | if (receivedString.indexOf('O') > 0) { //Disable all stepper motors 106 | state = 'O'; 107 | } 108 | receivedString = ""; //reset the string for the next incoming data 109 | } 110 | } 111 | else { 112 | receivedString += data; //store the incoming data in a string until "," is received (above) 113 | } 114 | } 115 | 116 | //// data from USB (Raspberry Pi) (same as above but Serial instead of Serial1) 117 | 118 | if (Serial.available() > 0) { 119 | char data = Serial.read(); 120 | if (data == ',') { 121 | 122 | if (receivedString.length() > 1) { 123 | int stepperSpeed = receivedString.toInt(); 124 | myStepper.setSpeed(stepperSpeed); 125 | if (receivedString.indexOf('X') > 0) { 126 | state = 'X'; 127 | } 128 | if (receivedString.indexOf('Y') > 0) { 129 | state = 'Y'; 130 | } 131 | if (receivedString.indexOf('C') > 0) { 132 | state = 'C'; 133 | } 134 | 135 | if (receivedString.indexOf('Z') > 0) { 136 | state = 'Z'; 137 | } 138 | 139 | if (receivedString.indexOf('R') > 0) { 140 | state = 'R'; 141 | } 142 | 143 | if (receivedString.indexOf('T') > 0) { 144 | state = 'T'; 145 | } 146 | 147 | if (receivedString.indexOf('L') > 0) { 148 | LEDintensity = map(stepperSpeed, 0, 20, 0, 255); 149 | analogWrite(LEDPWM, LEDintensity); 150 | } 151 | 152 | if (receivedString.indexOf('O') > 0) { 153 | state = 'O'; 154 | } 155 | receivedString = ""; 156 | } 157 | } 158 | else { 159 | receivedString += data; 160 | } 161 | } 162 | 163 | ///// RUN STEPPER MOTORS, only one runs at a given time 164 | 165 | if (state == 'X') { 166 | 167 | digitalWrite(EnableX, HIGH); 168 | myStepper.runSpeed(); 169 | } 170 | 171 | if (state == 'Y') { 172 | 173 | digitalWrite(EnableY, HIGH); 174 | myStepper.runSpeed(); 175 | } 176 | 177 | if (state == 'C') { 178 | 179 | digitalWrite(EnableC, HIGH); 180 | myStepper.runSpeed(); 181 | } 182 | 183 | if (state == 'Z') { 184 | 185 | digitalWrite(EnableZ, HIGH); 186 | myStepper.runSpeed(); 187 | } 188 | 189 | if (state == 'R') { 190 | 191 | digitalWrite(EnableR, HIGH); 192 | myStepper.runSpeed(); 193 | } 194 | 195 | if (state == 'T') { 196 | 197 | digitalWrite(EnableT, HIGH); 198 | myStepper.runSpeed(); 199 | } 200 | 201 | // TURN OFF all stepper motors 202 | 203 | if (state == 'O') { 204 | digitalWrite(EnableX, LOW); 205 | digitalWrite(EnableY, LOW); 206 | digitalWrite(EnableZ, LOW); 207 | digitalWrite(EnableC, LOW); 208 | digitalWrite(EnableR, LOW); 209 | digitalWrite(EnableT, LOW); 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /Bill of Materials (BOM).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Bill of Materials (BOM).pdf -------------------------------------------------------------------------------- /Circuit/CircuitAssembly.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/CircuitAssembly.pdf -------------------------------------------------------------------------------- /Circuit/Controller.pcb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/Controller.pcb -------------------------------------------------------------------------------- /Circuit/Controller_Gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/Controller_Gerber.zip -------------------------------------------------------------------------------- /Circuit/DesignSpark_how to generate Gerber.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/DesignSpark_how to generate Gerber.pdf -------------------------------------------------------------------------------- /Circuit/MainBoard.pcb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/MainBoard.pcb -------------------------------------------------------------------------------- /Circuit/MainBoard_Gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Circuit/MainBoard_Gerber.zip -------------------------------------------------------------------------------- /HQ-camera/CameraAdapter_HQ.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/HQ-camera/CameraAdapter_HQ.stl -------------------------------------------------------------------------------- /HQ-camera/HQ-camera.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/HQ-camera/HQ-camera.gif -------------------------------------------------------------------------------- /HQ-camera/Instructions_HQ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/HQ-camera/Instructions_HQ.pdf -------------------------------------------------------------------------------- /HQ-camera/MicroscoPy_HQ.py: -------------------------------------------------------------------------------- 1 | # Modified code for the HQ camera 2 | 3 | # MICROSCOPE APP 4 | # Program to control camera settings, microscope stepper motors, and LED illumination from keyboard 5 | # Camera: Pi camera v2 6 | # Enable the camera: Preferences --> Raspberry Pi Configuration --> Interfaces --> Camera --> Enable --> Restart 7 | # Increase GPU memory (Raspberry Pi 4, max. camera resolution): Preferences --> Raspberry Pi Configuration --> Performance --> GPU memory --> Change from 128 to 144 8 | # Arduino USB communication baud rate: 57600 9 | 10 | ## IMPORTANT! For Raspberry Pi zero, and Raspberry Pi 3, reduce the resolution by setting HighResolution = False 11 | 12 | HighResolution = True # True: (4056, 3040), False: (1920, 1080) 13 | 14 | KeyboardControl = True # set to "False" if the Arduino is not connected to the Raspberry Pi via USB 15 | 16 | import sys 17 | import easygui 18 | import serial 19 | import time 20 | from datetime import datetime 21 | from pynput.keyboard import Key, KeyCode, Listener 22 | 23 | ### camera 24 | 25 | from picamera import PiCamera 26 | camera = PiCamera() 27 | brightness = 50 #start-up brightness 28 | contrast= 0 #start-up contrast 29 | EV = 0 #start-up exposure compensation 30 | saturation = 0 #start-up saturation 31 | 32 | ### serial communication with Arduino 33 | 34 | if KeyboardControl == True: 35 | ser = serial.Serial('/dev/ttyACM0',57600) # type "ls /dev/tty*" to the terminal to check the serial port 36 | 37 | ### predefined motor speeds 38 | slow = 10 39 | medium = 100 40 | fast = 500 41 | speed = medium #start-up speed:medium, Ctrl key changes the speed 42 | 43 | zoom = 1.0 #start-up digital zoom factor 44 | 45 | LEDintensity = 20 #start-up LED intensity 46 | #will be multiplied by 5 to get 0-100% range in 5% steps to make it compatible with 47 | #the potentiometer on the Arduino joystick controller 48 | 49 | filename = "" #default filename prefix 50 | path = "/home/pi/Pictures" #default path 51 | 52 | arrowkey_mode = False #Alt key toggles this variable to select between X-Y (start-up) and Rotate-Tilt control 53 | recording_mode = False #Tab key toggles between the photo mode (start-up) and the video mode 54 | recording = False #variable to set during recording 55 | 56 | def camera_reset(): 57 | global brightness, contrast, EV, saturation 58 | brightness = 50 59 | camera.brightness = brightness 60 | contrast= 0 61 | camera.contrast = contrast 62 | EV = 0 63 | saturation = 0 64 | camera.saturation = saturation 65 | camera.exposure_compensation = 0 66 | camera.sensor_mode = 0 # auto 67 | if HighResolution == True: 68 | camera.resolution = (4056, 3040) # Pi camera v2 max resolution, 8MP, 3280,2464, Pi HQ: 4056, 3040 69 | else: 70 | camera.resolution = (1920, 1080) # reduced resolution, Full HD or (2592 x 1944) for the 5MP camera v1 71 | camera.rotation=180 72 | camera.annotate_text_size = 100 73 | camera.annotate_text = "" 74 | camera.iso = 0 75 | camera.shutter_speed = 0 76 | camera.framerate = 30 77 | camera.exposure_mode = 'auto' 78 | camera.awb_mode = 'auto' 79 | #camera.preview_fullscreen = False # optional 80 | #camera.preview_window = (0, 50, 1280, 960) #optional 81 | camera.start_preview() 82 | 83 | 84 | def shortcuts(): # keyboard shortcuts 85 | camera.preview_alpha=0 86 | easygui.msgbox("F1: keyboard shortcuts\ 87 | \n----------------------------------------------------------\ 88 | \nTab: toggle between photo and video modes\ 89 | \nP: start camera preview, p: stop camera preview\ 90 | \n----------------------------------------------------------\ 91 | \nB: increase brightness, b: decrease brightness\ 92 | \nC: increase contrast, c: decrease contrast\ 93 | \nV: increase EV, v: decrease EV\ 94 | \nS: increase saturation, s: decrease saturation\ 95 | \n----------------------------------------------------------\ 96 | \n+: digital zoom in, -: digital zoom out\ 97 | \n----------------------------------------------------------\ 98 | \ni: ISO, e: exposure time, r: framerate\ 99 | \nE: exposure mode, W: white-balance mode\ 100 | \n----------------------------------------------------------\ 101 | \n0: reset camera settings\ 102 | \n----------------------------------------------------------\ 103 | \nF: select folder, f: enter filename prefix (optional)\ 104 | \n----------------------------------------------------------\ 105 | \nEnter: save image or start\stop video depending on the mode\ 106 | \n----------------------------------------------------------\ 107 | \nArrow keys: X-Y or Rotate-Tilt\ 108 | \nPage up and Page down: Z up and down\ 109 | \nHome and End: Camera up and down\ 110 | \n----------------------------------------------------------\ 111 | \nCtrl: change motor speed (3 predefined speeds)\ 112 | \nAlt: toggle between X-Y and Rotate-Tilt\ 113 | \n----------------------------------------------------------\ 114 | \nEsc: exit","Keyboard shortcuts") 115 | camera.preview_alpha=255 116 | 117 | 118 | shortcuts() #display the keyboard shortcuts at the beginning 119 | camera_reset() # start the camera preview 120 | 121 | def on_press(key): 122 | global brightness, contrast, EV, saturation, zoom 123 | global speed, LEDintensity 124 | global arrowkey_mode, path, filename, recording_mode 125 | 126 | ### F1: keyboard shortcuts 127 | 128 | if key == Key.f1: 129 | shortcuts() 130 | 131 | ######## CAMERA 132 | 133 | ### Camera preview modes, video or photo 134 | 135 | if key == Key.tab: 136 | if recording_mode == False: 137 | camera.resolution = (1920, 1080) 138 | camera.annotate_text = "Video mode" 139 | recording_mode = True 140 | elif recording_mode == True: 141 | if HighResolution == True: 142 | camera.resolution = (4056, 3040) # Pi camera v2 max resolution, 8MP 143 | else: 144 | camera.resolution = (1920, 1080) # reduced resolution, Full HD 145 | camera.annotate_text = "Photo mode" 146 | recording_mode = False 147 | 148 | ### Reset camera settings 149 | if key == KeyCode(char='0'): 150 | camera_reset() 151 | 152 | ### digital zoom 153 | 154 | if key == KeyCode(char='+'): 155 | if zoom > 0.2: 156 | zoom = zoom - 0.1 157 | camera.zoom = (0,0,zoom,zoom) 158 | annotate_text = "Digital zoom:" + str(round(1/zoom,2)) + "X" 159 | camera.annotate_text = annotate_text 160 | 161 | if key == KeyCode(char='-'): 162 | if zoom < 1.0: 163 | zoom = zoom + 0.1 164 | camera.zoom = (0,0,zoom,zoom) 165 | annotate_text = "Digital zoom:" + str(round(1/zoom,2)) + "X" 166 | camera.annotate_text = annotate_text 167 | 168 | ### start and stop camera preview 169 | 170 | if key == KeyCode(char='P'): 171 | camera.start_preview() 172 | 173 | if key == KeyCode(char='p'): 174 | camera.stop_preview() 175 | 176 | ### brightness 177 | 178 | if key == KeyCode(char='B'): 179 | if brightness !=100: 180 | brightness += 5 181 | camera.brightness = brightness 182 | annotate_text = "Brightness:" + str(brightness) + "%" 183 | camera.annotate_text = annotate_text 184 | 185 | if key == KeyCode(char='b'): 186 | if brightness !=0: 187 | brightness -= 5 188 | camera.brightness = brightness 189 | annotate_text = "Brightness:" + str(brightness) + "%" 190 | camera.annotate_text = annotate_text 191 | 192 | ### contrast 193 | 194 | if key == KeyCode(char='C'): 195 | if contrast !=100: 196 | contrast += 5 197 | camera.contrast = contrast 198 | annotate_text = "Contrast:" + str(contrast) + "%" 199 | camera.annotate_text = annotate_text 200 | if key == KeyCode(char='c'): 201 | if contrast !=-100: 202 | contrast -= 5 203 | camera.contrast = contrast 204 | annotate_text = "Contrast:" + str(contrast) + "%" 205 | camera.annotate_text = annotate_text 206 | 207 | ### EV compensation 208 | 209 | if key == KeyCode(char='V'): 210 | if EV !=25: 211 | EV += 1 212 | camera.exposure_compensation = EV 213 | annotate_text = "EV:" + str(EV) 214 | camera.annotate_text = annotate_text 215 | if key == KeyCode(char='v'): 216 | if EV !=-25: 217 | EV -= 1 218 | camera.exposure_compensation = EV 219 | annotate_text = "EV:" + str(EV) 220 | camera.annotate_text = annotate_text 221 | 222 | ### saturation 223 | 224 | if key == KeyCode(char='S'): 225 | if saturation !=100: 226 | saturation += 5 227 | camera.saturation = saturation 228 | annotate_text = "Saturation:" + str(saturation) + "%" 229 | camera.annotate_text = annotate_text 230 | if key == KeyCode(char='s'): 231 | if saturation !=-100: 232 | saturation -= 5 233 | camera.saturation = saturation 234 | annotate_text = "Saturation:" + str(saturation) + "%" 235 | camera.annotate_text = annotate_text 236 | 237 | ### ISO 238 | 239 | if key == KeyCode(char='i'): 240 | camera.preview_alpha=0 241 | ISO_choices = ["0","100","200","320","400","500","640","800"] 242 | ISO_selected = easygui.choicebox("Select ISO (default: 0 for auto)", "ISO", ISO_choices) 243 | if ISO_selected is not None: 244 | camera.iso = int(ISO_selected) 245 | camera.preview_alpha=255 246 | 247 | ### white balance 248 | 249 | if key == KeyCode(char='W'): 250 | camera.preview_alpha=0 251 | WB_choices = ["off","auto","sunlight","cloudy","shade","tungsten","fluorescent","flash","horizon"] 252 | WB_selected = easygui.choicebox("Select white balance mode (default: auto)", "White Balance", WB_choices) 253 | if WB_selected is not None: 254 | camera.awb_mode = WB_selected 255 | if WB_selected == "off": 256 | WB_gain = easygui.enterbox("White balance gain - typical values between 0.9 and 1.9", "White balance gain", "1.0") 257 | if WB_gain is not None: 258 | camera.awb_gains = float(WB_gain) 259 | camera.preview_alpha=255 260 | 261 | ### frame rate 262 | 263 | if key == KeyCode(char='r'): 264 | camera.preview_alpha=0 265 | Framerate_choices = ["30","20", "15","10","5","2","1", "0.5", "0.1", "manual"] 266 | Framerate_selected = easygui.choicebox("Select framerate","Framerate", Framerate_choices) 267 | if Framerate_selected == "manual": 268 | Framerate_selected = easygui.enterbox("Framerate", "Input", "") 269 | if Framerate_selected is not None: 270 | camera.framerate = float(Framerate_selected) 271 | camera.preview_alpha=255 272 | 273 | ### exposure time (shutter speed) 274 | 275 | if key == KeyCode(char='e'): 276 | camera.preview_alpha=0 277 | Exposure_choices = ["0","10","20","50","100","200", "500", "1000", "manual"] 278 | Exposure_selected = easygui.choicebox("Select exposure time in ms (default: 0, auto)\n\ 279 | Maximum exposure time is determined by the framerate","Exposure time (shutter speed)", Exposure_choices) 280 | if Exposure_selected == "manual": 281 | Exposure_selected = easygui.enterbox("Exposure time (ms)", "Input", "") 282 | if Exposure_selected is not None: 283 | camera.shutter_speed = int(Exposure_selected)*1000 284 | camera.preview_alpha=255 285 | 286 | ### exposure modes 287 | 288 | if key == KeyCode(char='E'): 289 | camera.preview_alpha=0 290 | ExposureMode_choices = ["off","auto","night","nightpreview","backlight","spotlight"] 291 | ExposureMode_selected = easygui.choicebox("Exposure mode", "Exposure mode", ExposureMode_choices) 292 | if ExposureMode_selected is not None: 293 | camera.exposure_mode = ExposureMode_selected 294 | camera.preview_alpha=255 295 | 296 | ### select folder and set filename prefix 297 | 298 | if key == KeyCode(char='F'): 299 | camera.preview_alpha=0 300 | path = easygui.diropenbox() 301 | camera.preview_alpha=255 302 | if key == KeyCode(char='f'): 303 | camera.preview_alpha=0 304 | filename = easygui.enterbox("Filename prefix", "Input", "") 305 | camera.preview_alpha=255 306 | 307 | ######## MOTORIZED STAGE 308 | 309 | ### Ctrl: change motor speed 310 | 311 | if key == Key.ctrl_l or key == Key.ctrl_r: 312 | if speed == slow: 313 | speed = medium 314 | camera.annotate_text = "Motor speed = medium" 315 | elif speed == medium: 316 | speed = fast 317 | camera.annotate_text = "Motor speed = fast" 318 | elif speed == fast: 319 | speed = slow 320 | camera.annotate_text = "Motor speed = slow" 321 | 322 | ### keyboard arrow keys, Alt key toggles between X-Y and Rotate-Tilt 323 | ### send Arduino a string to control the motors, e.g. "-100X," or "500Y," 324 | ### the integer defines the direction and the speed of the motor, the letter selects the motor 325 | 326 | if key == Key.alt_l or key == Key.alt_r: 327 | if arrowkey_mode == True: 328 | arrowkey_mode = False 329 | camera.annotate_text = "X-Y" 330 | elif arrowkey_mode == False: 331 | arrowkey_mode = True 332 | camera.annotate_text = "Rotate-Tilt" 333 | 334 | if key == Key.left: 335 | if arrowkey_mode == False: 336 | data = str(speed*-1) + "X," 337 | else: 338 | data = str(speed*-1) + "R," 339 | ser.write(data.encode()) 340 | 341 | if key == Key.right: 342 | if arrowkey_mode == False: 343 | data = str(speed) + "X," 344 | else: 345 | data = str(speed) + "R," 346 | ser.write(data.encode()) 347 | 348 | if key == Key.down: 349 | if arrowkey_mode == False: 350 | data = str(speed*-1) + "Y," 351 | else: 352 | data = str(speed*-1) + "T," 353 | ser.write(data.encode()) 354 | 355 | if key == Key.up: 356 | if arrowkey_mode == False: 357 | data = str(speed) + "Y," 358 | else: 359 | data = str(speed) + "T," 360 | ser.write(data.encode()) 361 | 362 | ### Z up and down, Page up and Page down 363 | 364 | if key == Key.page_up: 365 | data = str(speed) + "Z," 366 | ser.write(data.encode()) 367 | 368 | if key == Key.page_down: 369 | data = str(speed*-1) + "Z," 370 | ser.write(data.encode()) 371 | 372 | ### Camera up and down, Home and End 373 | 374 | if key == Key.home: 375 | data = str(speed) + "C," 376 | ser.write(data.encode()) 377 | 378 | if key == Key.end: 379 | data = str(speed*-1) + "C," 380 | ser.write(data.encode()) 381 | 382 | ######## LED 383 | 384 | ### LED intensity, max. value in the data is set to 20 to make it compatible with the joystick 385 | ### PWM of the LED driver works in the opposite direction 386 | ### Intensity is controlled in 5% increments 387 | 388 | if key == KeyCode(char='L'): 389 | if LEDintensity !=20: 390 | LEDintensity += 1 391 | data = str(20-LEDintensity) + "L," 392 | ser.write(data.encode()) 393 | annotate_text = "LED intensity:" + str(LEDintensity*5) + "%" 394 | camera.annotate_text = annotate_text 395 | if key == KeyCode(char='l'): 396 | if LEDintensity !=0: 397 | LEDintensity -= 1 398 | data = str(20-LEDintensity) + "L," 399 | ser.write(data.encode()) 400 | annotate_text = "LED intensity:" + str(LEDintensity*5) + "%" 401 | camera.annotate_text = annotate_text 402 | 403 | 404 | def on_release(key): 405 | global path, filename, recording 406 | 407 | ### Esc: exit 408 | 409 | if key == Key.esc: 410 | sys.exit() 411 | 412 | ### Enter: save image 413 | 414 | if key == Key.enter: 415 | camera.annotate_text = "" 416 | now = datetime.now() 417 | if recording_mode == False: #photo mode 418 | now = datetime.now() 419 | output = path+"/"+ filename+now.strftime("%d-%m-%H%M%S.jpg") 420 | time.sleep(2) 421 | camera.capture(output, quality=100) 422 | camera.annotate_text = "Photo saved" 423 | if recording_mode == True: #video mode 424 | if recording == False: 425 | output = path+"/"+ filename+now.strftime("%d-%m-%H%M%S.h264") 426 | camera.annotate_text = "Recording will start in 2 seconds..." 427 | time.sleep(2) 428 | camera.annotate_text = "" 429 | camera.start_recording(output) 430 | recording = True 431 | elif recording == True: 432 | camera.stop_recording() 433 | camera.annotate_text = "Recording stopped" 434 | recording = False 435 | 436 | ### Stop motors when key is released 437 | 438 | if key == Key.up or key == Key.down or key == Key.left or key == Key.right\ 439 | or key == Key.page_up or key == Key.page_down or key == Key.home\ 440 | or key == Key.end: 441 | ser.write(b"0O,") ## send to Arduino "0O," to stop the motors 442 | 443 | with Listener(on_press=on_press,on_release=on_release) as listener: 444 | listener.join() 445 | 446 | 447 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /HQ-camera/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi High Quality (HQ) camera test 2 | 3 | This folder is dedicated to Raspberry Pi's new [HQ camera](https://www.raspberrypi.org/products/raspberry-pi-high-quality-camera/). 4 | 5 | The new camera gives a better quality imaging but it is 50% larger thus it requires a larger Lego housing, which challenges the tilting mechanism. The improvement is marginal for many cases because the performance of this microscope is anyway limited by other factors, like the low-cost objective lens. 6 | 7 | Overall, I think it is NOT worth the extra effort and cost unless a completely new microscope architecture with a more stable tilting mechanism and a better quality lens is designed. 8 | 9 | Follow these detailed [instructions](Instructions_HQ.pdf) if you would like to try yourself. 10 | 11 | **Note:** Use the modified Python code (**MicroscoPy_HQ.py**) in this folder for the best resolution. 12 | 13 | HQ-camera 14 | 15 | -------------------------------------------------------------------------------- /Image-examples/Droplets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Droplets.jpg -------------------------------------------------------------------------------- /Image-examples/Fruitfly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Fruitfly.jpg -------------------------------------------------------------------------------- /Image-examples/Integrated-Circuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Integrated-Circuit.jpg -------------------------------------------------------------------------------- /Image-examples/Microfluidic-chip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Microfluidic-chip.jpg -------------------------------------------------------------------------------- /Image-examples/Microfluidic-clock_and_paper-clip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Microfluidic-clock_and_paper-clip.jpg -------------------------------------------------------------------------------- /Image-examples/Microfluidic-structures.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Microfluidic-structures.jpg -------------------------------------------------------------------------------- /Image-examples/Microfluidic_clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Microfluidic_clock.jpg -------------------------------------------------------------------------------- /Image-examples/Microstructures_and_resistor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/Microstructures_and_resistor.jpg -------------------------------------------------------------------------------- /Image-examples/RGB-LED.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Image-examples/RGB-LED.jpg -------------------------------------------------------------------------------- /Instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/Instructions.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright ©2020 IBM Corp. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /List-of-Lego-bricks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/List-of-Lego-bricks.pdf -------------------------------------------------------------------------------- /Python/MicroscoPy.py: -------------------------------------------------------------------------------- 1 | # Program to control camera settings, microscope stepper motors, and LED illumination from keyboard 2 | # Camera: Pi camera v2 3 | # Enable the camera: Preferences --> Raspberry Pi Configuration --> Interfaces --> Camera --> Enable --> Reboot 4 | # Increase GPU memory (Raspberry Pi 4, max. camera resolution): Preferences --> Raspberry Pi Configuration --> Performance --> GPU memory --> Change from 128 to 144 --> Reboot 5 | # Arduino USB communication baud rate: 57600 6 | 7 | # The code can also be used to control the camera settings of any Pi camera without having a microscope. 8 | # In this case, set the variable KeyboardControl = False so that the code does not expect an Arduino board connected to the USB. 9 | 10 | ## IMPORTANT! For Raspberry Pi zero, and Raspberry Pi 3, reduce the resolution by setting HighResolution = False 11 | 12 | HighResolution = True # True: (3280, 2464), False: (1920, 1080) 13 | 14 | KeyboardControl = False # set to "True" if the Arduino is connected to the Raspberry Pi via USB and if you want to control the stepper motors via Keyboard 15 | 16 | import sys 17 | import easygui 18 | import serial 19 | import time 20 | from datetime import datetime 21 | from pynput.keyboard import Key, KeyCode, Listener 22 | 23 | ### camera 24 | 25 | from picamera import PiCamera 26 | camera = PiCamera() 27 | brightness = 50 #start-up brightness 28 | contrast= 0 #start-up contrast 29 | EV = 0 #start-up exposure compensation 30 | saturation = 0 #start-up saturation 31 | 32 | ### serial communication with Arduino 33 | 34 | if KeyboardControl == True: 35 | ser = serial.Serial('/dev/ttyACM0',57600) # type "ls /dev/tty*" to the terminal to check the serial port 36 | 37 | ### predefined motor speeds 38 | slow = 10 39 | medium = 100 40 | fast = 500 41 | speed = medium #start-up speed:medium, Ctrl key changes the speed 42 | 43 | zoom = 1.0 #start-up digital zoom factor 44 | 45 | LEDintensity = 20 #start-up LED intensity 46 | #will be multiplied by 5 to get 0-100% range in 5% steps to make it compatible with 47 | #the potentiometer on the Arduino joystick controller 48 | 49 | filename = "" #default filename prefix 50 | path = "/home/pi/Pictures" #default path 51 | 52 | arrowkey_mode = False #Alt key toggles this variable to select between X-Y (start-up) and Rotate-Tilt control 53 | recording_mode = False #Tab key toggles between the photo mode (start-up) and the video mode 54 | recording = False #variable to set during recording 55 | 56 | def camera_reset(): 57 | global brightness, contrast, EV, saturation 58 | brightness = 50 59 | camera.brightness = brightness 60 | contrast= 0 61 | camera.contrast = contrast 62 | EV = 0 63 | saturation = 0 64 | camera.saturation = saturation 65 | camera.exposure_compensation = 0 66 | camera.sensor_mode = 2 # full field of view 67 | if HighResolution == True: 68 | camera.resolution = (3280, 2464) # Pi camera v2 max resolution, 8MP 69 | else: 70 | camera.resolution = (1920, 1080) # reduced resolution, Full HD or (2592 x 1944) for the 5MP camera v1 71 | camera.rotation=180 72 | camera.annotate_text_size = 100 73 | camera.annotate_text = "" 74 | camera.iso = 0 75 | camera.shutter_speed = 0 76 | camera.framerate = 30 77 | camera.exposure_mode = 'auto' 78 | camera.awb_mode = 'auto' 79 | #camera.preview_fullscreen = False # optional 80 | #camera.preview_window = (0, 50, 1280, 960) #optional 81 | camera.start_preview() 82 | 83 | 84 | def shortcuts(): # keyboard shortcuts 85 | camera.preview_alpha=0 86 | easygui.msgbox("F1: keyboard shortcuts\ 87 | \n----------------------------------------------------------\ 88 | \nTab: toggle between photo and video modes\ 89 | \nP: start camera preview, p: stop camera preview\ 90 | \n----------------------------------------------------------\ 91 | \nB: increase brightness, b: decrease brightness\ 92 | \nC: increase contrast, c: decrease contrast\ 93 | \nV: increase EV, v: decrease EV\ 94 | \nS: increase saturation, s: decrease saturation\ 95 | \n----------------------------------------------------------\ 96 | \n+: digital zoom in, -: digital zoom out\ 97 | \n----------------------------------------------------------\ 98 | \ni: ISO, e: exposure time, r: framerate\ 99 | \nE: exposure mode, W: white-balance mode\ 100 | \n----------------------------------------------------------\ 101 | \n0: reset camera settings\ 102 | \n----------------------------------------------------------\ 103 | \nF: select folder, f: enter filename prefix (optional)\ 104 | \n----------------------------------------------------------\ 105 | \nEnter: save image or start\stop video depending on the mode\ 106 | \n----------------------------------------------------------\ 107 | \nArrow keys: X-Y or Rotate-Tilt\ 108 | \nPage up and Page down: Z up and down\ 109 | \nHome and End: Camera up and down\ 110 | \n----------------------------------------------------------\ 111 | \nCtrl: change motor speed (3 predefined speeds)\ 112 | \nAlt: toggle between X-Y and Rotate-Tilt\ 113 | \n----------------------------------------------------------\ 114 | \nEsc: exit","Keyboard shortcuts") 115 | camera.preview_alpha=255 116 | 117 | 118 | shortcuts() #display the keyboard shortcuts at the beginning 119 | camera_reset() # start the camera preview 120 | 121 | def on_press(key): 122 | global brightness, contrast, EV, saturation, zoom 123 | global speed, LEDintensity 124 | global arrowkey_mode, path, filename, recording_mode 125 | 126 | ### F1: keyboard shortcuts 127 | 128 | if key == Key.f1: 129 | shortcuts() 130 | 131 | ######## CAMERA 132 | 133 | ### Camera preview modes, video or photo 134 | 135 | if key == Key.tab: 136 | if recording_mode == False: 137 | camera.resolution = (1920, 1080) 138 | camera.annotate_text = "Video mode" 139 | recording_mode = True 140 | elif recording_mode == True: 141 | if HighResolution == True: 142 | camera.resolution = (3280, 2464) # Pi camera v2 max resolution, 8MP 143 | else: 144 | camera.resolution = (1920, 1080) # reduced resolution, Full HD 145 | camera.annotate_text = "Photo mode" 146 | recording_mode = False 147 | 148 | ### Reset camera settings 149 | if key == KeyCode(char='0'): 150 | camera_reset() 151 | 152 | ### digital zoom 153 | 154 | if key == KeyCode(char='+'): 155 | if zoom > 0.2: 156 | zoom = zoom - 0.1 157 | camera.zoom = (0,0,zoom,zoom) 158 | annotate_text = "Digital zoom:" + str(round(1/zoom,2)) + "X" 159 | camera.annotate_text = annotate_text 160 | 161 | if key == KeyCode(char='-'): 162 | if zoom < 1.0: 163 | zoom = zoom + 0.1 164 | camera.zoom = (0,0,zoom,zoom) 165 | annotate_text = "Digital zoom:" + str(round(1/zoom,2)) + "X" 166 | camera.annotate_text = annotate_text 167 | 168 | ### start and stop camera preview 169 | 170 | if key == KeyCode(char='P'): 171 | camera.start_preview() 172 | 173 | if key == KeyCode(char='p'): 174 | camera.stop_preview() 175 | 176 | ### brightness 177 | 178 | if key == KeyCode(char='B'): 179 | if brightness !=100: 180 | brightness += 5 181 | camera.brightness = brightness 182 | annotate_text = "Brightness:" + str(brightness) + "%" 183 | camera.annotate_text = annotate_text 184 | 185 | if key == KeyCode(char='b'): 186 | if brightness !=0: 187 | brightness -= 5 188 | camera.brightness = brightness 189 | annotate_text = "Brightness:" + str(brightness) + "%" 190 | camera.annotate_text = annotate_text 191 | 192 | ### contrast 193 | 194 | if key == KeyCode(char='C'): 195 | if contrast !=100: 196 | contrast += 5 197 | camera.contrast = contrast 198 | annotate_text = "Contrast:" + str(contrast) + "%" 199 | camera.annotate_text = annotate_text 200 | if key == KeyCode(char='c'): 201 | if contrast !=-100: 202 | contrast -= 5 203 | camera.contrast = contrast 204 | annotate_text = "Contrast:" + str(contrast) + "%" 205 | camera.annotate_text = annotate_text 206 | 207 | ### EV compensation 208 | 209 | if key == KeyCode(char='V'): 210 | if EV !=25: 211 | EV += 1 212 | camera.exposure_compensation = EV 213 | annotate_text = "EV:" + str(EV) 214 | camera.annotate_text = annotate_text 215 | if key == KeyCode(char='v'): 216 | if EV !=-25: 217 | EV -= 1 218 | camera.exposure_compensation = EV 219 | annotate_text = "EV:" + str(EV) 220 | camera.annotate_text = annotate_text 221 | 222 | ### saturation 223 | 224 | if key == KeyCode(char='S'): 225 | if saturation !=100: 226 | saturation += 5 227 | camera.saturation = saturation 228 | annotate_text = "Saturation:" + str(saturation) + "%" 229 | camera.annotate_text = annotate_text 230 | if key == KeyCode(char='s'): 231 | if saturation !=-100: 232 | saturation -= 5 233 | camera.saturation = saturation 234 | annotate_text = "Saturation:" + str(saturation) + "%" 235 | camera.annotate_text = annotate_text 236 | 237 | ### ISO 238 | 239 | if key == KeyCode(char='i'): 240 | camera.preview_alpha=0 241 | ISO_choices = ["0","100","200","320","400","500","640","800"] 242 | ISO_selected = easygui.choicebox("Select ISO (default: 0 for auto)", "ISO", ISO_choices) 243 | if ISO_selected is not None: 244 | camera.iso = int(ISO_selected) 245 | camera.preview_alpha=255 246 | 247 | ### white balance 248 | 249 | if key == KeyCode(char='W'): 250 | camera.preview_alpha=0 251 | WB_choices = ["off","auto","sunlight","cloudy","shade","tungsten","fluorescent","flash","horizon"] 252 | WB_selected = easygui.choicebox("Select white balance mode (default: auto)", "White Balance", WB_choices) 253 | if WB_selected is not None: 254 | camera.awb_mode = WB_selected 255 | if WB_selected == "off": 256 | WB_gain = easygui.enterbox("White balance gain - typical values between 0.9 and 1.9", "White balance gain", "1.0") 257 | if WB_gain is not None: 258 | camera.awb_gains = float(WB_gain) 259 | camera.preview_alpha=255 260 | 261 | ### frame rate 262 | 263 | if key == KeyCode(char='r'): 264 | camera.preview_alpha=0 265 | Framerate_choices = ["30","20", "15","10","5","2","1", "0.5", "0.1", "manual"] 266 | Framerate_selected = easygui.choicebox("Select framerate","Framerate", Framerate_choices) 267 | if Framerate_selected == "manual": 268 | Framerate_selected = easygui.enterbox("Framerate", "Input", "") 269 | if Framerate_selected is not None: 270 | camera.framerate = float(Framerate_selected) 271 | camera.preview_alpha=255 272 | 273 | ### exposure time (shutter speed) 274 | 275 | if key == KeyCode(char='e'): 276 | camera.preview_alpha=0 277 | Exposure_choices = ["0","10","20","50","100","200", "500", "1000", "manual"] 278 | Exposure_selected = easygui.choicebox("Select exposure time in ms (default: 0, auto)\n\ 279 | Maximum exposure time is determined by the framerate","Exposure time (shutter speed)", Exposure_choices) 280 | if Exposure_selected == "manual": 281 | Exposure_selected = easygui.enterbox("Exposure time (ms)", "Input", "") 282 | if Exposure_selected is not None: 283 | camera.shutter_speed = int(Exposure_selected)*1000 284 | camera.preview_alpha=255 285 | 286 | ### exposure modes 287 | 288 | if key == KeyCode(char='E'): 289 | camera.preview_alpha=0 290 | ExposureMode_choices = ["off","auto","night","nightpreview","backlight","spotlight"] 291 | ExposureMode_selected = easygui.choicebox("Exposure mode", "Exposure mode", ExposureMode_choices) 292 | if ExposureMode_selected is not None: 293 | camera.exposure_mode = ExposureMode_selected 294 | camera.preview_alpha=255 295 | 296 | ### select folder and set filename prefix 297 | 298 | if key == KeyCode(char='F'): 299 | camera.preview_alpha=0 300 | path = easygui.diropenbox() 301 | camera.preview_alpha=255 302 | if key == KeyCode(char='f'): 303 | camera.preview_alpha=0 304 | filename = easygui.enterbox("Filename prefix", "Input", "") 305 | camera.preview_alpha=255 306 | 307 | ######## MOTORIZED STAGE 308 | 309 | ### Ctrl: change motor speed 310 | 311 | if key == Key.ctrl_l or key == Key.ctrl_r: 312 | if speed == slow: 313 | speed = medium 314 | camera.annotate_text = "Motor speed = medium" 315 | elif speed == medium: 316 | speed = fast 317 | camera.annotate_text = "Motor speed = fast" 318 | elif speed == fast: 319 | speed = slow 320 | camera.annotate_text = "Motor speed = slow" 321 | 322 | ### keyboard arrow keys, Alt key toggles between X-Y and Rotate-Tilt 323 | ### send Arduino a string to control the motors, e.g. "-100X," or "500Y," 324 | ### the integer defines the direction and the speed of the motor, the letter selects the motor 325 | 326 | if key == Key.alt_l or key == Key.alt_r: 327 | if arrowkey_mode == True: 328 | arrowkey_mode = False 329 | camera.annotate_text = "X-Y" 330 | elif arrowkey_mode == False: 331 | arrowkey_mode = True 332 | camera.annotate_text = "Rotate-Tilt" 333 | 334 | if not KeyboardControl: 335 | return 336 | 337 | if key == Key.left: 338 | if arrowkey_mode == False: 339 | data = str(speed*-1) + "X," 340 | else: 341 | data = str(speed*-1) + "R," 342 | ser.write(data.encode()) 343 | 344 | if key == Key.right: 345 | if arrowkey_mode == False: 346 | data = str(speed) + "X," 347 | else: 348 | data = str(speed) + "R," 349 | ser.write(data.encode()) 350 | 351 | if key == Key.down: 352 | if arrowkey_mode == False: 353 | data = str(speed*-1) + "Y," 354 | else: 355 | data = str(speed*-1) + "T," 356 | ser.write(data.encode()) 357 | 358 | if key == Key.up: 359 | if arrowkey_mode == False: 360 | data = str(speed) + "Y," 361 | else: 362 | data = str(speed) + "T," 363 | ser.write(data.encode()) 364 | 365 | ### Z up and down, Page up and Page down 366 | 367 | if key == Key.page_up: 368 | data = str(speed) + "Z," 369 | ser.write(data.encode()) 370 | 371 | if key == Key.page_down: 372 | data = str(speed*-1) + "Z," 373 | ser.write(data.encode()) 374 | 375 | ### Camera up and down, Home and End 376 | 377 | if key == Key.home: 378 | data = str(speed) + "C," 379 | ser.write(data.encode()) 380 | 381 | if key == Key.end: 382 | data = str(speed*-1) + "C," 383 | ser.write(data.encode()) 384 | 385 | ######## LED 386 | 387 | ### LED intensity, max. value in the data is set to 20 to make it compatible with the joystick 388 | ### PWM of the LED driver works in the opposite direction 389 | ### Intensity is controlled in 5% increments 390 | 391 | if key == KeyCode(char='L'): 392 | if LEDintensity !=20: 393 | LEDintensity += 1 394 | data = str(20-LEDintensity) + "L," 395 | ser.write(data.encode()) 396 | annotate_text = "LED intensity:" + str(LEDintensity*5) + "%" 397 | camera.annotate_text = annotate_text 398 | if key == KeyCode(char='l'): 399 | if LEDintensity !=0: 400 | LEDintensity -= 1 401 | data = str(20-LEDintensity) + "L," 402 | ser.write(data.encode()) 403 | annotate_text = "LED intensity:" + str(LEDintensity*5) + "%" 404 | camera.annotate_text = annotate_text 405 | 406 | 407 | def on_release(key): 408 | global path, filename, recording 409 | 410 | ### Esc: exit 411 | 412 | if key == Key.esc: 413 | sys.exit() 414 | 415 | ### Enter: save image 416 | 417 | if key == Key.enter: 418 | camera.annotate_text = "" 419 | now = datetime.now() 420 | if recording_mode == False: #photo mode 421 | now = datetime.now() 422 | output = path+"/"+ filename+now.strftime("%d-%m-%H%M%S.jpg") 423 | time.sleep(1) 424 | camera.capture(output, quality=100) 425 | camera.annotate_text = "Photo saved" 426 | if recording_mode == True: #video mode 427 | if recording == False: 428 | output = path+"/"+ filename+now.strftime("%d-%m-%H%M%S.h264") 429 | camera.annotate_text = "Recording will start in 2 seconds..." 430 | time.sleep(2) 431 | camera.annotate_text = "" 432 | camera.start_recording(output) 433 | recording = True 434 | elif recording == True: 435 | camera.stop_recording() 436 | camera.annotate_text = "Recording stopped" 437 | recording = False 438 | 439 | ### Stop motors when key is released 440 | 441 | if KeyboardControl and (key == Key.up or key == Key.down or key == Key.left or key == Key.right\ 442 | or key == Key.page_up or key == Key.page_down or key == Key.home\ 443 | or key == Key.end): 444 | ser.write(b"0O,") ## send to Arduino "0O," to stop the motors 445 | 446 | with Listener(on_press=on_press,on_release=on_release) as listener: 447 | listener.join() 448 | 449 | 450 | 451 | 452 | 453 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An open-source MICROSCOPE built using LEGO bricks, 3D-printing, Arduino and Raspberry Pi 2 | 3 | ![Animation](docs/Animation.gif) 4 | 5 | ![Images](docs/Images.jpg) 6 | 7 | ![Stop-motion](docs/Stop-motion.gif) 8 | 9 | ## Key Features 10 | 11 | - **Fully motorized:** Camera angle, sample position, magnification and focus can be adjusted precisely using six stepper motors. 12 | - **Modular:** Stages and modules can be arranged in any configuration depending on the application. 13 | - **Versatile:** Uniform illumination guarantees high quality imaging for a variety of samples with reflective or transparent surfaces. 14 | - **Wide magnification range:** Samples with features from several centimeters to several micrometers can be imaged without changing the objective lens. 15 | - **Low-cost:** The whole assembly costs from $200 to $400 depending on the features and the vendors of the electronic components. 16 | - The microscope uses a **Raspberry Pi** mini-computer with an **8MP camera** to capture images and videos. Stepper motors and the illumination are controlled using a circuit board comprising an **Arduino microcontroller**, six **stepper motor drivers** and a **high-power LED driver**. All functions can be controlled from a keyboard connected to the Raspberry Pi or a separate custom-built Arduino joystick connected to the mainboard. **LEGO bricks** are used to construct the main body of the microscope to achieve a modular and easy-to-assemble design concept. 17 | 18 | ![Diagram](docs/Diagram.jpg) 19 | 20 | 21 | 22 | ## Assembly instructions 23 | 24 | | [Video (YouTube)](https://youtu.be/PBSYnk9T4o4) | [Instructions (PDF)](Instructions.pdf) | 25 | | :----------------------------------------------------------: | :--------------------------------------------: | 26 | | [![YouTube](docs/YouTube.jpg)](https://youtu.be/PBSYnk9T4o4) | [![](docs/Instructions.jpg)](Instructions.pdf) | 27 | | **[Circuit assembly (PDF)](/Circuit/CircuitAssembly.pdf)** | **[Raspberry Pi HQ camera test](/HQ-camera)** | 28 | | [![Circuit](docs/Circuit.jpg)](/Circuit/CircuitAssembly.pdf) | [![HQ-camera](docs/HQ-camera.jpg)](/HQ-camera) | 29 | 30 | 31 | 32 | ## Operation principle 33 | 34 | The microscope has a simple operation principle based on changing the magnification and the focus by adjusting the relative distances between a **camera**, a single **objective lens** and a **sample**. Briefly, two linear stages with stepper motors are used to adjust these distances for a continuous and wide magnification range. Four additional stepper motors **tilt** the camera module and change the **X-Y position** and **rotation** of the sample. A **uniform light source** illuminates the sample either from an angle *(reflected light)* or from the bottom of the sample *(transmitted light)*. The system can also be used as a *digital water contact angle goniometer* by taking cross-section images of droplets. 35 | 36 | ![Operation](docs/Operation.jpg) 37 | 38 | 39 | 40 | ## Assembly steps 41 | 42 | - [Enjoy the YouTube video and get a general idea about the assembly of the microscope.](https://youtu.be/PBSYnk9T4o4) 43 | 44 | - [Follow the detailed instructions provided in the PDF document.](Instructions.pdf) 45 | 46 | - [3D print the design files.](#3D-printing) 47 | 48 | - [Build the electronic board.](#Electronics) 49 | 50 | - [Upload the Arduino code.](#Uploading-the-Arduino-code) 51 | 52 | - [Assemble the microscope.](#Final-assembly) 53 | 54 | - [Run the Python code.](#Python-Raspberry-Pi) 55 | 56 | - [Try different operation modes and enjoy the microscope.](#Basic-operation-modes) 57 | 58 | - [Check these instructions if you would like to test the new hiqh quality (HQ) camera](/HQ-camera) 59 | 60 | 61 | 62 | ## 3D printing 63 | 64 | I assembled the main body of the microscope using individually-purchased [LEGO bricks](https://www.lego.com/en-us/page/static/pick-a-brick). Instead of using motors and gears from LEGO Technic, I designed custom actuators using [FreeCAD](https://www.freecadweb.org/) software and printed them using my personal [3D printer](https://www.creality3d.shop/products/creality3d-ender-3-pro-high-precision-3d-printer). This approach not only lowered the cost of the microscope but also gave me some flexibility in the design and implementation of precise linear and rotary actuators. In principle, the whole structure could be 3D-printed without using any LEGO parts but that would be less modular and more time consuming. 65 | 66 | All STL files for 3D printing and the original FreeCAD files for editing are available in this [folder](https://github.com/IBM/MicroscoPy/tree/master/3D-design-files). You may need to install [FreeCAD Gear workbench](https://wiki.freecadweb.org/Macro_FCGear) to edit the gears. 67 | 68 | :warning: A good quality printing depends on many factors. I optimized the designs after several iterations of printing. If the parts do not match well, some minor modification in the original design file (e.g. enlarging the holes matching to LEGO studs) or polishing/drilling may be required. More information on the printer and slicer settings is given in the [PDF document](Instructions.pdf) (page #72). 69 | 70 | ![3Dprinting](docs/3Dprinting.jpg) 71 | 72 | 73 | 74 | ## Electronics 75 | 76 | :warning: **This part requires some basic knowledge on electronic circuit design and Arduino.** 77 | 78 | All design and Gerber files of printed circuit boards (PCBs) are available in this [folder](/Circuit). I designed the PCBs using [DesignSpark PCB software](https://www.rs-online.com/designspark/pcb-software). The list of all components can be found in the [PDF document](Instructions.pdf) (page #71) 79 | 80 | There is also a separate [instruction manual](/Circuit/CircuitAssembly.pdf) giving more details about the assembly of the circuit boards. 81 | 82 | The operation of the electronics is straight-forward. I used two Arduino microcontrollers, one for the *mainboard* (essential) and one for the joystick *controller* (optional), to control the stepper motors and the LED illumination of the microscope. In principle, everything could be controlled directly from the Raspberry Pi without using any Arduino microcontroller but I decided to leave testing this option for another time. 83 | 84 | :bulb: The joystick controller is optional because the stepper motors and the LED can also be controlled from a keyboard connected to the Raspberry Pi. But having a joystick is fun! 85 | 86 | ![Electronics](docs/Electronics.jpg) 87 | 88 | 89 | 90 | I preferred [Adafruit ItsyBitsy 32u4 5V](https://learn.adafruit.com/introducting-itsy-bitsy-32u4) but any Arduino board with enough number of I/O pins should work. If you want to use the joystick controller, the Arduino boards should support serial (UART) communication because they cannot communicate with each other via their USB ports. 91 | 92 | The Arduino on the *controller* circuit reads the status of three [thumb joysticks](https://www.sparkfun.com/products/9032) (for six stepper motors) and a potentiometer (for the LED intensity) via its analog inputs. The data is sent to the Arduino on the *mainboard* via UART (RX, TX) serial communication. An OLED display on the *controller* can be used to display useful information, like the PWM (pulse-width modulation) intensity of the LED. The Arduino on the *mainboard* receives and processes the incoming data and sends stepper motor signals to the corresponding stepper motor via its respective [motor driver](https://www.adafruit.com/product/3297). Six motor drivers share the same signal pins but only one driver is activated at a time using the *enable* *(EN)* or the *sleep* *(SLP)* pin of the driver. This implementation requires only 10 I/O pins *(4 signal + 6 enable)* instead of 24 *(4 signal x 6 motors)*. It also prevents the heating of the motors when they are idle and limits the current consumption by allowing only one motor running at a time. Alternatively, a motor driver with a built-in indexer with STEP/DIRECTION control can be used to reduce the number of I/O pins but such drivers are more expensive. The data is sent only if there is a change in the joystick position is detected to avoid continuous communication. 93 | 94 | The *mainboard* can be powered from an external 5V wall charger or directly from the USB port of the Raspberry Pi. The latter also allows USB communication between the *mainboard* and the Raspberry Pi to control the stepper motors and the LED intensity from a keyboard. The intensity of the LED is controlled by PWM using a dedicated pin on the Arduino. I recommend keeping the LED off while the Raspberry Pi is booting and then gradually increasing the intensity. The [LED driver](https://www.digikey.com/products/en?keywords=945-1818-5-ND) and the [high-power LED](https://www.digikey.com/products/en?keywords=1672-1161-ND) used in this project required more than 6V for a rated operation. I initially used an external 12V power adapter for the LED but later generated 12V directly from the USB port of the Raspberry Pi using a step-up [DC-DC converter](https://www.banggood.com/DC-DC-Boost-Buck-Adjustable-Step-Up-Step-Down-Automatic-Converter-XL6009-Module-p-1087346.html?rmmds=search&cur_warehouse=CN). In this case, the 5V power adapter should be able to supply enough current for the Raspberry Pi, stepper motors and the LED. I tested the system using a single 5V / 3A supply (original Raspberry Pi 4 power supply or a wall charger). Different configurations may require different power ratings. For example, if the display is also powered from the same source, then a more powerful 5V source would be needed. [Here](https://www.digikey.com/products/en?keywords=709-GST60A05-P1J) is a good one. 95 | 96 | :warning: The high-power LED gets hot after some time, be cautious. I used a standard aluminum [heat sink](https://www.adafruit.com/product/3083) to help cooling a little bit. 97 | 98 | 99 | 100 | ## Uploading the Arduino code 101 | 102 | - Download the latest version of the [Arduino IDE](https://www.arduino.cc/en/main/software) 103 | - Install the libraries (Sketch → Include Library → Manage Libraries): 104 | - [AccelStepper](http://www.airspayce.com/mikem/arduino/AccelStepper/): used to control the stepper motors (mainboard). 105 | - (Optional) [Adafruit NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel): if you want to have a status LED or nice color effects in the mainboard. 106 | - (Optional) [Adafruit_GFX](https://github.com/adafruit/Adafruit-GFX-Library) and [Adafruit_SSD1306](https://github.com/adafruit/Adafruit_SSD1306): if you want to have an OLED display in the joystick to show the intensity of the LED illumination. 107 | - Add the board by following these [instructions](https://learn.adafruit.com/introducting-itsy-bitsy-32u4/arduino-ide-setup). 108 | - Upload the [code(s)](/Arduino). 109 | 110 | ![Arduino](docs/Arduino.jpg) 111 | 112 | 113 | 114 | ## Final assembly 115 | 116 | Follow the detailed instructions given in the [PDF document](Instructions.pdf). Briefly: 117 | 118 | - It is important to fix the [32x32 LEGO baseplate](https://www.lego.com/en-us/product/white-baseplate-11010) to a rigid table or support for a good mechanical stability. Rubber dampers or an air cushion can be used to minimize vibrations. 119 | - Unfortunately it is not possible to create a public shopping basket in the [LEGO shop](https://www.lego.com/en-us/page/static/pick-a-brick), all parts need to be added one by one. I listed the LEGO parts in [this document](List-of-Lego-bricks.pdf) but I recommend buying extra bricks and plates in case you need to change something. 120 | - Some LEGO parts are needed to be glued permanently for better stability. I provided recommendations in the document based on my experience. In general, it is a good idea to glue a smaller LEGO piece (e.g. 2x2) to a larger one (e.g. 2x4) to have a stronger interlocking while preserving the advantage of LEGO bricks for modularity. 121 | - After assembling the microscope and connecting all the cables, boot the Raspberry Pi. 122 | - :warning: Do not forget to enable the camera from the Raspberry Pi configuration (Preferences → Raspberry Pi configuration → Interfaces → Camera Enabled → Reboot) as explained [here](https://projects.raspberrypi.org/en/projects/getting-started-with-picamera). 123 | 124 | ![Assembly](docs/Assembly.gif) 125 | 126 | 127 | 128 | ## Python-Raspberry Pi 129 | 130 | I wrote a simple [program](/Python) in Python 3 to control the microscope, modify camera settings and take photos and videos from keyboard. The code allows changing almost all camera settings using keyboard shortcuts. The speed and the direction of the stepper motors and the LED intensity can also be controlled from the keyboard independently from the joystick controller. 131 | 132 | The code requires following dependencies: 133 | 134 | - [EasyGUI](https://pythonhosted.org/easygui/index.html): to generate simple message boxes and select the folder where the images are saved 135 | - [pySerial](https://pythonhosted.org/pyserial/): to communicate with the Arduino via USB 136 | - [pynput](https://pypi.org/project/pynput/): to monitor keyboard inputs 137 | 138 | There are two important parameters in the program to be changed according to your configuration: 139 | 140 | **HighResolution:** 141 | 142 | - **True** for (3280x2464), tested with Raspberry Pi 4. 143 | - **False** for (1920x1080), tested with Raspberry Pi 3 and Zero. 144 | 145 | **KeyboardControl:** 146 | 147 | - **True** if the Arduino mainboard is connected to the Raspberry Pi via USB. 148 | - **False** if the mainboard is not connected and powered from an external 5V supply 149 | 150 | **Finally, launch the program from the terminal and enjoy the microscope:** 151 | 152 | ```bash 153 | python3 /home/pi/MicroscoPy.py 154 | ``` 155 | 156 | ![Program](docs/Program.jpg) 157 | 158 | 159 | 160 | ## Basic operation modes 161 | 162 | ![Modes](docs/Modes.jpg) 163 | 164 | 165 | 166 | ## Media coverage 167 | 168 | [IEEE Spectrum](https://spectrum.ieee.org/geek-life/hands-on/build-a-sophisticated-microscope-using-lego-3d-printing-arduinos-and-a-raspberry-pi): "Build A Sophisticated Microscope Using Lego, 3D Printing, Arduinos, and a Raspberry Pi" 169 | 170 | [IBM - Medium article](https://medium.com/@IBMResearch/ibm-open-sources-300-fully-functional-lego-microscope-design-248a6cdc81bf): "IBM open sources $300 fully-functional LEGO®microscope design" 171 | 172 | [Futurism](https://futurism.com/engineer-published-scientific-papers-lego-microscope): "This Engineer Published Scientific Papers Using a Lego Microscope" 173 | 174 | 175 | 176 | ## License 177 | 178 | This project uses the [Apache License Version 2.0](LICENSE) software license. 179 | 180 | Separate third-party libraries used in Arduino and Python codes are licensed by their respective providers pursuant to their own separate licenses. 181 | 182 | 183 | 184 | ## About 185 | 186 | **The microscope is not currently available for purchase in kit or built form.** 187 | 188 | Copyright 2021 [Yuksel Temiz](https://www.linkedin.com/in/yukseltemiz/) 189 | 190 | -------------------------------------------------------------------------------- /docs/3Dprinting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/3Dprinting.jpg -------------------------------------------------------------------------------- /docs/Animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Animation.gif -------------------------------------------------------------------------------- /docs/Arduino.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Arduino.jpg -------------------------------------------------------------------------------- /docs/Assembly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Assembly.gif -------------------------------------------------------------------------------- /docs/Circuit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Circuit.jpg -------------------------------------------------------------------------------- /docs/Diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Diagram.jpg -------------------------------------------------------------------------------- /docs/Electronics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Electronics.jpg -------------------------------------------------------------------------------- /docs/HQ-camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/HQ-camera.jpg -------------------------------------------------------------------------------- /docs/Images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Images.jpg -------------------------------------------------------------------------------- /docs/Instructions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Instructions.jpg -------------------------------------------------------------------------------- /docs/Modes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Modes.jpg -------------------------------------------------------------------------------- /docs/Operation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Operation.jpg -------------------------------------------------------------------------------- /docs/Program.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Program.jpg -------------------------------------------------------------------------------- /docs/Stop-motion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/Stop-motion.gif -------------------------------------------------------------------------------- /docs/YouTube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/MicroscoPy/afc20d1d74053fe1d923274ad4d8b0caf470d1ef/docs/YouTube.jpg --------------------------------------------------------------------------------