grit-engine / luaimg

Lua-based automated / batch image manipulation tool
https://grit-engine.github.io/luaimg/
2 stars 1 forks source link

LuaImg Memory problems: Bug or User Error? #2

Closed Gavin-Holt closed 3 months ago

Gavin-Holt commented 6 months ago

Dear Dave,

Sorry to bother you, but I am very excited after discovering LuaImg:

Using your examples, I can see how to use convolution kernels to process the images, and this works well with my 4MB pelvic x-ray exports (JPEG). I am gradually implementing a collection of processes, based upon an old text book and some googling - see below [1].

On my hardware [2] I am having memory problems; the script consumes RAM up to about 2GB (25% of my total) and then crashes!

Reading about Lua garbage collection [3], it states "objects accessible from the root set are preserved".

I would really like to use LuaImg, it follows the Lua principle of providing the basic building blocks - so one can build anything.

Kind Regards Gavin Holt Orthopaedic surgeon - "strong as an ox, and twice as intelligent"

[1] Code

-- LuaImg prototype module and test script
-- Gavin Holt 2024

-- Load some lua helpers
require("extensions")   -- cls() and pause()
cls()

-- Convolution kernels  -- globals
function identity()
    return make(vec(3,3),1,{0,0,0, 0,1,0, 0,0,0})
end
function boxblur(n)
    -- Allow no parameter
    if not n then n=3 end
    -- Integers only
    n = floor(n)
    -- Alter any even numbers
    if (n % 2 == 0) then n = n + 1 end
    local t = {}
    for i=1,n*n,1 do
        t[i] = 1
    end 
    return make(vec(n,n),1,t)*(1/(n*n))
end
function sharpen()
    return make(vec(3,3),1,{0,-1,0, -1,5,-1, 0,-1,0})
end
function sobelx()
    return make(vec(3,3),1,{1,0,-1, 2,0,-2, 1,0,-1})
end
function sobely()
    return make(vec(3,3),1,{1,2,1, 0,0,0, -1,-2,-1})
end
function prewittx()
    return make(vec(3,3),1,{1,0,-1, 1,0,-1, 1,0,-1})
end
function prewitty()
    return make(vec(3,3),1,{1,1,1, 0,0,0, -1,-1,-1})
end
function scharrx()
    return make(vec(3,3),1,{47,0,-47, 162,0,-162, 47,0,-47})
end
function scharry()
    return make(vec(3,3),1,{47,162,47, 0,0,0, -47,-162,-47})
end
function laplace()
    return make(vec(3,3),1,{0,-1,0, -1,4,-1, 0,-1,0}):normalise()
end

-- Other global functions
function threashold(i,cutoff)
    return make(vec(i.width,i.height),3,true,vec(cutoff,cutoff,cutoff))
end

-- Namespace for utility functions
img = {}
function img.hist(filename)     
    local i = open(filename)
    local ret   = {}
    i:foreach(function(p) 
        intensity = unpack(p)       -- takes the first returned value ?correct
        intensity = math.floor(intensity*100)
        if intensity>100 then intensity = 101 end
        if not ret[intensity] then 
            ret[intensity] = 1
        else
            ret[intensity] = ret[intensity]+1
        end
    end)
    return(ret)
end
function img.show(filename)
    -- os.execute is blocking, so use a launcher
    os.execute([[O:\MyProfile\cmd\shelexec.exe /Params:]].. filename ..[[ /EXE O:\MyProfile\cmd\Imagine.exe ]])
    return
end

-- Tests
-- =====

-- Clear previous output - pass
os.execute([[del /q O:\MyProfile\tests\Lua_IMG\*.png]])

-- Load test image - pass
testfile = [[O:\MyProfile\tests\Lua_IMG\LeftHip001.jpg]]
testimg = open(testfile)

-- Too much activity seems to crash the thread @2GB memory usage
-- uncomment goto command to stop crashing
-- goto skip

-- testimg: type - pass
print(testfile..": type = "..type(testimg))

-- testimg: colourChannels - pass
print(testfile..": colourChannels = "..testimg.colourChannels)

-- testimg: hasAlpha - pass
print(testfile..": hasAlpha = "..tostring(testimg.hasAlpha))

-- testimg: height - pass
print(testfile..": height = "..testimg.height)

-- testimg: width - pass
print(testfile..": width = "..testimg.width)

-- testimg: numPixels - pass
print(testfile..": numPixels = "..testimg.numPixels)

-- testimg: size - pass
print(testfile..": size = "..testimg.size)

-- testimg: histogram - pass
t = img.hist(testfile)
print(testfile..": histogram\n")
print("|\tIntensity*100\t|\tFrequency\t|")
print("|\t-------------\t|\t---------\t|")
for i,v in ipairs(t) do print("|\t"..i.."\t\t|\t"..v.."\t\t|") end

-- testimg: identity save and view - pass
testimg:convolve(identity()):save([[O:\MyProfile\tests\Lua_IMG\01_identity.png]])
img.show([[O:\MyProfile\tests\Lua_IMG\01_identity.png]])

-- testimg: blur with boxblur - pass
testimg:convolve(boxblur(19)):save([[O:\MyProfile\tests\Lua_IMG\02_boxblur.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\02_boxblur.png]])

-- testimg: blur with a gaussian - pass
testimg:convolveSep(gaussian(33)):save([[O:\MyProfile\tests\Lua_IMG\03_gaussian.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\03_gaussian.png]])

-- tesimg: sobel - pass
testimg:convolve(sobelx()):convolve(sobely()):save([[O:\MyProfile\tests\Lua_IMG\04_sobel.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\04_sobel.png]])

-- tesimg: prewitt - pass
testimg:convolve(prewittx()):convolve(prewitty()):save([[O:\MyProfile\tests\Lua_IMG\05_prewitt.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\05_prewitt.png]])

-- tesimg: scharr - pass
testimg:convolve(scharrx()):convolve(scharry()):save([[O:\MyProfile\tests\Lua_IMG\06_scharr.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\06_scharr.png]])

-- tesimg: sharpen with "image + (image – blurred)" recipe - pass
s = testimg:convolveSep(gaussian(19))
S = (testimg + (testimg - s))
S:save([[O:\MyProfile\tests\Lua_IMG\07_sharp.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\07_sharp.png]])

-- testimg: sharpen with "laplace of the gaussian" recipe - pass with poor output 
-- unpack should probably not be used here!
l = testimg:convolveSep(gaussian(9)):convolve(laplace())
M = l:reduce(0, function(a,b) return max(unpack(a),unpack(b)) end)
m = -(-l):reduce(0, function(a,b) return max(unpack(a),unpack(b)) end)
l = ((l - m)/(M-m))
l:save([[O:\MyProfile\tests\Lua_IMG\08_laplace.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\08_laplace.png]])

-- testimg: Combination testimg:max(laplace)  - pass 
testimg:max(l):save([[O:\MyProfile\tests\Lua_IMG\09_begona.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\09_begona.png]])

::skip::

-- testimg: thresholding1 - not quite right yet - I think want a binary output
testimg:max(threashold(testimg,0.5)):save([[O:\MyProfile\tests\Lua_IMG\10_upperhalf.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\10_upperhalf.png]])

-- testimg: thresholding2 - not quite right yet -  I think want a binary output
testimg:min(threashold(testimg,0.5)):save([[O:\MyProfile\tests\Lua_IMG\11_lowerhalf.png]])
-- img.show([[O:\MyProfile\tests\Lua_IMG\11_lowerhalf.png]])

pause()
-- Future prospects
-- ================
-- testimg: grey scale - ? take Euclidian length of the color vector
-- testimg: median filter - May require hand rolled convolution
-- testimg: zero crossing edge detector
-- testimg: contour extraction
-- testimg: fast fourier curve fitting
-- testimg: classification into part: cup, stem, and head
-- testimg: orientation: of parts realtive to each other and a bone landmarks (e.g. pelvis, femoral shaft)
-- testing: registration: to allow comparison between images
-- testimg: migration: change over time indicating loosening of the implant from the bone

[2] Hardware

[3] https://www.lua.org/wshop18/Ierusalimschy.pdf

[4] Sample x-ray LeftHip001

Gavin-Holt commented 3 months ago

Guess this project is resting.