Skip to content

Commit e385697

Browse files
authored
Merge pull request #2733 from jedgarpark/memento-focus-stack
first commit MEMENTO focus stacking
2 parents 678f47c + 8854568 commit e385697

File tree

1 file changed

+308
-0
lines changed

1 file changed

+308
-0
lines changed

MEMENTO/Memento_Focus_Stack/code.py

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2+
# SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
3+
# SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries
4+
#
5+
# SPDX-License-Identifier: Unlicense
6+
7+
'''
8+
Focus stacking example. Set FOCUS_STEPS (5-10 is a good range) for 0-255 range
9+
Set STACK to True, or set to False to have JPEG mode take snapshots as usual.
10+
'''
11+
12+
import time
13+
import bitmaptools
14+
import displayio
15+
import gifio
16+
import ulab.numpy as np
17+
18+
import adafruit_pycamera
19+
20+
pycam = adafruit_pycamera.PyCamera()
21+
# pycam.live_preview_mode()
22+
23+
settings = (
24+
None,
25+
"resolution",
26+
"effect",
27+
"mode",
28+
"led_level",
29+
"led_color",
30+
"timelapse_rate"
31+
)
32+
curr_setting = 0
33+
34+
print("Starting!")
35+
pycam.tone(440, 0.1)
36+
last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
37+
onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
38+
timelapse_remaining = None
39+
timelapse_timestamp = None
40+
41+
STACK = True # mode placeholder
42+
FOCUS_STEPS = 20 # number of focus steps to increment during bracket from 0-255
43+
FOCUS_START = 30 # optionally, start the focus closer
44+
focus_stacking = False
45+
46+
while True:
47+
48+
if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0:
49+
# alpha blend
50+
new_frame = pycam.continuous_capture()
51+
bitmaptools.alphablend(
52+
onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED
53+
)
54+
pycam.blit(onionskin)
55+
elif pycam.mode_text == "GBOY":
56+
bitmaptools.dither(
57+
last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED
58+
)
59+
pycam.blit(last_frame)
60+
elif pycam.mode_text == "LAPS":
61+
if timelapse_remaining is None:
62+
pycam.timelapsestatus_label.text = "STOP"
63+
else:
64+
timelapse_remaining = timelapse_timestamp - time.time()
65+
pycam.timelapsestatus_label.text = f"{timelapse_remaining}s / "
66+
# Manually updating the label text a second time ensures that the label
67+
# is re-painted over the blitted preview.
68+
pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text
69+
pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text
70+
71+
# only in high power mode do we continuously preview
72+
if (timelapse_remaining is None) or (
73+
pycam.timelapse_submode_label.text == "HiPwr"
74+
):
75+
pycam.blit(pycam.continuous_capture())
76+
if pycam.timelapse_submode_label.text == "LowPwr" and (
77+
timelapse_remaining is not None
78+
):
79+
pycam.display.brightness = 0.05
80+
else:
81+
pycam.display.brightness = 1
82+
pycam.display.refresh()
83+
84+
if timelapse_remaining is not None and timelapse_remaining <= 0:
85+
# no matter what, show what was just on the camera
86+
pycam.blit(pycam.continuous_capture())
87+
# pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken
88+
try:
89+
pycam.display_message("Snap!", color=0x0000FF)
90+
pycam.capture_jpeg()
91+
except TypeError as e:
92+
pycam.display_message("Failed", color=0xFF0000)
93+
time.sleep(0.5)
94+
except RuntimeError as e:
95+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
96+
time.sleep(0.5)
97+
pycam.live_preview_mode()
98+
pycam.display.refresh()
99+
pycam.blit(pycam.continuous_capture())
100+
timelapse_timestamp = (
101+
time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1
102+
)
103+
else:
104+
pycam.blit(pycam.continuous_capture())
105+
106+
107+
pycam.keys_debounce()
108+
109+
if pycam.shutter.long_press:
110+
print("FOCUS")
111+
print(pycam.autofocus_status)
112+
pycam.autofocus()
113+
print(pycam.autofocus_status)
114+
115+
if pycam.shutter.short_count:
116+
print("Shutter released")
117+
118+
if pycam.mode_text == "STOP":
119+
pycam.capture_into_bitmap(last_frame)
120+
pycam.stop_motion_frame += 1
121+
try:
122+
pycam.display_message("Snap!", color=0x0000FF)
123+
pycam.capture_jpeg()
124+
except TypeError as e:
125+
pycam.display_message("Failed", color=0xFF0000)
126+
time.sleep(0.5)
127+
except RuntimeError as e:
128+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
129+
time.sleep(0.5)
130+
pycam.live_preview_mode()
131+
132+
if pycam.mode_text == "GBOY":
133+
try:
134+
f = pycam.open_next_image("gif")
135+
except RuntimeError as e:
136+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
137+
time.sleep(0.5)
138+
continue
139+
140+
with gifio.GifWriter(
141+
f,
142+
pycam.camera.width,
143+
pycam.camera.height,
144+
displayio.Colorspace.RGB565_SWAPPED,
145+
dither=True,
146+
) as g:
147+
g.add_frame(last_frame, 1)
148+
149+
if pycam.mode_text == "GIF":
150+
try:
151+
f = pycam.open_next_image("gif")
152+
except RuntimeError as e:
153+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
154+
time.sleep(0.5)
155+
continue
156+
i = 0
157+
ft = []
158+
pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access
159+
160+
pycam.display.refresh()
161+
with gifio.GifWriter(
162+
f,
163+
pycam.camera.width,
164+
pycam.camera.height,
165+
displayio.Colorspace.RGB565_SWAPPED,
166+
dither=True,
167+
) as g:
168+
t00 = t0 = time.monotonic()
169+
while (i < 15) or not pycam.shutter_button.value:
170+
i += 1
171+
_gifframe = pycam.continuous_capture()
172+
g.add_frame(_gifframe, 0.12)
173+
pycam.blit(_gifframe)
174+
t1 = time.monotonic()
175+
ft.append(1 / (t1 - t0))
176+
print(end=".")
177+
t0 = t1
178+
pycam._mode_label.text = "GIF" # pylint: disable=protected-access
179+
print(f"\nfinal size {f.tell()} for {i} frames")
180+
print(f"average framerate {i/(t1-t00)}fps")
181+
print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}")
182+
f.close()
183+
pycam.display.refresh()
184+
185+
if pycam.mode_text == "JPEG":
186+
pycam.tone(200, 0.1)
187+
if STACK:
188+
focus_stacking = True
189+
print("Start focus stack!")
190+
pycam.autofocus_vcm_step = FOCUS_START
191+
saved_settings = pycam.get_camera_autosettings()
192+
pycam.set_camera_exposure(saved_settings["exposure"])
193+
pycam.set_camera_gain(saved_settings["gain"])
194+
pycam.set_camera_wb(saved_settings["wb"])
195+
196+
else:
197+
try:
198+
pycam.display_message("Snap!", color=0x0000FF)
199+
pycam.capture_jpeg()
200+
pycam.live_preview_mode()
201+
except TypeError as e:
202+
pycam.display_message("Failed", color=0xFF0000)
203+
time.sleep(0.5)
204+
pycam.live_preview_mode()
205+
except RuntimeError as e:
206+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
207+
time.sleep(0.5)
208+
209+
if focus_stacking:
210+
vcm_step = pycam.autofocus_vcm_step
211+
vcm_step = min(255, vcm_step + FOCUS_STEPS)
212+
if vcm_step < 255:
213+
pycam.capture_jpeg()
214+
pycam.tone(1600 + (vcm_step*10), 0.05)
215+
pycam.autofocus_vcm_step = vcm_step
216+
pycam.display_message(str(vcm_step), color=0xFF00FF)
217+
pycam.live_preview_mode()
218+
print("Now at focus", pycam.autofocus_vcm_step)
219+
220+
else:
221+
focus_stacking = False
222+
print("Done stacking!")
223+
pycam.autofocus_vcm_step = FOCUS_START
224+
pycam.camera.exposure_ctrl = True
225+
pycam.set_camera_gain(None) # go back to autogain
226+
pycam.set_camera_wb(None) # go back to autobalance
227+
pycam.set_camera_exposure(None) # go back to auto shutter
228+
pycam.live_preview_mode()
229+
time.sleep(0.01)
230+
231+
232+
233+
if pycam.card_detect.fell:
234+
print("SD card removed")
235+
pycam.unmount_sd_card()
236+
pycam.display.refresh()
237+
if pycam.card_detect.rose:
238+
print("SD card inserted")
239+
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
240+
for _ in range(3):
241+
try:
242+
print("Mounting card")
243+
pycam.mount_sd_card()
244+
print("Success!")
245+
break
246+
except OSError as e:
247+
print("Retrying!", e)
248+
time.sleep(0.5)
249+
else:
250+
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
251+
time.sleep(0.5)
252+
pycam.display.refresh()
253+
254+
if pycam.up.fell:
255+
print("UP")
256+
key = settings[curr_setting]
257+
if key:
258+
print("getting", key, getattr(pycam, key))
259+
setattr(pycam, key, getattr(pycam, key) + 1)
260+
if pycam.down.fell:
261+
print("DN")
262+
key = settings[curr_setting]
263+
if key:
264+
setattr(pycam, key, getattr(pycam, key) - 1)
265+
if pycam.right.fell:
266+
print("RT")
267+
curr_setting = (curr_setting + 1) % len(settings)
268+
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelapse_rate":
269+
curr_setting = (curr_setting + 1) % len(settings)
270+
print(settings[curr_setting])
271+
# new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
272+
# pycam.set_resolution(pycam.resolutions[new_res])
273+
pycam.select_setting(settings[curr_setting])
274+
if pycam.left.fell:
275+
print("LF")
276+
curr_setting = (curr_setting - 1 + len(settings)) % len(settings)
277+
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelaps_rate":
278+
curr_setting = (curr_setting + 1) % len(settings)
279+
print(settings[curr_setting])
280+
pycam.select_setting(settings[curr_setting])
281+
# new_res = max(1, pycam.get_resolution()-1)
282+
# pycam.set_resolution(pycam.resolutions[new_res])
283+
if pycam.select.fell:
284+
print("SEL")
285+
if pycam.mode_text == "LAPS":
286+
pycam.timelapse_submode += 1
287+
pycam.display.refresh()
288+
if pycam.ok.fell:
289+
print("OK")
290+
if pycam.mode_text == "LAPS":
291+
if timelapse_remaining is None: # stopped
292+
print("Starting timelapse")
293+
timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate]
294+
timelapse_timestamp = time.time() + timelapse_remaining + 1
295+
# dont let the camera take over auto-settings
296+
saved_settings = pycam.get_camera_autosettings()
297+
# print(f"Current exposure {saved_settings=}")
298+
pycam.set_camera_exposure(saved_settings["exposure"])
299+
pycam.set_camera_gain(saved_settings["gain"])
300+
pycam.set_camera_wb(saved_settings["wb"])
301+
else: # is running, turn off
302+
print("Stopping timelapse")
303+
304+
timelapse_remaining = None
305+
pycam.camera.exposure_ctrl = True
306+
pycam.set_camera_gain(None) # go back to autogain
307+
pycam.set_camera_wb(None) # go back to autobalance
308+
pycam.set_camera_exposure(None) # go back to auto shutter

0 commit comments

Comments
 (0)