Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Eclipse Magic
- -- Programmed exposure sequence for a solar eclipse
- -- Eclipse Magic
- --
- -- Version 1.0.0
- --
- -- Copyright 2017 by Brian Greenberg, [email protected].
- -- Distributed under the GNU General Public License.
- --
- --
- -- ***************************************************************************************************
- -- ***************************************************************************************************
- --
- -- **NOTE** At the current time, the "camera.burst()" functionality requires the "lua_fix"
- -- beta build of Magic Lantern which can be found at https://builds.magiclantern.fm/experiments.html.
- -- Hopefully this function will be merged into the mainline soon. And no idea when support for newer
- -- bodies like the 70D will arrive.
- --
- -- ***************************************************************************************************
- -- ***************************************************************************************************
- --
- --
- -- Read through the comments at the top of the script, and modify the contact points to suit
- -- your location, and the exposure settings to suit your camera. Copy the script to
- -- the ML/SCRIPTS directory on your camera's media, and run it from the menu.
- --
- -- If you have issues, (and you aren't running with the test flag set) you should be able to
- -- reboot your camera, and it will pick up where it left off. Exposure events are tied to
- -- particular times, not a sequence that needs to start at a specific time.
- --
- -- See http://xjubier.free.fr/en/site_pages/SolarEclipseExposure.html for suggestions for
- -- exposure values.
- --
- --
- -- If this script is useful, feel free to send a PayPal donation to [email protected],
- -- or a Bitcoin donation to 1grnbrg3Ea4t6bxHvQKRvorbBeLNDXv2N.
- --
- -- ***************************************************************************************************
- -- ***************************************************************************************************
- -- ***************************************************************************************************
- --
- -- If you are testing, set this to 1, and the shutter won't be used.
- -- Set it to 0 for the real event.
- --
- TestBeepNoShutter = 1
- TestStartTime = 0
- -- Variable definitons that have to go here. Ignore them.
- c1 = {}
- c2 = {}
- c3 = {}
- c4 = {}
- --
- -- Set the 4 contact times here. Time zone is irrelevant, as long as your camera and these
- -- times are the same. Make sure the times are correct for your location, and that your camera
- -- is accurately set to GPS time.
- --
- c1.hr = 0
- c1.min = 0
- c1.sec = 30
- c2.hr = 0
- c2.min = 5
- c2.sec = 30
- c3.hr = 0
- c3.min = 8
- c3.sec = 0
- c4.hr = 0
- c4.min = 13
- c4.sec = 0
- --
- -- Partial phase settings.
- --
- PartialISO = 100
- PartialShutterSpeed = 0.0005 -- 1/2000 s (Enter using decimal seconds.)
- PartialMarginTime = 8 -- Number of seconds after C1 or C3 and before C2 or C4 to start exposures
- PartialExposureCount = 3 -- Number of partial phase exposures before and after totality
- PartialDoBkt = 1 -- Do you want to do exposure bracketing? 1 - yes, 0 - not
- PartialBktStep = 1 -- Number of f-stops in each step. Can 0.333333, 1, 2, etc
- PartialBktCount = 1 -- How many brackets on each side of the neutral exposure?
- --
- -- Do a fast burst of exposures at C2 and C3, to try to get Baily's beads and chromosphere.
- -- You will need to know how many exposures your camera will buffer, and how long it takes the
- -- buffer to fill. At "StartOffset" seconds before C2 or C3, the camera will take "BurstCount"
- -- exposures, as fast as it can. You should adjust C23StartOffset so that burst of image straddles
- -- the contact time. Note that, between the setting of the camera clock and the jitter in this
- -- script, there will be some error in the timing. +/- half a second or more is possible.
- --
- C23BurstCount = 14
- C23BurstStartOffset = 2
- C23BurstISO = 100
- C23BurstShutterSpeed = 0.002 -- 1/500 sec
- --
- -- During the time between C2 and C3, the script will run back and forth between the
- -- "MinShutterSpeed" and "MaxShutterSpeed" as quickly as possible, with an extra 2 long exposures
- -- at midpoint. "ExpStep" is the size of the f-stop variation, and can be set to 0.333333, 1, 2, etc.
- --
- TotalityISO = 100
- TotalityMinShutterSpeed = 0.000123 -- 1/8000 sec (MinShutterSpeed is the *fastest* speed to use.)
- TotalityMaxShutterSpeed = 1.0 -- 1/2 sec (MaxShutterSpeed is the *slowest*, longest speed used.)
- TotalityExpStep = 1
- ---------------------------------------------------------------------------------------------------------
- ---------------------------------------------------------------------------------------------------------
- --
- -- HERE THERE BE DRAGONS
- --
- ---------------------------------------------------------------------------------------------------------
- ---------------------------------------------------------------------------------------------------------
- --
- -- Times are easiest to deal with in seconds. This would be painful if they crossed over midnight,
- -- but late-night solar eclipses are rare.
- --
- c1_sec = c1.hr * 3600 + c1.min * 60 + c1.sec
- c2_sec = c2.hr * 3600 + c2.min * 60 + c2.sec
- c3_sec = c3.hr * 3600 + c3.min * 60 + c3.sec
- c4_sec = c4.hr * 3600 + c4.min * 60 + c4.sec
- max_sec = c2_sec + ((c3_sec - c2_sec) / 2)
- tick_offset = 0
- tick_time = 0
- --
- -- Get the current time (in seconds) from the camera's clock.
- --
- function get_cur_secs ()
- local cur_time = dryos.date
- local cur_secs = (cur_time.hour * 3600 + cur_time.min * 60 + cur_time.sec)
- if ( TestBeepNoShutter == 1 )
- then
- cur_secs = (cur_secs - TestStartTime) -- If we're testing, start the clock at
- -- now, not actual time.
- end
- return cur_secs
- end
- --
- -- Take a time variable expressed in seconds (which is what all times are
- -- stored as) and convert it back to HH:MM:SS
- --
- function pretty_time (time_secs)
- local text_time = ""
- local hrs = 0
- local mins = 0
- local secs = 0
- hrs = (math.floor(time_secs/3600) * 3600)
- mins = (math.floor((time_secs - (hrs * 3600))/60)*60)
- secs = (time_secs - (hrs*3600) - (mins * 60))
- text_time = string.format("%02d:%02d:%02d", hrs, mins, secs)
- return text_time
- end
- --
- -- Every second, update the current time available to all functions.
- -- The camera maintains a millisecond timer since power-on. We can use this to
- -- get close to the beginning of a given second. I think.
- --
- event.seconds_clock = function (ignore)
- tick_time = get_cur_secs()
- tick_offset = dryos.ms_clock
- return true
- end
- --
- -- Hurry up and wait for the next important time to arrive.
- --
- function wait_until (done_waiting)
- local counter = get_cur_secs()
- local next_sec = 0
- print ("Waiting for ", pretty_time(done_waiting), " in ",(done_waiting - counter), " seconds.")
- repeat
- task.yield (1000) -- Let the camera do other tasks for a second.
- counter = get_cur_secs()
- until (counter >= (done_waiting - 1))
- if ( counter < done_waiting)
- then
- -- Loop /should/ exit the second before we are done. But
- -- It's possible that it could exit early in our target
- -- second. If so, we don't want to wait around to
- -- (done_waiting + 1) to exit.
- next_sec = (1000 - (dryos.ms_clock - tick_offset))
- msleep (next_sec) -- Hard sleep, don't let anything else have priority.
- end
- end
- --
- -- Say "CHEESE!". Set up the camera, and take a picture. Also deals with any requested
- -- bracketing.
- --
- function take_shot(iso, speed, dobkt, bktstep, bktcount)
- local bktspeed = 0.0
- camera.iso.value = iso
- if (dobkt == 0)
- then -- Single exposure
- camera.shutter.value = speed
- if (TestBeepNoShutter == 0)
- then
- camera.shoot(false)
- print ("Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value,
- " shutter: 1/", 1/camera.shutter.value)
- task.yield(10) -- Exposures can take time. Give other stuff a chance to run.
- else
- print ("Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value,
- " shutter: 1/", 1/camera.shutter.value)
- beep(1,50)
- task.yield (600 + camera.shutter.ms)
- end
- else -- Bracketing exposure
- -- Loop through the requested number of exposure brackets.
- for bktnum = PartialBktCount,(-1 * PartialBktCount),-1 do
- bktspeed = PartialShutterSpeed * (2.0^(bktnum * PartialBktStep))
- camera.shutter.value = bktspeed
- if (TestBeepNoShutter == 0) then
- camera.shoot(false)
- print ("Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value,
- " shutter: 1/", 1/camera.shutter.value)
- task.yield(10) -- Give other stuff a chance to run
- else
- print ("Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value,
- " shutter: 1/", 1/camera.shutter.value)
- beep(1,50)
- task.yield ((600 + camera.shutter.ms))
- end
- end
- end
- end
- --
- -- Burst is simpler than single shot, because no brackets. Set the camera, pull the trigger.
- --
- -- I have tried replacing this with multiple calls to "camera.shoot()", but it is still considerably
- -- slower than "camera.burst()", and can also crash the camera -- nothing permanent, but still
- -- not something I want to put out.
- --
- function take_burst (count, iso, speed)
- camera.shutter.value = speed
- camera.iso.value = iso
- if (TestBeepNoShutter == 0)
- then
- camera.burst(count)
- print ("Burst Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value, " shutter: 1/",
- 1/camera.shutter.value, "Count: ", count)
- task.yield(10)
- else
- print ("Burst Click! Time: ", pretty_time(tick_time), " ISO: ", camera.iso.value, " shutter: 1/",
- 1/camera.shutter.value, "Count: ", count)
- beep(3,50)
- task.yield (4000 + (count * camera.shutter.ms))
- end
- end
- --
- -- Take the spaced exposures for the C1-C2 and C3-C4 periods. Take the margin times off either
- -- end, split the time into the right intervals, and fire off take_picture()
- --
- function do_partial (start, stop)
- local margin_start = start + PartialMarginTime
- local margin_end = stop - PartialMarginTime
- local image_time = margin_start
- local image_interval = math.floor((margin_end - margin_start) / (PartialExposureCount - 1))
- local exposure_count = 0
- if ( tick_time >= margin_end ) -- Are we past this phase already?
- then
- return
- end
- repeat
- if (tick_time <= image_time)
- then
- wait_until(image_time)
- take_shot (PartialISO, PartialShutterSpeed, PartialDoBkt, PartialBktStep, PartialBktCount)
- end
- image_time = image_time + image_interval
- exposure_count = exposure_count + 1
- until (exposure_count >= PartialExposureCount)
- end
- --
- -- Start the burst shot a little before C2, then start running through exposure settings, going from
- -- short, fast exposures to slow, long exposures and thenback to short, until just before the midpoint
- -- of the eclipse. Take two long exposures at that point, for good measure.
- --
- function do_c2max()
- local cur_shutter_speed = 0
- if ( tick_time >= max_sec ) -- Are we past this phase already?
- then
- return
- end
- if ( tick_time <= (c2_sec - C23BurstStartOffset) ) -- Have we past the burst for Baily's beads?
- then
- wait_until (c2_sec - C23BurstStartOffset)
- take_burst (C23BurstCount, C23BurstISO, C23BurstShutterSpeed)
- end
- cur_shutter_speed = TotalityMinShutterSpeed
- repeat
- take_shot(TotalityISO, cur_shutter_speed, 0, 0, 0)
- cur_shutter_speed = cur_shutter_speed * 2.0^TotalityExpStep
- if (cur_shutter_speed > (1.5 * TotalityMaxShutterSpeed))
- then
- cur_shutter_speed = TotalityMinShutterSpeed
- end
- until (tick_time >= math.max((max_sec - 2 ), (max_sec - (2 * TotalityMaxShutterSpeed + 1))))
- -- Leave room for 2 long exposures
- take_shot(TotalityISO, TotalityMaxShutterSpeed, 0, 0, 0)
- take_shot(TotalityISO, TotalityMaxShutterSpeed, 0, 0, 0)
- end
- --
- -- Similar to do_c2max, but reversed. Exposures run from longest to shortest (and then repeat),
- -- the burst starts just before C3, and there are no bonus exposures.
- --
- function do_maxc3()
- local cur_shutter_speed = 0
- if ( tick_time >= (c3_sec + C23BurstStartOffset) ) -- Are we past this phase already?
- then
- return
- elseif ( tick_time < (c3_sec - C23BurstStartOffset) ) -- Do we have time for some totality exposures?
- then
- cur_shutter_speed = TotalityMaxShutterSpeed
- repeat
- take_shot(TotalityISO, cur_shutter_speed, 0, 0, 0)
- cur_shutter_speed = cur_shutter_speed / (2.0^TotalityExpStep)
- if (cur_shutter_speed < (TotalityMinShutterSpeed / 1.5))
- then
- cur_shutter_speed = TotalityMaxShutterSpeed
- end
- until (tick_time >= (c3_sec - C23BurstStartOffset - 1))
- end
- wait_until (c3_sec - C23BurstStartOffset)
- take_burst (C23BurstCount, C23BurstISO, C23BurstShutterSpeed)
- end
- --
- -- The ringleader.
- --
- function main()
- local starttime
- starttime = get_cur_secs()
- TestStartTime = starttime
- menu.close()
- console.show()
- print ()
- print ()
- print ("-------------------------------------")
- print (" Eclipse Magic")
- print (" Released under the GNU GPL")
- print ("-------------------------------------")
- print ()
- task.yield(1100) -- Wait here for a full second. If not, we can end up getting into timing
- -- loops before tick_time is updated. Mostly only important when the
- -- TestBeepNoShutter flag is set, but it doesn't cost anything.
- -- If the camera is not in manual mode, trying to set the shutter speed throws errors.
- -- Check to make sure we are in manual mode, and refuse to run if we're not.
- if (camera.mode == MODE.M)
- then
- do_partial (c1_sec, c2_sec)
- do_c2max()
- do_maxc3()
- do_partial (c3_sec, c4_sec)
- else
- beep (5, 100)
- print("Camera must be in manual (M) mode!!")
- print()
- print("Press any button to exit the script. Change the mode and re-run.")
- key.wait()
- end
- event.seconds_clock = nil
- console.hide()
- end -- Done. Hope there were no clouds.
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement