class Experimenter(threading.Thread): def __init__(self, hw=None, cam=None): self.hw = hw self.cam = cam self.cfg = Config() self.delay = 60 self.duration = 7 self.dir = os.path.expanduser('~') self.starttime = 0 self.endtime = 0 self.running = False self.status = "Stopped" self.daytime = "TBD" self.quit = False self.stop_experiment = False self.status_change = threading.Event() self.next_status = '' self.last_captured = None self.nshots = 0 self.idlepos = 0 threading.Thread.__init__(self) def stop(self): self.status = "Stopping" self.next_status = '' self.stop_experiment = True log("Stopping running experiment...") def isDaytime(self): oldres = self.cam.resolution self.cam.resolution = (320, 240) self.cam.iso = self.cfg.get('dayiso') self.cam.shutter_speed = 1000000 // self.cfg.get('dayshutter') output = np.empty((240, 320, 3), dtype=np.uint8) self.cam.capture(output, 'rgb') self.cam.resolution = oldres debug("Daytime estimation mean value: " + str(output.mean())) return output.mean() > 10 def setWB(self): debug("Determining white balance.") self.cam.awb_mode = "auto" time.sleep(2) g = self.cam.awb_gains self.cam.awb_mode = "off" self.cam.awb_gains = g def takePicture(self, name): filename = "" prev_daytime = self.daytime self.daytime = self.isDaytime() # always turn on led self.hw.LEDControl(True) if self.daytime: time.sleep(0.5) self.cam.shutter_speed = 1000000 // self.cfg.get('dayshutter') self.cam.iso = self.cfg.get('dayiso') self.cam.color_effects = None filename = os.path.join(self.dir, name + "-day.png") else: time.sleep(0.5) self.cam.shutter_speed = 1000000 // self.cfg.get('nightshutter') self.cam.iso = self.cfg.get('nightiso') #self.cam.color_effects = (128, 128) filename = os.path.join(self.dir, name + "-night.png") if prev_daytime != self.daytime and self.daytime and self.cam.awb_mode != "off": # if there is a daytime shift, AND it is daytime, AND white balance was not previously set, # set the white balance to a fixed value. # thus, white balance will only be fixed for the first occurence of daylight. self.setWB() debug("Capturing %s." % filename) self.cam.exposure_mode = "off" self.cam.capture(filename) self.last_captured = filename self.cam.color_effects = None self.cam.shutter_speed = 0 # we leave the cam in auto exposure mode to improve daytime assessment performance self.cam.exposure_mode = "auto" # turn off led self.hw.LEDControl(False) def run(self): while not self.quit: self.status_change.wait() if self.next_status == 'run': self.next_status = '' self.status_change.clear() self.runExperiment() def go(self): self.next_status = 'run' self.status_change.set() def runExperiment(self): if self.running: raise RuntimeError('An experiment is already running.') try: debug("Starting experiment.") self.running = True self.status = "Initiating" self.starttime = time.time() self.endtime = time.time() + 60 * 60 * 24 * self.duration self.last_captured = None self.delay = self.delay or 0.001 self.nshots = self.duration * 24 * 60 // self.delay self.cam.exposure_mode = "auto" self.cam.shutter_speed = 0 self.hw.LEDControl(False) for i in range(4): platedir = "plate" + str(i + 1) os.makedirs(os.path.join(self.dir, platedir), exist_ok=True) while time.time() < self.endtime and not self.stop_experiment: loopstart = time.time() nextloop = time.time() + 60 * self.delay if nextloop > self.endtime: nextloop = self.endtime for i in range(4): # rotate stage to starting position if i == 0: self.hw.motorOn(True) self.status = "Finding start position" debug("Finding initial position.") self.hw.findStart( calibration=self.cfg.get('calibration')) debug("Found initial position.") else: self.status = "Imaging" # rotate cube 90 degrees debug("Rotating stage.") self.hw.halfStep(100, 0.03) # wait for the cube to stabilize time.sleep(0.5) now = time.strftime("%Y%m%d-%H%M%S", time.localtime()) name = os.path.join("plate" + str(i + 1), "plate" + str(i + 1) + "-" + now) self.takePicture(name) self.nshots -= 1 self.hw.motorOn(False) self.status = "Waiting" if self.idlepos > 0: # alternate between resting positions during idle, stepping 45 degrees per image self.hw.motorOn(True) self.hw.halfStep(50 * self.idlepos, 0.03) self.hw.motorOn(False) self.idlepos += 1 if self.idlepos > 7: self.idlepos = 0 while time.time() < nextloop and not self.stop_experiment: # keep rotating while waiting, similar light conditions for each plate self.hw.motorOn(True) self.hw.halfStep(21, 0.03) self.hw.motorOn(False) time.sleep(60) finally: log("Experiment stopped.") self.cam.color_effects = None self.status = "Stopped" self.stop_experiment = False self.running = False self.cam.exposure_mode = "auto" self.cam.meter_mode = 'spot'
class HWControl: def __init__(self): gpio.setmode(gpio.BCM) self.cfg = Config() self.pins = { 'LED': self.cfg.get('LED'), 'sensor': self.cfg.get('sensor'), 'PWMa': self.cfg.get('PWMa'), 'PWMb': self.cfg.get('PWMb'), 'coilpin_M11': self.cfg.get('coilpin_M11'), 'coilpin_M12': self.cfg.get('coilpin_M12'), 'coilpin_M21': self.cfg.get('coilpin_M21'), 'coilpin_M22': self.cfg.get('coilpin_M22'), 'stdby': self.cfg.get('stdby') } self.led = False self.GPIOInit() def GPIOInit(self): gpio.setwarnings(False) gpio.setup(self.pins['LED'], gpio.OUT) gpio.setup(self.pins['sensor'], gpio.IN, pull_up_down=gpio.PUD_DOWN) gpio.setup(self.pins['PWMa'], gpio.OUT) gpio.setup(self.pins['PWMb'], gpio.OUT) gpio.setup(self.pins['coilpin_M11'], gpio.OUT) gpio.setup(self.pins['coilpin_M12'], gpio.OUT) gpio.setup(self.pins['coilpin_M21'], gpio.OUT) gpio.setup(self.pins['coilpin_M22'], gpio.OUT) gpio.setup(self.pins['stdby'], gpio.OUT) gpio.output(self.pins['PWMa'], True) gpio.output(self.pins['PWMb'], True) self.LEDControl(False) self.motorOn(False) def cleanup(self): gpio.cleanup() def findStart(self, calibration=None): """rotates the imaging stage until the positional switch is activated""" calibration = calibration or self.cfg.get('calibration') timeout = 60 starttime = time.time() # make sure that switch is not depressed when starting if gpio.input(self.pins['sensor']): while gpio.input( self.pins['sensor']) and time.time() < starttime + timeout: self.halfStep(1, 0.03) while not gpio.input( self.pins['sensor']) and time.time() < starttime + timeout: self.halfStep(1, 0.03) if time.time() < starttime + timeout: self.halfStep(calibration, 0.03) else: log("Timed out while finding start position! Images will be misaligned." ) # sets the motor pins as element in sequence def setStepper(self, M_seq, i): gpio.output(self.pins['coilpin_M11'], M_seq[i][0]) gpio.output(self.pins['coilpin_M12'], M_seq[i][1]) gpio.output(self.pins['coilpin_M21'], M_seq[i][2]) gpio.output(self.pins['coilpin_M22'], M_seq[i][3]) # steps the stepper motor using half steps, "delay" is time between coil change # 400 steps is 360 degrees def halfStep(self, steps, delay, keep_motor_on=False): time.sleep(0.005) # time for motor to activate for i in range(0, steps): self.setStepper(self.halfstep_seq, self.seqNumb) self.seqNumb += 1 if (self.seqNumb == 8): self.seqNumb = 0 time.sleep(delay) # sets motor standby status def motorOn(self, value): gpio.output(self.pins['stdby'], value) # turns on and off led def LEDControl(self, value): gpio.output(self.pins['LED'], value) self.led = value # focuses the ArduCam motorized focus camera # code is from ArduCam GitHub repo def focusCam(self, val): value = (val << 4) & 0x3ff0 data1 = (value >> 8) & 0x3f data2 = value & 0xf0 if os.path.exists('/dev/i2c-0'): os.system("i2cset -y 0 0x0c %d %d" % (data1, data2)) if os.path.exists('/dev/i2c-1'): os.system("i2cset -y 1 0x0c %d %d" % (data1, data2)) # my copy of the pinout pins = {} # state of stepper motor sequence seqNumb = 0 # sequence for one coil rotation of stepper motor using half step halfstep_seq = [(1, 0, 0, 0), (1, 0, 1, 0), (0, 0, 1, 0), (0, 1, 1, 0), (0, 1, 0, 0), (0, 1, 0, 1), (0, 0, 0, 1), (1, 0, 0, 1)]
class Experimenter(threading.Thread): def __init__(self, hw=None, cam=None): self.hw = hw self.cam = cam self.cfg = Config() self.delay = 60 self.duration = 7 self.dir = os.path.expanduser('~') self.starttime = 0 self.endtime = 0 self.running = False self.status = "Stopped" self.daytime = "TBD" self.quit = False self.stop_experiment = False self.status_change = threading.Event() self.next_status = '' self.last_captured = None self.nshots = 0 threading.Thread.__init__(self) def stop(self): self.status = "Stopping" self.next_status = '' self.stop_experiment = True log("Stopping running experiment...") def isDaytime(self): # determine if it's day or not. # we need to set a bogus shutter speed before switching to auto exposure mode # otherwise the camera can get stuck for some reason self.cam.shutter_speed = 10000 self.cam.exposure_mode = "auto" self.cam.shutter_speed = 0 self.cam.iso = 100 self.cam.meter_mode = 'matrix' # to determine day/night we use a rotating list of the last 5 exposure readings # exposure may take a very long time to settle, so we use some heuristics to determine whether it is day or night itsday = 'TBD' exps = deque([-1, 100, -1, 100, -1]) stuck = 0 while itsday == 'TBD': time.sleep(1) exps.popleft() # round the exposure to nearest 1000 exps.append(self.cam.exposure_speed // 1000) if exps[4] < exps[3] < exps[2] < exps[1] < exps[0]: if mean(exps) < 33: # decreasing exposure, value below 33000 itsday = True elif exps[4] > exps[3] > exps[2] > exps[1] > exps[0]: if mean(exps) > 33: # increasing exposure time, value above 33999 itsday = False elif exps[4] == exps[3] == exps[2] == exps[1] == exps[0]: if mean(exps) < 33: # stable exposure, value below 33000 itsday = True elif mean(exps) > 33: # stable exposure, value above 33999 itsday = False else: # we are stuck at around 1/30 for some reason stuck = stuck + 1 if stuck > 20: # we have been stuck for over 20 seconds, just call it night so we can move on itsday = False debug("[isDaytime] Daytime: " + str(itsday) + ". Exposure values: " + str(exps)) return itsday def setWB(self): debug("Determining white balance.") self.cam.awb_mode = "auto" time.sleep(2) g = self.cam.awb_gains self.cam.awb_mode = "off" self.cam.awb_gains = g def takePicture(self, name): filename = "" prev_daytime = self.daytime self.daytime = self.isDaytime() if self.daytime: time.sleep(0.5) self.cam.shutter_speed = 1000000 // self.cfg.get('dayshutter') self.cam.iso = self.cfg.get('dayiso') self.cam.color_effects = None filename = os.path.join(self.dir, name + "-day.jpg") else: # turn on led self.hw.LEDControl(True) time.sleep(0.5) self.cam.shutter_speed = 1000000 // self.cfg.get('nightshutter') self.cam.iso = self.cfg.get('nightiso') self.cam.color_effects = (128, 128) filename = os.path.join(self.dir, name + "-night.jpg") if prev_daytime != self.daytime and self.daytime and self.cam.awb_mode != "off": # if there is a daytime shift, AND it is daytime, AND white balance was not previously set, # set the white balance to a fixed value. # thus, white balance will only be fixed for the first occurence of daylight. self.setWB() debug("Capturing %s." % filename) self.cam.exposure_mode = "off" self.cam.capture(filename) self.last_captured = filename self.cam.color_effects = None self.cam.shutter_speed = 0 # we leave the cam in auto exposure mode to improve daytime assessment performance self.cam.exposure_mode = "auto" if not self.daytime: # turn off led self.hw.LEDControl(False) def run(self): while not self.quit: self.status_change.wait() if self.next_status == 'run': self.next_status = '' self.status_change.clear() self.runExperiment() def go(self): self.next_status = 'run' self.status_change.set() def runExperiment(self): if self.running: raise RuntimeError('An experiment is already running.') try: debug("Starting experiment.") self.running = True self.status = "Initiating" self.starttime = time.time() self.endtime = time.time() + 60 * 60 * 24 * self.duration self.last_captured = None self.delay = self.delay or 0.001 self.nshots = self.duration * 24 * 60 // self.delay self.cam.exposure_mode = "auto" self.cam.shutter_speed = 0 self.hw.LEDControl(False) for i in range(4): platedir = "plate" + str(i + 1) os.makedirs(os.path.join(self.dir, platedir), exist_ok=True) while time.time() < self.endtime and not self.stop_experiment: loopstart = time.time() nextloop = time.time() + 60 * self.delay if nextloop > self.endtime: nextloop = self.endtime for i in range(4): # rotate stage to starting position if i == 0: self.hw.motorOn(True) self.status = "Finding start position" debug("Finding initial position.") self.hw.findStart( calibration=self.cfg.get('calibration')) debug("Found initial position.") else: self.status = "Imaging" # rotate cube 90 degrees debug("Rotating stage.") self.hw.halfStep(100, 0.03) # wait for the cube to stabilize time.sleep(0.5) now = time.strftime("%Y%m%d-%H%M%S", time.localtime()) name = os.path.join("plate" + str(i + 1), "plate" + str(i + 1) + "-" + now) self.takePicture(name) self.nshots -= 1 self.hw.motorOn(False) # this part is "active waiting", rotating the cube slowly over the period of options.delay # this ensures consistent lighting for all plates. # account for the time spent capturing images. self.status = "Waiting" secs = 0 while time.time() < nextloop and not self.stop_experiment: time.sleep(1) secs += 1 if self.delay > 15 and secs == int(self.delay / 7.5): # don't bother if delay is very short (<= 15 min) secs = 0 self.hw.motorOn(True) self.hw.halfStep(50, 0.03) self.hw.motorOn(False) finally: log("Experiment stopped.") self.cam.color_effects = None self.status = "Stopped" self.stop_experiment = False self.running = False self.cam.exposure_mode = "auto" self.cam.meter_mode = 'spot'