Python "it" Scripting
Histogram


return to main index


Introduction

Histograms displayed by digital cameras and applications such as PhotoShop offer a convenient way of judging the distribution of brightness in an image. The python code presented in lising 1 adds a new item to the Commands menu of 'it', figure 1, that will display a histogram of the current catalog image in a Firefox browser.



Figure 1


The Histogram command saves, in a directory named "histogram" in the current Maya project folder, a web page and two images. After saving the files the Histogram command opens the web page using Firefox. For example, the histogram of the rendered image shown in figure 1 can be viewed here,
    histogram.html.

A histogram of the same image displayed by the PhotoShop's,
    Image->Adjustments->Levels
menu is shown next. The histogram generated by the Histogram python code will, in general, closely match Photoshop's "Levels" of the same image.



Figure 2


Adding the Histogram Command to "it"

Information on how to add the Histogram command to "it" can be found in,
    "Python "it" Scripting: Adding to the Commands Menu"
An example of how the command is automatically loaded by "it" can be also found in,
    "RfM: Customizing"



Listing 1 (Histogram.py)


# A utility command for displaying an histogram of the current image.
# Colored images are internally converted to an illuminace grayscale
# before the IceMan.Histogram() operator generates the data used to
# draw a bar graph. The shape of the graph displayed in 'it' will 
# match a PhotoShop histogram generated by the,
#    Image->Adjustments->Levels menu
# Unlike PhotoShop this utility makes no attempt to apply differential
# vertical scaling in order to avoid excessively "flat" bar graphs.
#
# M.Kesson Aug 25 2014
# Feb 19 2016: Added html output.
# Sep 12 2016: Added openInFirefox(). 
  
import math,it,ice,os,time,sys,subprocess
import struct
from it.It3Command import It3Command
  
class Histogram(It3Command):
    def __init__(self):
        It3Command.__init__(self)
        self.m_menuPath = 'Commands/Histogram'
  
    def Invoke(self):
        self.histogram()
        
    def histogram(self):
        try:
            elem = it.GetCurrentElement()
        except:
            it.app.Warning('The Histogram command cannot be used without an image.')
            it.app.RaiseLogWindow()
            return
        srcImage = elem.GetImage()
            
        # Histogram graphic____________________________________    
        w,h = self.getDimensions(srcImage)
        h_totalPixels = w * h
        # Light gray background
        bg_image = ice.FilledImage(ice.constants.FLOAT,[0,512,0,256],[0.9,0.9,0.9])
        # Dark gray bars
        bar_color = ice.Card(ice.constants.FLOAT, [0.24,0.24,0.24,1])
        bar_width = 2
            
        # Histogram values_____________________________________
        data = srcImage.Histogram(256, [0,1])
        gray = []
        max_bar_num = 0
        num_components = len(data)
        if num_components > 3:
            num_components = 3
        for n in range(len(data[0])):
            if num_components == 3:
                num = data[0][n] * 0.21 + data[1][n] * 0.72 + data[2][n] * 0.07
            else:
                num = data[0][n]    
            gray.append(num)
            if num > max_bar_num:
                max_bar_num = num
                
        # Bar Chart Outline___________________________________
        verts = [(0,0)]
        for n in range(len(gray)):
            # Scaling by 220 pixels ensures the tallest bar will not touch 
            # the top edge of the background.
            y = (float(gray[n])/max_bar_num) * 220
            x = n * bar_width
            verts.append( (x,y) )
            verts.append( (x + 1,y) )
        verts.append( (512,0) )    
        bars = ice.PolyFill(bar_color, verts, [0,0]) # zero filtering
  
        # Compositing________________________________________
        bg_image = bg_image.Over(bars)
        bg_image = bg_image.Flip(False, True, False) # flip vertically
        # Now the web page is automatically opened there is no point in
        # displaying the image in "it".
        #it.AddImage(bg_image)
        
        path = self.makeOutDirectory()
        imagepath = os.path.join(path, 'src_rgb.jpg')
        self.saveImage(srcImage, imagepath, 2.2)
        
        #hsv_image = srcImage.RGBToHSV()
        gray = srcImage.RGBToHSV().Shuffle([2])
        imagepath = os.path.join(path, 'src_gray.jpg')
        self.saveImage(gray, imagepath, 2.2)
        
        htmlpath = os.path.join(path, 'histogram.html')
        it.app.Info('Saving histogram.html "%s"' % htmlpath)
        #it.app.RaiseLogWindow()
        self.saveHtml(htmlpath, verts)
        self.openInFirefox(htmlpath)
    # http://www.cyberciti.biz/faq/howto-run-firefox-from-the-command-line/
    # open -a firefox -g http://news.google.com
  
    #-------------------------------------------------
    # openInFirefox
    #-------------------------------------------------
    def openInFirefox(self, fullpath):
        if os.name == "posix":
            os.chmod(fullpath, 0777)
            if sys.platform == 'darwin': # MacOSX
                args = ['open', '-a', 'firefox', fullpath]
            else:    # linux
                args = ['firefox', fullpath]
            subprocess.Popen(args,stdout=subprocess.PIPE)
        else:    # windows
            args = ['cmd','/C','start','firefox', fullpath]
            subprocess.call(args, shell=True)
    
    #-----------------------------------------------------
    # Returns width and height
    def getDimensions(self, image):
        box = image.DataBox()
        return [box[1] - box[0] + 1, box[3] - box[2] + 1]
        
    def saveImage(self, image, path, gamma):
        image = image.Gamma(gamma);
        image.SetMetaDataItem('JPEG_QUALITY', 100)
        image.Save(path, ice.constants.FMT_JPEG)
        
    def makeOutDirectory(self):
        outname = 'histogram'
        cwdpath = os.getcwd()
        if len(cwdpath) < 3:
            cwdpath = os.environ['HOME']
        out_dirpath = os.path.join(cwdpath, outname)
        if not os.path.exists(out_dirpath):
            os.mkdir(out_dirpath)
        return out_dirpath
        
    def saveHtml(self, path, verts):
        f = open(path, 'w')
        f.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"\n')
        f.write(' "http://www.w3.org/TR/REC-html40/loose.dtd">\n')
        f.write('<HTML><HEAD><TITLE>Histogram</TITLE></HEAD>\n')
        f.write('<BODY BGCOLOR="#ffffff">\n')
        f.write('<BR><BR><BR><BR><BR><BR><BR>\n')
        f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
        f.write('    <TR>\n')
        f.write('        <TD width="50" NOWRAP> </TD>\n')
        f.write('        <TD NOWRAP>\n')
        f.write('            <img src="src_rgb.jpg" onmouseover="this.src=\'src_gray.jpg\'" onmouseout="this.src=\'src_rgb.jpg\'" alt="" width=520 border="0">\n')
        f.write('            <BR><BR><BR>\n')
        f.write('        </TD>\n')
        f.write('    </TR>\n')
        f.write('</TABLE>\n')
        f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
        f.write('    <TR>\n')
        f.write('        <TD width="50" NOWRAP> </TD>\n')
        emptyBinCount = 0.0;
        nonEmptybinCount = 0.0
        for n in range(0,len(verts),4):
            hexstr = self.getHexStr( float(n)/ (len(verts) * 1.1))
            try:
                vert_1 = verts[n]
                vert_2 = verts[n+1]
                vert_3 = verts[n+2]
                vert_4 = verts[n+3]
                y = (vert_1[1] + vert_2[1] + vert_3[1] + vert_4[1])/4
            except:
                y = 0
            if y == 0:
                emptyBinCount += 1.0
                hexstr = "ff0000"
            else:
                nonEmptybinCount += 1.0
            if y < 2:
                y = 2
            f.write('        <TD valign="bottom" NOWRAP>\n')
            f.write('            <div style="width:2px;height:%dpx;background-color:#%s"></div>\n' % (y,hexstr))
            f.write('        </TD>\n')
        dynamicRangeNotUsed = emptyBinCount / (emptyBinCount + nonEmptybinCount)
        f.write('    </TR>\n')
        f.write('</TABLE>\n')
        f.write('<TABLE BORDER="0" CELLSPACING="0">\n')
        f.write('    <TR>\n')
        f.write('        <TD width="50" NOWRAP> </TD>\n')
        f.write('        <TD NOWRAP>\n')
        f.write('             <BR><P>Dynamic range <B>not</B> used: %1.1f percent.</P>\n' % (dynamicRangeNotUsed * 100) )
        f.write('        </TD>\n')
        f.write('    </TR>\n')
        f.write('</TABLE>\n')
        f.write('</BODY></HTML>\n')
        f.close()
    
    def getHexStr(self, percent):
        val = int(16 * percent)
        return hex(val).split('x')[1] * 6
        
        
# Add the new menu item
it.commands.append(Histogram)






© 2002- Malcolm Kesson. All rights reserved.