Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Thursday, February 1, 2024

Yolo AutoCropping Presentation Videos


 

As camera resolutions continue to improve the feasibility of capturing a full scene of a classroom, lecture, presentation hall, or the such and autonomously focusing attention on the presenter becomes more practical.  Generally, a camera operator pans and zooms in on the presenter as they make their way around the stage to draw the audience attention to the intended target.  Professionally filmed videos draw the audiences attention to the speaker and their production quality contributes to a more informative presentation.

Wide-angle, static camera positions are an alternative for capturing presentations but generally fail to draw the audience attention to the speaker.  With robust object-detection, the position of the presenter can be automated and thru the use of auto-cropping the presenter can offer a budget-friendly alternative to more professional video production facilities.


YOLO (You Only Look Once) takes a different approach from classic computer vision by utilizing a classifier as a detector.  Authored by Joseph Redmon at the University of Washington, YOLO sub-samples and image into regions, assumes each region has an object and executes a classifier on each region, then merges the classifier groups into a list of final objects.  

 

Below is a proof-of-concept utilizing YOLO in an auto-cropping manner.  The wide-angle source video is used as input, object-detection is focused on the front of the room, detects the presenter and auto-cropped around the presenter.  Once the presenter location is available, we use a variety of means to 'pan the camera', the first by snapping to the presenter location, the second by smoothing the camera motion by incorporating a 2-dimensional shaper, the third using the shaper but only moving the camera when the presenter nears the edges of the current crop window.

Each mechanism is a rough implementation, focused on rapid proof-of-concept rather than optimal results, but you get the idea.  


The source video was found on here; Minnebar7

 

 



Tuesday, January 30, 2024

Published My First Python Package


 

 

I started dabbling with Python back in 2012'ish, using it pretty regularly over the years but generally keeping my projects close to home.  Recently, I dipped my toe into publishing a Python package, out to the known universe.

Back in the late 90's, the Precambrian Digital Age, I took a couple courses that continue to pique my interest time and time again.  Parallel processing was primarily constrained to supercomputers like the Cray-1 that was homed in a nearby lab on campus, on full display behind a full-glass wall.  A workhorse which eagerly awaited computationally intensive parallelized programs. 

A customized version of Fortran, its vocabulary, the Computer Science department rarely used it, aerospace and atmospheric sciences most heavily used the system.

The second course, Distributed Operating Systems, taken a bit later seemed to pair well with this budding interest in high performance computing.  Beowulf clusters, commodity-grade networked computers running Linux, could be created from RadioShack-provided equipment fueled by inspiration.   Cloud computing, virtual machines and even network-intensive applications hadn't breached the digital horizon, but small-cluster networked labs provided inspiration that one day multitudes of computing assets would one day join hands in forming highly networked, parallel, distributed systems that can be considered common today.

While robust and reliable distributed systems are highly sought after, engineering them is plagued with challenges.  Failed requests could be due to loss of the sent message, the loss of the response, the destination service abruptly terminating, relocation of the service, a over-tasked memory/cpu that slows the response,....or any number of other factors.  Python and ZeroMQ pair well to allow the creation of a distributed system framework which inspired my budding project.

dividere UG

The public project repository is located at:

https://github.com/lipeltgm/dividere


This is my first cut at publishing a python package, I tried to apply good design, test and documentation principles along the way.  One particular challenge I encountered is that the package dependencies require a version of Protobuf that isn't currently available via 'normal channels'.  I'm hoping in time that complication will self-correct when compliant versions become the default.

Until then, it likely will require manual installation of protobuff-v3.19 (or later) before installing via pip3 from pypi:

https://pypi.org/project/dividere/ 

$ pip3 install dividere


With the foundation in place, I'm intending on extending the framework to support more reliable messaging, database components, robust failover detection and recovery.  

More to come in the future, fingers-crossed.


Tuesday, March 16, 2021

Data Visualization with PyGal -- Pymntos

Recently presented 'Data Visualization with PyGal' to the Python Mn Meetup group https://www.meetup.com/PyMNtos-Twin-Cities-Python-User-Group/

A pretty remarkable group, full of very knowledgeable and supportive individuals.  I'd highly recommend attending this meetup if you're interested in Python, ranging from beginner to advanced.  

I slapped together a video on YouTube for your amusement;


Slides and code segments are available at our GitHub repo: https://github.com/fsk-software/pub

Enjoy.

Saturday, March 13, 2021

Colorizing Text with Python

 

Let me tell you a story, a story of struggles and challenges, a drab colorless life until one day a gleaming figure steps out of the mist and introduces me to the rainbow of colors that are......

Actually, it's just a short post of how to add a little color to your Python console output.

Let's start with a class definition, one that defines the escape key sequences necessary for adding color to simple text.  The general format is encapsulating the text within a start and end key sequence.  The string starts with a header, followed by the color sequence, then the actual string, followed by a trailer sequence that indicates we're done with the key sequence.

 

$ cat -n Color.py
     1    #!/usr/bin/python
     2   
     3    class Color:
     4      Header='\033[95m';
     5      Trailer='\033[0m';
     6      Default      = '\033[39m';
     7      Black        = '\033[30m';
     8      Red          = '\033[31m';
     9      Green        = '\033[32m';
    10      Yellow       = '\033[33m';
    11      Blue         = '\033[34m';
    12      Magenta      = '\033[35m';
    13      Cyan         = '\033[36m';
    14      LightGray    = '\033[37m';
    15      DarkGray     = '\033[90m';
    16      LightRed     = '\033[91m';
    17      LightGreen   = '\033[92m';
    18      LightYellow  = '\033[93m';
    19      LightBlue    = '\033[94m';
    20      LightMagenta = '\033[95m';
    21      LightCyan    = '\033[96m';
    22      White        = '\033[97m';
    23   
    24      Bold='\033[1m';
    25      Underline='\033[4mm';
    26   
    27      @staticmethod
    28      def colorize(val,color):
    29        colorVal=eval('Color.%s'%(color));
    30        retVal="%s%s%s%s"%(Color.Header,colorVal,val,Color.Trailer);
    31        return retVal;

 

The 'colorize' method takes in a string and a color, returns a string sequence. As an alternative, you can use the key sequences explicitly.  This short example demonstrates both possibilities.

 $ cat -n foo
     1    #!/usr/bin/python
     2    #-- https://godoc.org/github.com/whitedevops/colors
     3    
     4    from Color import Color;
     5    
     6    print "Using explicit control characters";
     7    print " " + Color.Header + Color.Red + "Red" + Color.Trailer;
     8    print " " + Color.Header + Color.Green + "Green" + Color.Trailer;
     9    print " " + Color.Header + Color.Blue + "Blue" + Color.Trailer;
    10    
    11    for color in ['Black', 'Red', 'Green', 'Yellow' , 'Blue', 'Magenta', 'Cyan', 'LightGray', 'DarkGray', 'LightRed', 'LightGreen', 'LightYellow', 'LightBlue', 'LightMagenta', 'LightCyan', 'White']:
    12      print " > %s"%(Color.colorize(color,color));
    13    


Now, go slap some color in your boring old programs!


 

Tuesday, September 22, 2020

Generating Multi-Plot Real-Time Plots with Python


In an earlier post the real-time plotting capabilities were demonstrated, we're extending on this by showing how to generate multiple plots simultaneously.  A couple noteworthy observations, in the past post the X and Y scaling was automatically scaled after each element addition.  While you can still do this, typically for multiplots we would prefer maintaining a shared X range.  While somewhat unnecessary, I've elected to maintain a uniform Y range.




#!/usr/bin/python
from pylab import *;
import time;

def log(M):
  print "__(log) " + M;

def test02():
  plt.ion();
  fig=plt.figure(1);
  ax1=fig.add_subplot(311);
  ax2=fig.add_subplot(312);
  ax3=fig.add_subplot(313);
  l1,=ax1.plot(100,100,'r-');
  l2,=ax2.plot(100,100,'r-');
  l3,=ax3.plot(100,100,'r-');
  time.sleep(3);

  D=[];
  i=0.0;
  while (i < 50.0):
    D.append((i,sin(i),cos(i),cos(i*2)));
    T1=[x[0] for x in D];
    L1=[x[1] for x in D];
    L2=[x[2] for x in D];
    L3=[x[3] for x in D];

    l1.set_xdata(T1);
    l1.set_ydata(L1);

    l2.set_xdata(T1);
    l2.set_ydata(L2);

    l3.set_xdata(T1);
    l3.set_ydata(L3);

    ax1.set_xlim([0,50]);
    ax2.set_xlim([0,50]);
    ax3.set_xlim([0,50]);
    ax1.set_ylim([-1.5,1.5]);
    ax2.set_ylim([-1.5,1.5]);
    ax3.set_ylim([-1.5,1.5]);

    plt.draw();
    i+=0.10;
  show(block=True);

#---main---
log("main process initializing");
test02();
log("main process terminating");

Easy Peasy;



Tuesday, August 25, 2020

Software System Forensics -- Auto Generated Message Trace Diagrams


Understanding an existing software system can be a daunting task.  Diving head-first into a source code repository with the objective of gaining a system understanding can be particularly challenging.  Taking the high-dive into source code often results in crawling down a variety of rabbit holes that may or may not be of particular relevance.  It's not uncommon for software to have edge cases and/or 'dead code' that while are compiled into the release are rarely (or ever) executed due to run-time constraints.  But, really, what are the alternatives?

Whelp friends, what if you could execute a software system, gather method calls, w/caller and callees, and create a visual representation of the process flow?  That will be the topic of this particular blog post.

Let's introduce our team:

Our power forward; the hustle with the muscle, the beta with aaaalllllll the data.....GDB.


At point guard; the mate that will translate, the teammate that will update....your buddy and mine...Python.

And rounding out the crew, a battering ram of a diagram....WebSequenceDiagram.  


That's our roster; GDB to collect caller/callee information, Python to convert GDB output into something that can be used to generate a visual diagram, and WebSequenceDiagram to create the diagram.  This particular team has proven to be quite beneficial when I've been tossed into the deep end of the pool without my water-wings.  Let's work through a simple example;

Behold, an overly simple software system source file:
$ cat -n main.cpp 
     1 #include <stdio.h>
     2
     3 class C
     4 {
     5   public:
     6     C() { }
     7     void beak();
     8     void flap();
     9     void shake();
    10     void clap();
    11 };
    12
    13 void C::beak() {}
    14 void C::flap() {}
    15 void C::shake() {}
    16 void C::clap() {}
    17
    18 class B
    19 {
    20   public:
    21     B():c_() { }
    22     void stepOnce();
    23   private:
    24     C c_;
    25 };
    26
    27 void B::stepOnce() { c_.beak(); c_.flap(); c_.shake(); c_.clap(); }
    28
    29 class A
    30 {
    31   private:
    32     B b_;
    33   public:
    34     A():b_() { }
    35     void run();
    36 };
    37 void A::run() { for(int i=0; i<10; ++i) b_.stepOnce(); }
    38
    39 int main()
    40 {
    41   printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
    42   A obj;
    43   obj.run();
    44   printf("(%s:%d) main process terminating\n",__FILE__,__LINE__);
    45 }

Even the most modest of software engineers can peek at this code and understand it without the need for any advanced tools, but this process of capturing debug info and transforming it into a sequence diagram works for far more complicated systems, frankly it's saved me hours and hours of tracing through source code.  Fred R. Barnard may have not been a software engineer, but he just as well could have been when he coined the phrase "a picture is worth a thousand words".  

So, that's our system, let's turn our attention to GDB.  We'll author a GDB command script which will perform all the heavy lifting; we'll enable logging, write gdb info to a gdb.log file, set up breakpoints in methods we are particularly interested in (e.g. class A, B, C), the breakpoints will print the backtrace and release the process to continue.  The backtraces saved in the gdb log file will be used to extract the caller/callee methods for our diagram.
$ cat -n gdb.cmd 
     1 set pagination off
     2 set logging file ./gdb.log
     3 set logging overwrite on
     4 set logging on
     5
     6 define MyTrace
     7   bt 2
     8   cont
     9 end
    10
    11 break main
    12 commands
    13   rbreak ^A::
    14     commands
    15       MyTrace
    16   end
    17   
    18   rbreak ^B::
    19     commands
    20       MyTrace
    21   end
    22   
    23   rbreak ^C::
    24     commands
    25       MyTrace
    26   end
    27   
    28   cont
    29 end
    30
    31 run
    32 quit

Armed with the gdb command script, we simply run our main process under gdb as follows:
$ gdb --batch -x ./gdb.cmd ./main 2> /dev/null

When the process terminates, we have a gdb.log file that takes the form:
$ more gdb.log 
Breakpoint 1 at 0x40063c: file main.cpp, line 40.

Breakpoint 1, main () at main.cpp:40
40 {
Breakpoint 2 at 0x4006e4: file main.cpp, line 34.
void A::A();
...
Breakpoint 2, A::A (this=0x7fffffffdc77) at main.cpp:34
34     A():b_() { }
#0  A::A (this=0x7fffffffdc77) at main.cpp:34
#1  0x0000000000400670 in main () at main.cpp:42

Breakpoint 4, B::B (this=0x7fffffffdc77) at main.cpp:21
21     B():c_() { }
#0  B::B (this=0x7fffffffdc77) at main.cpp:21
#1  0x00000000004006f0 in A::A (this=0x7fffffffdc77) at main.cpp:34

Breakpoint 6, C::C (this=0x7fffffffdc77) at main.cpp:6
6     C() { }
#0  C::C (this=0x7fffffffdc77) at main.cpp:6
#1  0x00000000004006d4 in B::B (this=0x7fffffffdc77) at main.cpp:21

Breakpoint 3, A::run (this=0x7fffffffdc77) at main.cpp:37
37 void A::run() { for(int i=0; i<10; ++i) b_.stepOnce(); }
#0  A::run (this=0x7fffffffdc77) at main.cpp:37
#1  0x000000000040067c in main () at main.cpp:43

Since we created breakpoints for all our class A,B,C methods, hitting one will produce a backtrace depth of 2, the caller(#1) and the callee(#0).  Since the stack trace has the class name and method, we have sufficient info to create a sequence diagram, we just have to parse the gdb log file and extract the info.

Python is an amazing tool for file processing/parsing and the one we'll be using.  We will use some regex magic and string commands to transform the gdb raw output into a text file similar to this: 
$ cat -n mtd.txt
     1 main -> A:A()
     2 A -> B:B()
     3 B -> C:C()
     4 main -> A:run()
     5 A -> B:stepOnce()
     6 B -> C:beak()
     7 B -> C:flap()
     8 B -> C:shake()
     9 B -> C:clap()
This string format, <object> -> <class>:<method>(), is compliant with Web Sequence Diagram, simply copy-n-pasting in the contents into the web-app will produce magic.  More on that later, let's turn our head toward the necessary Python script.
$ cat -n mkMtd 
     1 #!/usr/bin/python
     2 import re;
     3 import sys;
     4
     5 # https://www.websequencediagrams.com/
     6
     7 def methodName(S):
     8   retVal="";
     9   m1=re.search(".+ (.+)::(.+)\((.+)\)",S);
    10   if m1:
    11     retVal="%s:%s()"%(str(m1.group(1).strip()),str(m1.group(2).strip()));
    12   else:
    13     m2=re.search(".+ in (.+)\(.*\) (.+)",S);
    14     if m2:
    15       cName=' '.join(m2.group(2).split(' ')[1:]).split('.')[0];
    16       retVal="%s:%s"%(cName, str(m2.group(1)));
    17     else:
    18       m2=re.search(".+ (.+)\(.*\) at (.+)",S);
    19       cName=m2.group(2).split(".")[0];
    20       retVal="%s:%s"%(cName, str(m2.group(1)));
    21   return retVal;
    22
    23 def parseDebugOutput(fileName):
    24   with open(fileName, 'r') as fp:
    25     C=fp.read();
    26   lastLine=(None,None);
    27   noDupCallMap=dict();
    28   for line in C.split('\n'):
    29     callerX=re.search("#0 .*",line);
    30     if callerX:
    31       m1=methodName(line);
    32     calledX=re.search("#1 .*",line);
    33     if calledX:
    34       m2=methodName(line);
    35       mtdLine="%s -> %s"%(m2.split(':')[0],m1);
    36       print mtdLine;
    37
    38 inFile=sys.argv[1];
    39 parseDebugOutput(inFile);

You run this delicious little bastard as follows:

$ ./mkMtd ./gdb.log

And it spits out Web Sequence Diagram compliant input commands;

Export the results into a PNG and you can include it in your design documentation;

With a bit of additional work, the diagram creation could be also automated by using the code from a previous post: https://dragonquest64.blogspot.com/2020/05/python-generated-sequence-diagrams.html

It's worth noting that while this method have time-and-time again proven useful to me, it presents a specific challenge;
You're likely to use this on a sophisticated system, one with dozens of classes, hundreds of methods and setting a breakpoint in each of them is technically possible, your diagram will quickly become an eye-sore.  The challenge is carving out the uninteresting methods from the breakpoints or the gdb log file and that process can be time-consuming.  I'd argue, not as time-consuming as spending dozens of hours browsing source code, but it will take a time investment of trial-n-error.  So, be prepared to spend some time on that.

I've used this technique in multi-process systems (capturing and displaying message entry/exit points), investigated in-memory DB accesses (during system initialization) and executed this capture/analysis on specific user scenarios.  It's an incredibly useful technique, produces valuable information, but takes some fine-tuning to find the right balance in breakpoint/method captures.

Cheers.


Tuesday, July 7, 2020

Mandelbrot Set with Python



Data can be beautiful.  Visualizing data is a worthwhile skill to acquire and it's relatively simple with Python.  Let's explore how to do some data visualization as an exercise.

The Mandelbrot set is often regarded as an example of art meeting science.  It's generated by evaluating the behavior of complex numbers and generating the results.  The end effect is a infinite and beautiful depiction of pure mathematics, an acid-trip of color and structure as you continuously zoom into the graph.

One of the best descriptions of the Mandelbrot set can be found here, I recommend you spend a few minutes to appreciate the concept before we begin graphing it with a simple Python snippet.

In about 30 lines of code we can create our own colorized visualization of the mandelbrot set.

Let's look at the source, then step into some of the details;
$ cat -n mandelbrot 
     1 #!/usr/bin/python
     2 import matplotlib.pyplot as plt;
     3 import sys;
     4
     5 def colorize(n):
     6   h="#%06x"%(int(n*2**23));
     7   return h;
     8
     9 xRange=[-2,1];
    10 yRange=[-1.5,1.5];
    11 incr=0.005;
    12
    13 x=xRange[0];
    14 while(x < xRange[1]):
    15   y=yRange[0];
    16   while(y < yRange[1]):
    17     c=x+y*1j;
    18     z=0;
    19     try:
    20       for k in range(50):
    21         z=z**2+c;
    22       if(abs(z) < 2):
    23         rgb=colorize(abs(z));
    24         plt.plot(x,y,'.',color=rgb);
    25     except:
    26       pass;
    27     y += incr;
    28   x += incr;
    29
    30 plt.xlim(xRange[0],xRange[1]);
    31 plt.ylim(yRange[0],yRange[1]);
    32 plt.savefig(sys.argv[1]);


Let's look over the non-Mandelbrot stuff first.  We're using the matplotlib library for our simple plotting example, line 9-10 we define our plot x and y ranges and enforce them in lines 30-31.  Finally in line 31 we save the plot to a figure rather than display to the screen.  We pass in the figure filename as a command line argument so ./mandelbrot foo.png would generate a foo.png file.  Lines 23-24 calculates a color for the pixel and plots it at (x,y).  Without going into details, the nested loop (lines 13-28) steps through the floating point 2D range defined by the x and y ranges, stepping by 0.005 (defined in line 11).  Each iteration, we selectively plot, or don't plot, a colorized pixel.  We plot a pixel if it's position is part of the Mandelbrot set.

The rest of the details are specific to calculating the Mandelbrot set.  In particular, lines 17,20-26.  The referenced video explores how and why this is done, but let's revisit some of the particulars.

Line 17 consists of the assignment of the complex number for each (x,y) position in the range.  Note 1j is the Python representation of complex number i.   This assignment was covered in the video, specifically, but easily overlooked;

The inclusion in the mandelbrot set for this position is characterized by how this complex number behaves under the influence of iteration of the function starting at 0.  This means, z starts as 0 and we repeatedly inject the f(z)=z^2+c in our loop, the end result will either blow up implying it's not in the mandelbrot set, or it'll remain bounded (e.g. <= 2) which means it's part of the set.  Lastly, we use the magnitude of the result to determine the color of the pixel.  This is optional, but it adds a level of beauty. We can represent the looping of (x,y) range and determination of it's inclusion/exclusion in the mandelbrot set by the clip from the reference video;

The end result is our visualization;
No go do something cool!

Sunday, May 17, 2020

Python Generated Sequence Diagrams


Understanding a system or software architecture simply, truly, absolutely, undoubtedly requires an occasional sequence diagram.

I left university before object-oriented design became the norm, but it was trending up.  As part of my first job we were officially trained in OO and the use of Rational Rose.  The tool was integrated into our development process and environment, I embraced that tool, particularly in the ability to depict class interactions via 'message trace diagrams', otherwise known as 'sequence diagrams'.  Unfortunately, Rational Rose is pricey and uncommon in today's development practices but sequence diagrams are alive and well.  Whiteboarding diagrams is tedious and time-consuming, but the inter-webs has on-line tools that really ease the creation of diagrams; like this fella right here: https://www.websequencediagrams.com/

With a bit of effort, Python paired with ImageMagick can generate a rough approximation.

A quick tool/library can make this:
  diagram=Diagram();
  obj1=Object('object1');
  obj2=Object('object2');
  obj3=Object('object3');

  m1=Message(obj1,obj2,'method1(arg1)');
  diagram.add(m1);

  m2=Message(obj2,obj1,'return val');
  diagram.add(m2);

  m3=Message(obj1,obj1,'method2(abc)');
  diagram.add(m3);

  m4=Message(obj1,obj3,'method3()');
  diagram.add(m4);

  diagram.draw();

into this:


The code/library follows:
user@kaylee:~/PyMtd$ cat -n drawMtd 
     1 #!/usr/bin/python
     2 import os;
     3
     4 def log(S):
     5   print "__LOG '%s'"%(S);
     6   pass;
     7
     8 class Diagram: 
     9   def __init__(self):
    10     self.outFile_="./out.jpg";
    11     self.width_=0;
    12     self.height_=0;
    13     self.msgList_=[];
    14
    15   def add(self, msg):
    16     self.msgList_.append(msg);
    17
    18   def textDim(self, text):
    19     cmd="convert label:'%s' %s"%(text,'temp.jpg');
    20     os.system(cmd);
    21     cmd="identify temp.jpg | cut -f 3 -d ' '";
    22     retVal=os.popen(cmd).read();
    23     os.system("rm temp.jpg");
    24     return retVal.rstrip('\n');
    25
    26   def draw(self):
    27     betweenObj=100;
    28     objY=20;
    29
    30     #--position object, heads of lifelines
    31     iW=betweenObj;
    32     for msg in self.msgList_:
    33       msg.src_.y_=objY;
    34       msg.sink_.y_=objY;
    35       if msg.src_.x_==0:
    36         msg.src_.x_=iW;
    37         iW+=betweenObj;
    38       if msg.sink_.x_==0:
    39         msg.sink_.x_=iW;
    40         iW+=betweenObj;
    41
    42     #--draw message lines
    43     cmd="convert ";
    44     y=50;
    45     rightArrow="l -15,-5  +5,+5  -5,+5  +15,-5 z"
    46     leftArrow="l +15,+5  -5,-5  +5,-5  -15,+5 z"
    47     for msg in self.msgList_:
    48       src=msg.src_;
    49       sink=msg.sink_;
    50       if src.x_ == sink.x_:
    51         W=30;
    52         H=20;
    53         cmd+="-draw 'line %d, %d %d,%d' "%(src.x_,y, src.x_+W,y);
    54         cmd+="-draw 'line %d, %d %d,%d' "%(src.x_+W,y,src.x_+W,y+H);
    55         cmd+="-draw 'line %d, %d %d,%d' "%(src.x_+W,y+H,src.x_,y+H);
    56         cmd+="-draw \"path \'M %d,%d %s'\" "%(src.x_,y+H,leftArrow);
    57         D=self.textDim(msg.label_);
    58         tH=int(D.split('x')[0])/2;
    59         tW=int(D.split('x')[1])/2;
    60         cmd+="-draw 'text %d,%d \"%s\"' "%(src.x_+W+5,y+((tH)/2), msg.label_);
    61         y=y+H;
    62       else:
    63         cmd+= "-draw 'line %d,%d %d,%d' "%(src.x_,y,sink.x_,y);
    64         cmd+="-draw \"path \'M %d,%d %s'\" "%(sink.x_,y,(leftArrow if src.x_ > sink.x_ else rightArrow));
    65         D=self.textDim(msg.label_);
    66         textWidth=int(D.split('x')[0])/2;
    67         textHeight=int(D.split('x')[1])/2;
    68         cmd+="-draw 'text %d,%d \"%s\"' "%((src.x_+sink.x_)/2-textWidth, y-(textHeight/2), msg.label_);
    69       y+=20;
    70     self.height_=y+50;
    71     self.width_=iW;
    72     cmd+="-size %dx%d xc:white -fill none -stroke black "%(self.width_,self.height_);
    73
    74     #--draw lifeline
    75     L=[];
    76     for msg in self.msgList_:
    77       src=msg.src_;
    78       sink=msg.sink_;
    79       D=self.textDim(src.name_);
    80       w=int(D.split('x')[0])/2;
    81       for obj in [src, sink]:
    82         draw=not (obj in L);
    83         if (draw):
    84           cmd+="-draw 'text %d,%d \"%s\"' "%(obj.x_-w,obj.y_-5,obj.name_);
    85           cmd+="-draw 'stroke-dasharray 5 5 line %d, %d %d,%d' "%(obj.x_,obj.y_, obj.x_,self.height_-20);
    86           L.append(obj);
    87     
    88     cmd+="%s"%(self.outFile_);
    89     log(cmd);
    90     os.system(cmd);
    91
    92 class Object:
    93   def __init__(self,name):
    94     self.name_=name;
    95     self.x_=0;
    96     self.y_=0;
    97
    98 class Message:
    99   def __init__(self, srcObj, sinkObj,label):
   100     self.src_=srcObj;
   101     self.sink_=sinkObj;
   102     self.label_=label;
   103
   104
   105 def test00():
   106   diagram=Diagram();
   107   obj1=Object('object1');
   108   obj2=Object('object2');
   109   obj3=Object('object3');
   110
   111   m1=Message(obj1,obj2,'method1(arg1)');
   112   diagram.add(m1);
   113
   114   m2=Message(obj2,obj1,'return val');
   115   diagram.add(m2);
   116
   117   m3=Message(obj1,obj1,'method2(abc)');
   118   diagram.add(m3);
   119
   120   m4=Message(obj1,obj3,'method3()');
   121   diagram.add(m4);
   122
   123   diagram.draw();
   124
   125 def test01():
   126   diagram=Diagram();
   127   diagram.draw();
   128
   129 def test02():
   130   L=[];
   131   for i in range(0,10):
   132     L.append(Object('object%d'%i));
   133
   134   diagram=Diagram();
   135   for obj in L:
   136     m=Message(L[0],obj,'message x');
   137     diagram.add(m);
   138     m=Message(L[0],obj,'methodX()');
   139     diagram.add(m);
   140     diagram.add(Message(obj,obj,'ping'));
   141
   142   diagram.draw();
   143
   144 #---main---
   145 test00();
   146 #test01();
   147 #test02();

Pair this with some logging analysis, or debugger traces and you could auto-generate entire system interactions with ease.

Take it and do great things.