Skip to content

Instantly share code, notes, and snippets.

@Lewiscowles1986
Last active August 22, 2017 04:43
Show Gist options
  • Save Lewiscowles1986/ff11dfc46820a1374d357d29f5f5134d to your computer and use it in GitHub Desktop.
Save Lewiscowles1986/ff11dfc46820a1374d357d29f5f5134d to your computer and use it in GitHub Desktop.

Suggested fix for Average Layer Gimp Script

http://registry.gimp.org/node/5012?destination=node/5012

Pre-requisites

  • Only tested on Ubuntu Xenial so-far (should be good on Debian Stretch as well)
  • Python 2 with pip
  • GIMP (this should hopefully be obvious)
sudo pip install scipy numpy
mkdir -p ~/.gimp-2.8/plug-ins
cd ~/.gimp-2.8/plug-ins
wget https://gist.githubusercontent.com/Lewiscowles1986/ff11dfc46820a1374d357d29f5f5134d/raw/e6d32d48ec8a9c35f762820630d4881622748d1e/average_layer-suggested-fix.py
chmod +x average_layer-suggested-fix.py

Changes

  • scipy no longer has stats.median, using Numpy for this See Here
  • register didn't work, so I used /usr/lib/gimp/2.0/foggify.py as a reference
  • Added two additional options from GIMP registry comments (I have no idea if these are needed)
    • [PF_IMAGE, "image", "Input image", None],
    • [PF_DRAWABLE, "drawable", "Input drawable", None],
  • commented out the pdb.gimp_message line (it's annoying AF)
#!/usr/bin/python
###############################
#Compute average layer v1.1
#python gimp plugin
#2008, Elmar Hoefner
#Licensed under GPLv3
#see www.fsf.org for details
###############################
# Version history
# [V1.0 - not officially numbered] First release
# V1.1 - Work on (rectangular) selections
#In a 4d array (like superarray) access layers, rows, columns and pixels as follows:
#array[:,x] = Row x in all layers
#array[:,:,y] = Column y in all layers
#array[:,x,y] = Pixel xy in all layers
#array[:,x,y,rgba] = Channel rgba (0-3) in all layers
# to access meanarray colors:
# meanarray.T[0] = R
# meanarray.T[1] = G
# meanarray.T[2] = B
# meanarray.T[3] = A
# If missing an alpha channel is added
import scipy
from scipy.stats import mode, gmean, hmean
from numpy import median
from gimpfu import *
import os
from math import floor
gettext.install("gimp20-python", gimp.locale_directory, unicode=True)
class Averager:
def __init__(self, image, meantype, as_new_image, only_visible_layers, cutoff, side):
"""Constructor"""
self.image=image
self.meantype=meantype
self.as_new_image=as_new_image
if only_visible_layers:
self.layerlist=[]
for layer in image.layers:
if layer.visible:
self.layerlist.append(layer)
#if self.layerlist==[] # no layer is visible, choose all
#self.layerlist=img.layers
else: self.layerlist=image.layers
# Enough layers must be left after cutting extreme values...
self.values_to_cut=int(floor(cutoff*len(self.layerlist)/100.0))
self.cutoff_side=side
# Create a value to check how many layers/values are left:
if self.cutoff_side < 2:
remove = 1
else: remove = 2
self.values_used=len(self.layerlist)-remove*self.values_to_cut
#image format or selection format, if one:
non_empty, x1, y1, x2, y2 = pdb.gimp_selection_bounds(image)
self.selection_width=x2-x1
self.selection_height=y2-y1
self.image_width=image.width
self.image_height=image.height
self.origin_x=x1
self.origin_y=y1
self.endpoint_x=x2
self.endpoint_y=y2
self.bpp=4 # always assume alpha (see read_in!)
#self.sarray=None # sarray is created by calling self.superarray()
# How much should the progress indicator advance?
self.step=1/(self.values_used)
def read_in(self,drawable):
"""Convert pixel region to scipy array. Taken (& modified) from John Fremlin's retinex implementation."""
# always add alpha to save hassles later...
drawable.add_alpha()
#get a pixel region
pr = drawable.get_pixel_rgn(self.origin_x, self.origin_y, self.selection_width, self.selection_height, False)
# Convert pr to array, float is needed to keep precision in calculations
a = scipy.fromstring(pr[self.origin_x:self.endpoint_x,self.origin_y:self.endpoint_y],"B")
assert(a.size == self.selection_width * self.selection_height * self.bpp)
imagearr = scipy.array(a.reshape(self.selection_height,self.selection_width,self.bpp),scipy.float32)[:,:,0:self.bpp]
return imagearr
def write_out(self,drawable, imagearr):
"""Convert scipy array to pixel region. Taken (& modified) from John Fremlin's retinex implementation."""
# Convert back to unsigned integer
byte_image = scipy.array((imagearr).round(0),scipy.uint8)
self.pr = drawable.get_pixel_rgn(self.origin_x, self.origin_y, self.selection_width, self.selection_height, True)
assert(byte_image.size == self.selection_width * self.selection_height * self.bpp)
# pixel region needs absolute coordinates!
self.pr[self.origin_x:self.endpoint_x,self.origin_y:self.endpoint_y] = byte_image.tostring()
def superarray(self):
"""Create a 'superarray' containing all layers in an additional axis"""
# axis (0) is layer index
# create a superarray for all layers, always assume alpha channel (-> '4')
# acccess the full layer on axis 0
sarr=scipy.zeros((len(self.layerlist),self.selection_height,self.selection_width,4))
gimp.progress_init("Reading layers...")
local_step=self.step
for layer in self.layerlist:
# read each layer into a subarray of superarray
sarr[self.layerlist.index(layer)]=self.read_in(layer)
gimp.progress_update(self.step)
local_step+=self.step
self.sarray=sarr
return
def winsorize_superarray(self):
"""Cut extreme values from color channels"""
gimp.progress_init("Winsorizing...")
self.sarray.sort(0)
if self.cutoff_side==2:
self.sarray=self.sarray[self.values_to_cut:-self.values_to_cut]
elif self.cutoff_side==0:
self.sarray=self.sarray[self.values_to_cut:]
elif self.cutoff_side==1:
self.sarray=self.sarray[:-self.values_to_cut]
gimp.progress_update(1)
return
def calculate_mean(self):
"""Return mean array depending on mean type"""
if self.values_to_cut>0: self.winsorize_superarray()
if self.meantype == "arith":
self.layername="Arithmetical Mean"
gimp.progress_init("Calculating "+self.layername)
self.meanarray=scipy.mean(self.sarray,0)
gimp.progress_update(1)
return
elif self.meantype == "geom":
self.layername="Geometrical Mean"
gimp.progress_init("Calculating "+self.layername)
self.meanarray=gmean(self.sarray)
gimp.progress_update(1)
return
elif self.meantype == "harm":
self.layername="Harmonic Mean"
gimp.progress_init("Calculating "+self.layername)
self.meanarray=hmean(self.sarray)
gimp.progress_update(1)
return
elif self.meantype == "median":
self.layername="Median"
gimp.progress_init("Calculating "+self.layername)
self.meanarray=median(self.sarray, 0)
gimp.progress_update(1)
return
elif self.meantype == "mode":
self.layername="Mode"
gimp.progress_init("Calculating "+self.layername)
self.meanarray=mode(self.sarray)[0]
gimp.progress_update(1)
return
def export(self):
"""Export new layer or new image"""
gimp.progress_init("Exporting...")
if self.as_new_image:
new_image=gimp.Image(self.image_width,self.image_height,RGB)
# Convenience: Set filename of new image to layer name and use same file extension & path as original file
if self.image.filename: # this only works if the original image has a file name, i.e. has been saved earlier
path=os.path.split(self.image.filename)[0]
ext=self.image.filename.split('.')[-1]
new_image.filename=os.path.join(path,self.layername+'.'+ext)
else:
new_image.filename=self.layername
#Create layer, image and display
meanlayer=gimp.Layer(new_image, self.layername, self.image_width, self.image_height)
meanlayer.add_alpha()
#fill with transparency
pdb.gimp_drawable_fill(meanlayer, 3)
self.write_out(meanlayer, self.meanarray)
new_image.add_layer(meanlayer)
disp=gimp.Display(new_image)
else:
meanlayer=gimp.Layer(self.image, self.layername, self.image_width, self.image_height)
meanlayer.add_alpha()
#fill with transparency
pdb.gimp_drawable_fill(meanlayer, 3)
self.write_out(meanlayer, self.meanarray)
self.image.add_layer(meanlayer)
self.image.raise_layer_to_top(meanlayer)
gimp.progress_update(1)
return
def do_the_work(image, drawable, meantype, as_new_image, only_visible_layers, cutoff, side):
"""Main function, should be named 'main', but this name is already used by the gimp-python interface"""
working_class=Averager(image,meantype,as_new_image,only_visible_layers, cutoff, side)
#pdb.gimp_message("%i out of %i values per Pixel are used." % (working_class.values_used, len(working_class.layerlist)))
working_class.superarray()
#pdb.gimp_message("superarray shape: %s" % working_class.sarray.shape)
working_class.calculate_mean()
#pdb.gimp_message("new superarray shape: %s" % working_class.sarray.shape)
working_class.export()
return
#def tester():
#"""make testing easier"""
#img=gimp.image_list()[-1]
#draw=img.layers[0]
#do_the_work(img,draw,"arith",1,1,30,0)
register(
"average_layer",
_("Compute average layer v1.1"),
_("Compute average layer offering different statistical approaches.\n Arithmetical mean with cutoff equals winsorized mean.\n Try to use cutoff on both sides with different means \n to remove unwanted objects in a series of photographs.\n Mode is quite slow, but works (Take your time!)."),
"Elmar W. Hoefner",
"Licensed under GPLv3",
"May 2008",
"A_verage Layer",
"RGB*, GRAY*",
[
[PF_IMAGE, "image", "Input image", None],
[PF_DRAWABLE, "drawable", "Input drawable", None],
[PF_RADIO, "meantype", "Choose mean method", "arith", (
("A_rithmetical", "arith"),
("_Geometrical", "geom"),
("_Harmonic","harm"),
("M_edian","median"),
("_Mode", "mode"))],
[PF_TOGGLE, "as_new_image", "Paste as new image", 1],
[PF_TOGGLE, "only_visible_layers", "Only use visible layers", 1],
[PF_SPINNER, "cutoff", "Cut off extreme values (percent).\n At least one value is left, \npercentage is rounded (floor) to whole layers", 10, (0,100,5)],
[PF_SPINNER, "side", "Cut off side (dark (0), bright (1), both(2))", 2, (0,2,1)]
],
[],
do_the_work,
menu="<Image>/Filters/Generic/A_verage Layer")
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment