Posts: 6,342
Threads: 274
Joined: Oct 2016
Reputation:
564
Operating system(s):
Gimp version: 2.10
1) No, I don't know what you mean. Are you taking in account the fact that the Y coordinates go down and not up?
2) The layer variable is actually a Python object(*) with a writable "name" attribute, so you can do something like:
Code:
layer.name="Layer %03d" % some_sequence_number
or even
Code:
layer.name="Object @(%3d,%3d)" % (i*sample,j*sample)
Speaking of "i*sample", a seasoned Pythonista would loop like this:
[code]
for x in range(0,img.width,sample):
for y in range(0,img.height,sample):
# and use x,y instead of i*sample,j*sample
[code]
(*) Most things you manipulate are: image, drawable, layer (subclass of drawable), channel... and they have methods that wrap the most used pdb.* calls. See this not so complete doc and if you are curious, do a "dir(image)" or "dir(layer)" or "dir(gimp)" in your Python console.
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-22-2018, 11:30 PM
(This post was last modified: 01-23-2018, 12:05 AM by mich_lloid.)
(01-22-2018, 10:52 PM)Ofnuts Wrote: 1) No, I don't know what you mean. Are you taking in account the fact that the Y coordinates go down and not up?
2) The layer variable is actually a Python object(*) with a writable "name" attribute, so you can do something like:
Code:
layer.name="Layer %03d" % some_sequence_number
or even
Code:
layer.name="Object @(%3d,%3d)" % (i*sample,j*sample)
Speaking of "i*sample", a seasoned Pythonista would loop like this:
Code:
for x in range(0,img.width,sample):
for y in range(0,img.height,sample):
# and use x,y instead of i*sample,j*sample
[code]
(*) Most things you manipulate are: image, drawable, layer (subclass of drawable), channel... and they have methods that wrap the most used pdb.* calls. See [url=http://www.gimp.org/docs/python/index.html]this not so complete doc[/url] and if you are curious, do a "dir(image)" or "dir(layer)" or "dir(gimp)" in your Python console.
[/code]
Thanks for the tips
1) The selection went vertically instead of horizontally, i had to switch image.height loop with image.width, very counter intuative.
I also have to apply the mask layer for this script to work, thankfully the color data of the transparent pixels is preserved.
The code now looks like this:
Code:
img = gimp.image_list()[0]
layer = img.active_layer
#sample every 10 pixels
sample = 10
counter = 1
for y in range(0, img.height , sample):
for x in range(0, img.width, sample):
alphacheck = pdb.gimp_color_picker(img, layer, x, y, 0,0,0)
if alphacheck[-1] > 0:
pdb.gimp_fuzzy_select(layer, x, y, 254.9 , 2,0,0, 0,0)
prev_layer = img.active_layer
newlayer = pdb.gimp_layer_copy( img.active_layer, pdb.gimp_drawable_has_alpha( img.active_layer ) )
pdb.gimp_image_insert_layer(img, newlayer, None, -1)
bounds = pdb.gimp_selection_bounds(img)
newlayer.name = "Layer %03d (%d, %d, %d, %d)" % (counter, bounds[1], bounds[2], bounds[3] - bounds[1], bounds[4] - bounds[2])
counter += 1
pdb.gimp_layer_resize(img.active_layer, bounds[3] - bounds[1], bounds[4] - bounds[2], -bounds[1], -bounds[2])
img.active_layer = prev_layer
pdb.gimp_edit_clear(prev_layer)
Could you make it go faster?
Posts: 6,342
Threads: 274
Joined: Oct 2016
Reputation:
564
Operating system(s):
Gimp version: 2.10
(01-22-2018, 11:30 PM)mich_lloid Wrote: 1) The selection went vertically instead of horizontally, i had to switch image.height loop with image.width, very counter intuitive.
Well, *you* wrote the inner loop on Y so the computer abides There is nothing "intuitive" in programming, at least not at the beginning.
(01-22-2018, 11:30 PM)mich_lloid Wrote: I also have to apply the mask layer for this script to work, thankfully the color data of the transparent pixels is preserved.
The code now looks like this:
Code:
img = gimp.image_list()[0]
layer = img.active_layer
#sample every 10 pixels
sample = 10
counter = 1
for y in range(0, img.height , sample):
for x in range(0, img.width, sample):
alphacheck = pdb.gimp_color_picker(img, layer, x, y, 0,0,0)
if alphacheck[-1] > 0:
pdb.gimp_fuzzy_select(layer, x, y, 254.9 , 2,0,0, 0,0)
prev_layer = img.active_layer
newlayer = pdb.gimp_layer_copy( img.active_layer, pdb.gimp_drawable_has_alpha( img.active_layer ) )
pdb.gimp_image_insert_layer(img, newlayer, None, -1)
bounds = pdb.gimp_selection_bounds(img)
newlayer.name = "Layer %03d (%d, %d, %d, %d)" % (counter, bounds[1], bounds[2], bounds[3] - bounds[1], bounds[4] - bounds[2])
counter += 1
pdb.gimp_layer_resize(img.active_layer, bounds[3] - bounds[1], bounds[4] - bounds[2], -bounds[1], -bounds[2])
img.active_layer = prev_layer
pdb.gimp_edit_clear(prev_layer)
Could you make it go faster?
get_pixel() is possibly somewhat faster than pdb.gimp_color_picker(img, layer, x, y, 0,0,0)
Set a variable to the initial active layer and stop wondering what layer is the active one after that.
"bounds = pdb.gimp_selection_bounds(img)" is better written "_, x1, y1, x2, y2 = pdb.gimp_selection_bounds(image)" and then use x1,x2,y1,y2... instead of indexing bounds[]. Not much faster but easier to read. "_" is by convention the nale of a variable you won't use.
You are playing with fire, what if you sample in the hole inside an object?
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-23-2018, 02:23 PM
(This post was last modified: 01-23-2018, 02:27 PM by mich_lloid.)
"get_pixel() is possibly somewhat faster than pdb.gimp_color_picker(img, layer, x, y, 0,0,0)"
Wow, drawable.get_pixel() is approximately 6,5 times faster than pdb.gimp_color_picker, thanks for pointing me to that.
the script looks like this now:
Code:
import time
img = gimp.image_list()[0]
def objects_to_layers(image, smp=10):
time_start = time.time()
img = image
sample = smp
counter = 1
newlayer = pdb.gimp_layer_copy( img.active_layer, pdb.gimp_drawable_has_alpha( img.active_layer ) )
img.add_layer(newlayer)
for y in range(0, img.height , sample):
for x in range(0, img.width, sample):
#alphacheck = pdb.gimp_color_picker(img, img.active_layer, x, y, 0,0,0)
alphacheck = img.active_layer.get_pixel(x,y)
if alphacheck[-1] > 0:
pdb.gimp_fuzzy_select(img.active_layer, x, y, 254.9 , 2,0,0, 0,0)
src_layer = img.active_layer
newlayer = pdb.gimp_layer_copy( img.active_layer, pdb.gimp_drawable_has_alpha( img.active_layer ) )
img.add_layer(newlayer)
_, x1, y1, x2, y2 = pdb.gimp_selection_bounds(img)
newlayer.name = "Layer %03d (%d, %d, %d, %d)" % (counter, x1, y1, x2 - x1, y2 - y1)
counter += 1
pdb.gimp_layer_resize(img.active_layer, x2 - x1, y2 - y1, -x1, -y1)
img.active_layer = src_layer
pdb.gimp_edit_clear(src_layer)
img.remove_layer(src_layer)
time_end = time.time()
print ('time taken: ' + str(time_end - time_start) + ' seconds.')
results as to speed with samples of 10, 5 and 1 pixels for a 1024x1024 image with 5 objects:
➤> objects_to_layers(img)
time taken: 2.11100006104 seconds.
➤> objects_to_layers(img, 5)
time taken: 7.57000017166 seconds.
➤> objects_to_layers(img, 1)
time taken: 181.577000141 seconds.
Also your script "Layer > Extract Objects > Extract objects to layers..." gives these results with holes as objects:
My script result:
I thought for a while that gimp crashed with samples of 1 pixels, that's how long it took.
I read that python's array module will get even faster results in Akkana Peck's blog: http://shallowsky.com/blog/gimp/pygimp-pixel-ops.html
How do I implement that into my script?
Posts: 6,342
Threads: 274
Joined: Oct 2016
Reputation:
564
Operating system(s):
Gimp version: 2.10
What Akkana is mentioning is the "pixel region" thing I alluded to in post #9 of this thread. Not sure it helps you here because it is just a raw interface to the layer data, which is *copied* to the Python variable, so if you use Gimp calls to update the layer, you may have to extract it again, or update the array all by yourself, so the final result may not be that much faster.
PS; Post you test image, so that I can try my fixes
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
(01-23-2018, 03:17 PM)Ofnuts Wrote: What Akkana is mentioning is the "pixel region" thing I alluded to in post #9 of this thread. Not sure it helps you here because it is just a raw interface to the layer data, which is *copied* to the Python variable, so if you use Gimp calls to update the layer, you may have to extract it again, or update the array all by yourself, so the final result may not be that much faster.
PS; Post you test image, so that I can try my fixes
You can reproduce my image very easily, I just made 5 doodles, four in each corner and one in the middle with a brush on a transparent image as you see them in my previous post, just make sure you make objects with holes in them to see what I mean with your script that they (the holes) are counted as objects.
I think the speed bottleneck is the 2 standard for loops and the get_pixel(), I'll have to study that some more
Posts: 6,342
Threads: 274
Joined: Oct 2016
Reputation:
564
Operating system(s):
Gimp version: 2.10
Updated the script on SourceForge. Actually just needed a single call to gimp_histogram().
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
01-23-2018, 10:13 PM
(This post was last modified: 01-23-2018, 10:23 PM by mich_lloid.)
Ok, so I've been looking at sample scripts of jfmdev at "http://registry.gimp.org/node/28124"
very insteresting are scripts "test-discolour-layer-v3.py" which loops over pixels with arrays and "test-discolour-layer-v4.py" which loops over pixels with tiles.
Looping over pixels with array module:
Code:
import time
import array
def loop_array():
time_start = time.time()
img = gimp.image_list()[0]
layer = img.active_layer
srcRgn = layer.get_pixel_rgn(0, 0, layer.width, layer.height, False, False)
pixelSize = len(srcRgn[0,0])
srcArray = array.array("B", srcRgn[0:layer.width, 0:layer.height])
for y in range(layer.height):
for x in range(layer.width):
# Get the pixel
pos = (x * layer.height + y) * pixelSize
pixel = srcArray[pos], srcArray[pos+1], srcArray[pos+2], srcArray[pos+3]
time_end = time.time()
print ('time taken: ' + str(time_end - time_start) + ' seconds.')
loop_array()
➤> loop_array()
time taken: 20.2019999027 seconds.
20 seconds is pretty bad for looping over 1024x1024 image.
looping over pixels with tiles:
Code:
import time
def loop_tiles():
time_start = time.time()
img = gimp.image_list()[0]
layer = img.active_layer
tn = int(layer.width / 64)
if(layer.width % 64 > 0):
tn += 1
tm = int(layer.height / 64)
if(layer.height % 64 > 0):
tm += 1
for i in range(tn):
for j in range(tm):
srcTile = layer.get_tile(False, j, i)
for x in range(srcTile.ewidth):
for y in range(srcTile.eheight):
# Get the pixel
pixel = srcTile[x,y]
print( ord( pixel[-1] ))
time_end = time.time()
print ('time taken: ' + str(time_end - time_start) + ' seconds.')
loop_tiles()
➤>time taken: 0.218999862671 seconds.
Wow, 0.21 seconds, looping over pixels with tiles is definitely the way to go.
Ok, so now my questions are:
1) How do I translate a tile's pixel position to it's layer's global pixel position so I can select it with "pdb.gimp_fuzzy_select(img.active_layer, <x_position_of_pixel>, <y_position_of_pixel>, 254.9 , 2,0,0, 0,0)" ?
2) How do I exit the loop, update the layer by clearing selection, update layer, and resume the loop from last tile?
f.e. I want to accomplishthese steps for a 1024x1024 image with 16x16 tiles:
-I find a non-transparent pixel at tile(3,4)
-I exit the tile loop
-select that pixel and neighboring non-transparent pixels with 'pdb.gimp_fuzzy_select'
-do some stuff
-update the layer
-and then resume tile loop at tile(3,4) until tile(16,16).
-script hopefully ends succesfully.
Posts: 6,342
Threads: 274
Joined: Oct 2016
Reputation:
564
Operating system(s):
Gimp version: 2.10
You are jumping to conclusions. The problem isn't accessing the region, it is looping... and you don't really need to copy to an array you can loop of the region directly (but it is only marginally faster).
Looping on tiles is fast, if you don't need to reload the tiles. Tiles are to Gimp what RAM pages are to your system. If your algorithm can use a tile, put it back, and never need to come back to it, it's fast. If you load tiles at random, performance will degrade.
1) How do I translate a tile's pixel position to it's layer's global pixel position so I can select it with "pdb.gimp_fuzzy_select(img.active_layer, <x_position_of_pixel>, <y_position_of_pixel>, 254.9 , 2,0,0, 0,0)" ?
Just add the "layer.offsets" to the layer-relative X and Y position.
2) How do I exit the loop, update the layer by clearing selection, update layer, and resume the loop from last tile?
f.e. I want to accomplishthese steps for a 1024x1024 image with 16x16 tiles:
-I find a non-transparent pixel at tile(3,4)
-I exit the tile loop
-select that pixel and neighboring non-transparent pixels with 'pdb.gimp_fuzzy_select'
-do some stuff
-update the layer
-and then resume tile loop at tile(3,4) until tile(16,16).
-script hopefully ends succesfully.
Why do you need to exit the tile loop? Just reload the tile when you are done with "some stuff/update the layer"
Posts: 16
Threads: 1
Joined: Jan 2018
Reputation:
0
Operating system(s):
- Windows (Vista and later)
Thanks Ofnuts!
Wow, looping over tiles is ridiculously faster than standard looping with python, 5 seconds versus 3 minutes with a 1024x1024 image with 5 objects sampling every pixel.
The script now looks like this, any suggestions to make script better would be really welcome:
Code:
import time
def objects_to_layers(img, sample = 1):
time_start = time.time()
#samples = 2
#img = gimp.image_list()[0]
src_layer = img.active_layer
layer = src_layer.copy()
img.add_layer(layer)
counter = 1
temp_layer = layer.copy()
tile_rows = int(layer.width / 64)
if(layer.width % 64 > 0):
tile_rows += 1
tile_cols = int(layer.height / 64)
if(layer.width % 64 > 0):
tile_cols += 1
for tile_row in range(tile_rows):
for tile_col in range(tile_cols):
#print(tile_row, tile_col)
#srcTile = layer.get_tile(False, tile_row, tile_col)
srcTile = temp_layer.get_tile(False, tile_row, tile_col)
if srcTile != None:
for tile_x in range(0, srcTile.ewidth, sample):
for tile_y in range(0, srcTile.eheight, sample):
pixel = srcTile[tile_x, tile_y]
# if pixel is not completely transparent
if ord( pixel[3] ) > 0:
layer_pixel_pos_x = tile_col * 64 + tile_x
layer_pixel_pos_y = tile_row * 64 + tile_y
pdb.gimp_fuzzy_select(img.active_layer, layer_pixel_pos_x, layer_pixel_pos_y, 254.9 , 2,0,0,0,0)
newlayer = layer.copy()
img.add_layer(newlayer)
x1, y1, x2, y2 = layer.mask_bounds
newlayer.name = "Layer %03d (%d, %d, %d, %d)" % (counter, x1, y1, x2 - x1, y2 - y1)
counter += 1
newlayer.resize(x2 - x1, y2 - y1, -x1, -y1)
img.active_layer = layer
pdb.gimp_edit_clear(layer)
#update tile by updating temp_layer
temp_layer = layer.copy()
srcTile = temp_layer.get_tile(False, tile_row, tile_col)
img.remove_layer(layer)
time_end = time.time()
print ('time taken: ' + str(time_end - time_start) + ' seconds.')
img = gimp.image_list()[0]
objects_to_layers(img)
I tested with a large 4096 x 4096 image with 194 objects on them, took 24 seconds to get them to seperate layers, I won't test with old script, will probably take at least 10 minutes...
I tried to update tiles directly with active layer in image, but the tiles wouldn't update, that's why i used a temp_layer copy of active_image.
Can you tell me why this is or what I probably did wrong?
|