Feature request: Intervalometer 2x exposure time

Started by pholler, May 06, 2015, 12:40:59 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

pholler

Hi guys,

i experiment with timelapses and AETTR and find it very useful. Just awesome!
One thing that would be very handy for all timelapsers out there who do day-to-night or night-to-day-lapses would be to adjust the intervalometer automatically with the actual shutter time. Simple algebra would be nice. Something like two times shutter time plus 2s.

Of course you can adjust the intervalometer with the adv_int-module but its static and you need to program the intervals by guessing the exposure time that will be needed.

Greetz
Peter


pholler

Amazing! I just spent a bit more than an hour to download and compile ML but Lua-scripting is now working and i am amazed. Just awesome!
I am now starting to write a script for that. When i am finished i am going to post ist here. THANX!

pholler

Hi David,

i am still amazed by alle possibilties poosible with LUA scripting on ML!

At the moment i am having some troubles calculating the actual shutter speed from APEX to seconds. As "^" is not working i tried it with math.pow, but it didn't work. My code looks like this now:

menu.new
{
    name = "Shutter speed",
    help = "Show the shutter speed",
    select = function()
        console.show()
shutTimeAPEX = camera.shutter/10
shutTime = 1/(2 ^ shutTimeAPEX)
        print("Shutter APEX: "..shutTimeAPEX)
        print("Shutter: "..shutTime)
    end
}


Is there a better way to convert APEX to seconds?

Greetings
Peter

dmilligan

There are some issues with some floating point operations (these cameras don't have hardware floating point), some things work, some things don't. I haven't had a chance to look into these issues. I would suggest only using integer math, or just using a lookup table for now.

pholler


dmilligan

Okay, it looks like the issue is with floating point conversions. The actual floating point math seems to work even the ^ (you just need to convert everything back to integer with math.floor() if you try to display it or pass it to a function)


print(math.floor(2^3)) -- prints 8
print(math.floor((4 / 3) * 3)) -- prints 4
print(4/3) -- crashes at runtime
print(1.5) -- crashes at runtime
print(math.floor(1.5)) --crashes when the lua interpreter tries to compile


@a1ex or @g3gg0, do you have any idea what is going on here? it seems like we are missing the ability to convert floating point numbers to string?

console_printf("%f",1.5);

just prints out f

a1ex

If you use DryOS sprintf (the one from ML), it's not exactly standards-compliant. We should probably embed the one from dietlibc.

edit: the one from recovery branch (which afaik it's from dietlibc) doesn't work either (probably needs some definitions to enable floating point support).

dmilligan

I figured out the source of the crash when printing, it had to do with LUA_COMPAT_FLOATSTRING (which actually had nothing to do with snprintf). There's some code in lua that tries to add ".0" when formatting floats that look like integers, it was crashing for some reason, so I disabled it and no more crashing. I tried just copying the code for the snprintf functions from dietlibc into the lua module, it doesn't really work, I get strange results like: "0.=33333" "0.?" and "0.66666,"

dmilligan

Fixed it! Now printing floats works. I had to use a custom 'ftoa()' function. Now to figure out why floating point literals crash the interpreter...

pholler


pholler

I finished my first script now and the automatic intervalometer changes by shutter time a working perfectly now. Thanx David! LUA is just awesome!

I am going to try the script in a timelapse with AETTR tonight. I hope it does not start raining...


So here is my script. It's quite basic and i am still confused by the LUA syntax but it works:
minInterval = 10;
shutInterval_menu = menu.new
{
parent = "Intervalometer",
name = "Interval 2x Shutter speed",
help = "Sets the interval to 2x the shutter speed; min. interval is 10s",
choices = { "Off", "On"},
value = "Off",
}

function event.intervalometer(shootCount)
if shutInterval_menu.value =="On" then
shutTimeAPEX = camera.shutter/10
shutTime = 1/(2^shutTimeAPEX)
myInterval = math.ceil(shutTime*2)
if myInterval < minInterval then myInterval=minInterval end
interval.time=myInterval
print("#"..shootCount, "Shutter: "..shutTime, "new interval: "..myInterval)
return true
end
end

dmilligan

Now that floating point seems to be fully working, I have improved the API for basic exposure settings with various units (some floating point, some integer), so you don't have to do that conversion yourself. So now instead of camera.shutter you have the following:

camera.shutter.raw
camera.shutter.apex
camera.shutter.apexf
camera.shutter.ms
camera.shutter.value
camera.shutter:tostring()


aperture, iso, and ec also follow the same basic pattern. see the updated docs for more info

pholler

Hi,

i finished my script now and am quite happy with it.

If someone has a similar problem here is my solution:
shutInterval_menu = menu.new
{
parent = "Intervalometer",
name = "Interval 2x Shutter speed",
help = "Sets the interval to n-times the shutter speed",
update = function(self) return self.submenu["Enabled"].value end,
submenu =
{
{
name = "Enabled",
choices = {"Off","On"},
},
{
name = "shutter-multiplier",
help = "define n [1] for interval = n*shutter; normally n=2",
min = 0.1, max = 10,
unit = UNIT.DEC,
value = 2,
},
{
name = "min. interval",
help = "min. interval value [s]",
min = 0, max = 600,
unit = UNIT.TIME,
value = 10,
},
{
name = "processing time",
help = "min. time between two shots [s]",
min = 0, max = 600,
unit = UNIT.TIME,
value = 7,
},
},
}


function event.intervalometer(shootCount)
if shutInterval_menu.submenu["Enabled"].value =="On" then
multipleShutter = math.ceil(camera.shutter.value*shutInterval_menu.submenu["shutter-multiplier"].value)
shutPlusProces = math.ceil(camera.shutter.value + shutInterval_menu.submenu["processing time"].value)
myInterval = math.max(shutInterval_menu.submenu["min. interval"].value, multipleShutter,shutPlusProces)
interval.time=myInterval
-- for Debugging:
-- print("n: "..shutInterval_menu.submenu["shutter-multiplier"].value, "min. interval: "..shutInterval_menu.submenu["min. interval"].value, "proc. time: "..shutInterval_menu.submenu["processing time"].value)
-- print("#"..shootCount, "Shutter: "..camera.shutter.value, "new interval: "..myInterval)
return true
end
end


You do have the possibility so select three values:
shutter-multiplier ... the interval is calculated by shutter multiplied with this value (n*shutter; normally for 180°-shutter you choose 2 here)
min. interval ... minimal interval between two shots
processing time ... the amount  of time the camera needs for processing (AETTR) and MLU between two shots (shutter + processing time)

In the end the script calculates three intervals and chooses the maximum out of it. So you can be sure the acutal interval is always bigger than the minimal interval, it is always greater than n-times the shutter and there is alwas enough time for MLU and AETTR.


dmilligan

Looks good, you might consider also adding the ability to save your configuration (save your menu selections after the camera reboots, like most other ML settings). I made an easy way to do this, there's some example code the in copy2m script that shows you how. You can reuse the following block of code from that script in your script:


--[[---------------------------------------------------------------------------
Class for saving/loading config data
use config.data to read/write value(s) you would like to save and restore
@type config
]]
config = {}
config.configs = {}
config.__index = config

--[[---------------------------------------------------------------------------
Create a new config instance, filename will be determined automagically
@param default the default data values
@function create
]]
function config.create(default)
    local cfg = {}
    --determine the config filename automatically based on the script's filename
    local thisfile = debug.getinfo(1,"S").short_src
    assert(thisfile ~= nil, "Could not determine script filename")
    --capture between the last '/' and last '.' in the filename
    local short_name = string.match(thisfile,"/([^/%.]+)%.[^/%.]+$")
    cfg.filename = string.format("%s%s.cfg", dryos.config_dir.path,short_name)
    assert(thisfile ~= cfg.filename, "Could not determine config filename")
    cfg.default = default
    setmetatable(cfg,config)
    cfg.data = cfg:load()
    table.insert(config.configs, cfg)
    if event.config_save == nil then
        event.config_save = function(unused)
            for i,v in ipairs(config.configs) do
                v:saving()
                v:save()
            end
        end
    end
    return cfg
end

--[[---------------------------------------------------------------------------
Create a new config instance from a menu structure, filename will be determined
automagically
@param m the menu to create a config for
@function create_from_menu
]]
function config.create_from_menu(m)
    local default = {}
    --default values are simply the menu's default values
    default[m.name] = m.value
    if m.submenu ~= nil then
        --todo: recurse into sub-submenus
        for k,v in pairs(m.submenu) do
            default[k] = v.value
        end
    end
    local cfg = config.create(default)
    cfg.menu = m
    --copy back loaded data to the menu structure
    m.value = cfg.data[m.name]
    if m.submenu ~= nil then
        --todo: recurse into sub-submenus
        for k,v in pairs(m.submenu) do
             v.value = cfg.data[m.name]
        end
    end
    return cfg
end

--[[---------------------------------------------------------------------------
Load the config data from file (by executing the file)
@function load
]]
function config:load()
    local status,result = pcall(dofile,self.filename)
    if status and result ~= nil then
        return result
    else
        print(result)
        return self.default
    end
end

--[[---------------------------------------------------------------------------
Called right before config data is saved to file, override this function to
update your config.data when the config is being saved
@function saving
]]
function config:saving()
    --default implementation: save menu structure if there is one
    if self.menu ~= nil then
        self.data[self.menu.name] = self.menu.value
        if self.menu.submenu ~= nil then
            for k,v in pairs(self.menu.submenu) do
                self.data[k] = v.value
            end
        end
    end
end

--[[---------------------------------------------------------------------------
Manually save the config data to file (data is saved automatically when the
ML backend does it's config saving, so calling this function is unecessary
unless you want to do a manual save).
Whatever is in the 'data' field of this instance is saved. Only numbers,
strings and tables can be saved (no functions, threads or userdata)
@function save
]]
function config:save()
    local f = io.open(self.filename,"w")
    f:write("return ")
    assert(f ~= nil, "Could not save config: "..self.filename)
    config.serialize(f,self.data)
    f:close()
end

--private
function config.serialize(f,o)
    if type(o) == "number" then
        f:write(o)
    elseif type(o) == "string" then
        f:write(string.format("%q", o))
    elseif type(o) == "table" then
        f:write("{\n")
        for k,v in pairs(o) do
            f:write("  [")
            config.serialize(f,k)
            f:write("] = ")
            config.serialize(f,v)
            f:write(",\n")
        end
        f:write("}\n")
    else
        --something we don't know how to serialize, just skip it
    end
end


You would use it like this right after your shutInterval_menu declaration:

config.create(shutInterval_menu)


I would eventually like to add this and other stuff written in pure lua to the API itself (so you don't have to put it directly in your script), but I need to come up with a good way to implement "library scripts"