Adafruit_Learning_System_Gu.../MEMENTO/Memento_Focus_Stack/code.py
2024-02-13 22:24:53 -08:00

308 lines
11 KiB
Python

# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
# SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
'''
Focus stacking example. Set FOCUS_STEPS (5-10 is a good range) for 0-255 range
Set STACK to True, or set to False to have JPEG mode take snapshots as usual.
'''
import time
import bitmaptools
import displayio
import gifio
import ulab.numpy as np
import adafruit_pycamera
pycam = adafruit_pycamera.PyCamera()
# pycam.live_preview_mode()
settings = (
None,
"resolution",
"effect",
"mode",
"led_level",
"led_color",
"timelapse_rate"
)
curr_setting = 0
print("Starting!")
pycam.tone(440, 0.1)
last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
timelapse_remaining = None
timelapse_timestamp = None
STACK = True # mode placeholder
FOCUS_STEPS = 20 # number of focus steps to increment during bracket from 0-255
FOCUS_START = 30 # optionally, start the focus closer
focus_stacking = False
while True:
if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0:
# alpha blend
new_frame = pycam.continuous_capture()
bitmaptools.alphablend(
onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED
)
pycam.blit(onionskin)
elif pycam.mode_text == "GBOY":
bitmaptools.dither(
last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED
)
pycam.blit(last_frame)
elif pycam.mode_text == "LAPS":
if timelapse_remaining is None:
pycam.timelapsestatus_label.text = "STOP"
else:
timelapse_remaining = timelapse_timestamp - time.time()
pycam.timelapsestatus_label.text = f"{timelapse_remaining}s / "
# Manually updating the label text a second time ensures that the label
# is re-painted over the blitted preview.
pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text
pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text
# only in high power mode do we continuously preview
if (timelapse_remaining is None) or (
pycam.timelapse_submode_label.text == "HiPwr"
):
pycam.blit(pycam.continuous_capture())
if pycam.timelapse_submode_label.text == "LowPwr" and (
timelapse_remaining is not None
):
pycam.display.brightness = 0.05
else:
pycam.display.brightness = 1
pycam.display.refresh()
if timelapse_remaining is not None and timelapse_remaining <= 0:
# no matter what, show what was just on the camera
pycam.blit(pycam.continuous_capture())
# pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
pycam.display.refresh()
pycam.blit(pycam.continuous_capture())
timelapse_timestamp = (
time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1
)
else:
pycam.blit(pycam.continuous_capture())
pycam.keys_debounce()
if pycam.shutter.long_press:
print("FOCUS")
print(pycam.autofocus_status)
pycam.autofocus()
print(pycam.autofocus_status)
if pycam.shutter.short_count:
print("Shutter released")
if pycam.mode_text == "STOP":
pycam.capture_into_bitmap(last_frame)
pycam.stop_motion_frame += 1
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
if pycam.mode_text == "GBOY":
try:
f = pycam.open_next_image("gif")
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
continue
with gifio.GifWriter(
f,
pycam.camera.width,
pycam.camera.height,
displayio.Colorspace.RGB565_SWAPPED,
dither=True,
) as g:
g.add_frame(last_frame, 1)
if pycam.mode_text == "GIF":
try:
f = pycam.open_next_image("gif")
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
continue
i = 0
ft = []
pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access
pycam.display.refresh()
with gifio.GifWriter(
f,
pycam.camera.width,
pycam.camera.height,
displayio.Colorspace.RGB565_SWAPPED,
dither=True,
) as g:
t00 = t0 = time.monotonic()
while (i < 15) or not pycam.shutter_button.value:
i += 1
_gifframe = pycam.continuous_capture()
g.add_frame(_gifframe, 0.12)
pycam.blit(_gifframe)
t1 = time.monotonic()
ft.append(1 / (t1 - t0))
print(end=".")
t0 = t1
pycam._mode_label.text = "GIF" # pylint: disable=protected-access
print(f"\nfinal size {f.tell()} for {i} frames")
print(f"average framerate {i/(t1-t00)}fps")
print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}")
f.close()
pycam.display.refresh()
if pycam.mode_text == "JPEG":
pycam.tone(200, 0.1)
if STACK:
focus_stacking = True
print("Start focus stack!")
pycam.autofocus_vcm_step = FOCUS_START
saved_settings = pycam.get_camera_autosettings()
pycam.set_camera_exposure(saved_settings["exposure"])
pycam.set_camera_gain(saved_settings["gain"])
pycam.set_camera_wb(saved_settings["wb"])
else:
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
pycam.live_preview_mode()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
if focus_stacking:
vcm_step = pycam.autofocus_vcm_step
vcm_step = min(255, vcm_step + FOCUS_STEPS)
if vcm_step < 255:
pycam.capture_jpeg()
pycam.tone(1600 + (vcm_step*10), 0.05)
pycam.autofocus_vcm_step = vcm_step
pycam.display_message(str(vcm_step), color=0xFF00FF)
pycam.live_preview_mode()
print("Now at focus", pycam.autofocus_vcm_step)
else:
focus_stacking = False
print("Done stacking!")
pycam.autofocus_vcm_step = FOCUS_START
pycam.camera.exposure_ctrl = True
pycam.set_camera_gain(None) # go back to autogain
pycam.set_camera_wb(None) # go back to autobalance
pycam.set_camera_exposure(None) # go back to auto shutter
pycam.live_preview_mode()
time.sleep(0.01)
if pycam.card_detect.fell:
print("SD card removed")
pycam.unmount_sd_card()
pycam.display.refresh()
if pycam.card_detect.rose:
print("SD card inserted")
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
for _ in range(3):
try:
print("Mounting card")
pycam.mount_sd_card()
print("Success!")
break
except OSError as e:
print("Retrying!", e)
time.sleep(0.5)
else:
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
time.sleep(0.5)
pycam.display.refresh()
if pycam.up.fell:
print("UP")
key = settings[curr_setting]
if key:
print("getting", key, getattr(pycam, key))
setattr(pycam, key, getattr(pycam, key) + 1)
if pycam.down.fell:
print("DN")
key = settings[curr_setting]
if key:
setattr(pycam, key, getattr(pycam, key) - 1)
if pycam.right.fell:
print("RT")
curr_setting = (curr_setting + 1) % len(settings)
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelapse_rate":
curr_setting = (curr_setting + 1) % len(settings)
print(settings[curr_setting])
# new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
# pycam.set_resolution(pycam.resolutions[new_res])
pycam.select_setting(settings[curr_setting])
if pycam.left.fell:
print("LF")
curr_setting = (curr_setting - 1 + len(settings)) % len(settings)
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelaps_rate":
curr_setting = (curr_setting + 1) % len(settings)
print(settings[curr_setting])
pycam.select_setting(settings[curr_setting])
# new_res = max(1, pycam.get_resolution()-1)
# pycam.set_resolution(pycam.resolutions[new_res])
if pycam.select.fell:
print("SEL")
if pycam.mode_text == "LAPS":
pycam.timelapse_submode += 1
pycam.display.refresh()
if pycam.ok.fell:
print("OK")
if pycam.mode_text == "LAPS":
if timelapse_remaining is None: # stopped
print("Starting timelapse")
timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate]
timelapse_timestamp = time.time() + timelapse_remaining + 1
# dont let the camera take over auto-settings
saved_settings = pycam.get_camera_autosettings()
# print(f"Current exposure {saved_settings=}")
pycam.set_camera_exposure(saved_settings["exposure"])
pycam.set_camera_gain(saved_settings["gain"])
pycam.set_camera_wb(saved_settings["wb"])
else: # is running, turn off
print("Stopping timelapse")
timelapse_remaining = None
pycam.camera.exposure_ctrl = True
pycam.set_camera_gain(None) # go back to autogain
pycam.set_camera_wb(None) # go back to autobalance
pycam.set_camera_exposure(None) # go back to auto shutter