Yesterday I decided to implement the retinex algorithm described by
John McCann in 1999 as a gimp plugin.

I am using Python (in particular numpy for the main calculations) and
consequently chose to put in some modifications to the algorithm to
make it more efficient, but it makes generally the same effect as that
described in

   Brian Funt, Florian Ciurea, and John McCann "Retinex in Matlab," Proceedings 
of the IS&T/SID Eighth Color Imaging Conference: Color Science, Systems and 
Applications, 2000, pp 112-121.

   http://www.cs.sfu.ca/~colour/publications/IST-2000/index.html

As this is my first foray into gimp plugging in, I'd appreciate if
someone could look over the code. Is there a more efficient way of
getting out one colour channel of the image at a time? At the moment I
read in the whole image which takes a lot of memory.

Searching the archives today I notice that Pedro Paf was suggesting
implementing it as a Summer of Code project - as the algorithm is very
simple (a day to make even starting no numpy or Gimp python
knowledge), what enhancements where being contemplated?

#! /usr/bin/env python

#   Implementation of a retinex algorithm similar to that described by
#   John McCann in 1999

#   For more information about the algorithm see http://www.cs.sfu.ca/~colour/publications/IST-2000/index.html
#   Brian Funt, Florian Ciurea, and John McCann "Retinex in Matlab," Proceedings of the IS&T/SID Eighth Color Imaging Conference: Color Science, Systems and Applications, 2000, pp 112-121.

#   Copyright (C) 2007 John Fremlin
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import gimp
from gimpfu import *

import numpy
import scipy
import scipy.ndimage
import struct
import Image

gettext.install("gimp20-python", gimp.locale_directory, unicode=True)

small_amount = 1/1024.0
difference_from_neighbours_kernel = numpy.array([ [-1, -1, -1], [-1,8,-1], [-1,-1,-1]],"d")
global_logscale = True

def shrink(chan,scale):
    return scipy.ndimage.zoom(chan,1/float(scale),prefilter=False,order=5)

def image_clip(chan):
    if global_logscale:
        return chan.clip(-numpy.inf,0.0)
    else:
        return chan.clip(0.0,1.0)

def retinex_at_scale(retinex,orig,scale):
    assert(orig.size == retinex.size)
    working = orig
    diff = scipy.ndimage.convolve(working,difference_from_neighbours_kernel)
    result = (retinex + diff)/2
    working = (retinex + image_clip(result))/2

    return working

def resize(chan,new_size):
    orig = chan.shape
    zoom = [((new+0.9)/float(old)) for old, new in zip(orig, new_size)]
    ret = scipy.ndimage.zoom(chan,zoom,prefilter=False,order=5)
    assert(new_size == ret.shape)
    return ret

def process_one_channel(chan):
    retinex = numpy.array([[chan.mean()]],"d")
    
    for logscale in range(int(numpy.log2(min(*chan.shape))),-1,-1):
        scale = 1 << logscale
        orig = shrink(chan,scale)
        retinex = retinex_at_scale(resize(retinex,orig.shape),orig,scale)
    return retinex
#    return numpy.abs(chan-retinex)

def retinex(image):
    if global_logscale:
        image = numpy.log(image+small_amount)


    [ width, height, channels ] = image.shape
    for c in range(channels):
        gimp.progress_update(c/channels)
        image[:,:,c] = process_one_channel(image[:,:,c])

    if global_logscale:
        image = numpy.exp(image)-small_amount

    return image

def progress_update(stage,proportion_done):
    gimp.progress_update(proportion_done*0.10)

def read_in(drawable):
    width = drawable.width
    height = drawable.height
    bpp = drawable.bpp
    pr = drawable.get_pixel_rgn(0, 0, width, height, False)
#     image = numpy.zeros((height,width,3),"d")
#     for y in range(height):
#         for x in range(width):
#             image[y,x,:] = struct.unpack('BBB',pr[x,y][0:3])
            
#         progress_update("read-in", (y / float(height)))

    a = numpy.fromstring(pr[:,:],"B")
    assert(a.size == width * height * bpp)
    image = numpy.array(a.reshape(height,width,bpp),"d")[:,:,0:min(bpp,3)]
    return image/256.0



def write_out(drawable,image):
    byte_image = numpy.array((image*256).round(0),"B")
    width = drawable.width
    height = drawable.height
    bpp = drawable.bpp

    pr = drawable.get_pixel_rgn(0, 0, width, height, True)
    assert(byte_image.size == width * height * bpp)
    pr[:,:] = byte_image.tostring()

#     for y in range(height):
#         for x in range(width):
#             pr[x,y] = struct.pack('BBB',*byte_image[y,x,:])
            
#         progress_update("write-out", (y / float(height)))

    

def retinex_plugin(img, drawable, new_layer_name, opacity, logscale, flatten):
    img.undo_group_start()
    try:
        global_logscale = logscale
        image = read_in(drawable)

        image = retinex(image)

        new_layer = gimp.Layer(img, new_layer_name, drawable.width, drawable.height, RGB_IMAGE,
                               opacity, NORMAL_MODE)
        write_out(new_layer,image)
        img.add_layer(new_layer,0)
        
        if flatten:
            img.flatten()

    finally:
        img.undo_group_end()
        

register(
    "mccann99-retinex",
    N_("Lighten the dark areas of an image while preserving detail"),
    "Performs a retinex algorithm similar to that described by John McCann in 1999",
    "John Fremlin",
    "John Fremlin",
    "2007",
    N_("McCann99 Retinex..."),
    "RGB*",
    [
        (PF_IMAGE, "image", "Input image", None),
        (PF_DRAWABLE, "drawable", "Input drawable", None),
        (PF_STRING, "name",       _("New _layer name"), _("Retinex")),
        (PF_SLIDER, "opacity",    _("Op_acity"),    25, (0, 100, 1)),
        (PF_BOOL, "logscale", _("Lo_garithmic brightness manipulation"), True),
        (PF_BOOL, "flatten", _("_Flatten layers together after processing"), True)
    ],
    [],
    retinex_plugin, 
    menu="<Image>/Filters/Enhance",
    domain=("gimp20-python", gimp.locale_directory)
    )

main()
_______________________________________________
Gimp-developer mailing list
Gimp-developer@lists.XCF.Berkeley.EDU
https://lists.XCF.Berkeley.EDU/mailman/listinfo/gimp-developer

Reply via email to