Gimp-Forum.net
Searching for python script islands to layers - Printable Version

+- Gimp-Forum.net (https://www.gimp-forum.net)
+-- Forum: GIMP (https://www.gimp-forum.net/Forum-GIMP)
+--- Forum: Extending the GIMP (https://www.gimp-forum.net/Forum-Extending-the-GIMP)
+--- Thread: Searching for python script islands to layers (/Thread-Searching-for-python-script-islands-to-layers)

Pages: 1 2 3 4


RE: Searching for python script islands to layers - Ofnuts - 01-22-2018

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.


RE: Searching for python script islands to layers - mich_lloid - 01-22-2018

(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? Wink


RE: Searching for python script islands to layers - Ofnuts - 01-23-2018

(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 Smile 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? Wink

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?


RE: Searching for python script islands to layers - mich_lloid - 01-23-2018

"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:
[Image: 6Py8XBT.jpg]
My script result:
[Image: BQs4j9d.jpg]

I thought for a while that gimp crashed with samples of 1 pixels, that's how long it took. Tongue

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?


RE: Searching for python script islands to layers - Ofnuts - 01-23-2018

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 Smile


RE: Searching for python script islands to layers - mich_lloid - 01-23-2018

(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 Smile

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


RE: Searching for python script islands to layers - Ofnuts - 01-23-2018

Updated the script on SourceForge. Actually just needed a single call to gimp_histogram().


RE: Searching for python script islands to layers - mich_lloid - 01-23-2018

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.


RE: Searching for python script islands to layers - Ofnuts - 01-24-2018

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"


RE: Searching for python script islands to layers - mich_lloid - 01-24-2018

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?