Filtering images using the Tensor package

Arjen Markus (23 July 2009) Exploring the possibilities of the Tensor package, I thought it would be fun and useful to see how you can use it to filter images. after all: an image can be regarded as a two-dimensional array of numerical data and the Tensor package is meant to deal with such data.

The case I had in mind was simple edge detection. Basically: determine the gradients in intensity in the image and use a "clipping" function to highlight the strong gradients.

It took me some experimentation to find out that matrix multiplication (tensor multiplication if you like) will not do the job - it is a limitation of the matrix multiplication itself, not of the Tensor package. So, instead:

  • The first algorithm simply flattens the filter and the part of the image being treated, multiplies the elements one by one and determines the sum. This algorithm loops over parts of the image.
  • The second algorithm shifts the image, scales it according to the filter's entries and sums these weighted images.

Here is the output:

Original:
--------------------
                    
                    
                    
                    
                    
       *********    
       *********    
       *********    
       *********    
       *********    
       *********    
                    
                    
                    
                    
                    
                    
                    
                    
                    
--------------------
Filtered:
--------------------
                    
                    
                    
                    
       *********    
      *        *    
      *        *    
      *        *    
      *        *    
      *        *    
      **********    
                    
                    
                    
                    
                    
                    
                    
                    
                    
--------------------
{1 18} {1 18}
{1 18} {2 19}
{2 19} {1 18}
Filtered (alternative):
--------------------
                    
                    
                    
                    
       *********    
      *        *    
      *        *    
      *        *    
      *        *    
      *        *    
      **********    
                    
                    
                    
                    
                    
                    
                    
                    
                    
--------------------

The code:

# filter.tcl --
#      Illustrate the use of the Tensor package to do filtering
#      on images
#
#      Note:
#      The filtering procedures assume the filter dimensions are odd!
#

package require Tensor

namespace eval filter {
    variable count 0
}

# filteredImage --
#     Create a filtered image
#
proc filter::filteredImage {filter image} {
    variable count

    incr count

    ::tensor::create img$count -type double -size [$image dimensions]

    ::tensor::create part -type double -size [$filter dimensions]

    set size  [expr [join [$filter dimensions] *]]
    set fdims [$filter dimensions]

    ::tensor::create rfilter -type double -size $fdims
    ::tensor::create rpart   -type double -size $size
    ::tensor::create cell    -type double -size 1

    foreach {rows  cols}  [$image  dimensions] {break}
    foreach {frows fcols} [$filter dimensions] {break}

    set rowsf [expr {$frows/2}]
    set colsf [expr {$fcols/2}]
    set rowsl [expr {$rows-$frows/2}]
    set colsl [expr {$cols-$fcols/2}]

    rfilter = tensor filter
    rfilter reshape $size

    for {set i $rowsf} {$i < $rowsl} {incr i} {
        for {set j $colsf} {$j < $colsl} {incr j} {
            part = tensor $image section \
                [list [list [expr {$i-$frows/2}] [expr {$i+$frows/2}]] \
                      [list [expr {$j-$fcols/2}] [expr {$j+$fcols/2}]] ]

            part reshape $size

            rpart =  tensor part
            rpart *= tensor rfilter

            part reshape $fdims

            cell = contraction rpart 0

            img$count section [list $i $j] = scalar [cell]
        }
    }

    rename part    {}
    rename rpart   {}
    rename rfilter {}
    rename cell    {}

    return img$count
}

# filteredImageX --
#     Create a filtered image - alternative implementation
#
proc filter::filteredImageX {filter image} {
    variable count

    incr count

    ::tensor::create img$count -type double -size [$image dimensions]
    ::tensor::create partial   -type double -size [$image dimensions]

    foreach {rows  cols}  [$image  dimensions] {break}
    foreach {frows fcols} [$filter dimensions] {break}

    set psection [list [list [expr {$frows/2}] [expr {$rows-$frows/2-1}]] \
                       [list [expr {$fcols/2}] [expr {$cols-$fcols/2-1}]] ]

    for {set i 0} {$i < $frows} {incr i} {
        for {set j 0} {$j < $fcols} {incr j} {
            set factor [$filter section [list $i $j]]

            if { $factor != 0.0 } {

                set isection [list [list $i [expr {$rows+$i-$frows}]] \
                                   [list $j [expr {$cols+$j-$fcols}]] ]


                puts $isection

                partial section $psection = tensor $image section $isection
                partial *= scalar $factor

                img$count += tensor partial
            }
        }
    }

    rename partial {}

    return img$count
}

# main --
#     Test it
#
::tensor::create filter -initial {{0.0  0.0 0.0}
                                  {0.0 -2.0 1.0}
                                  {0.0  1.0 0.0}}

::tensor::create image -size {20 20}
image section {{5 10} {7 15}} = scalar 1.0

puts "Original:"
puts [string repeat - 20]
foreach row [image] {
    foreach col $row {
        puts -nonewline [expr {abs($col) > 0.5? "*": " "}]
    }
    puts ""
}
puts [string repeat - 20]

set result [filter::filteredImage filter image]

puts "Filtered:"
#
# Numerical output:
# puts [join [$result] \n]

puts [string repeat - 20]
foreach row [$result] {
    foreach col $row {
        puts -nonewline [expr {abs($col) > 0.5? "*": " "}]
    }
    puts ""
}
puts [string repeat - 20]

#
# Use alternative implementation
#
set result [filter::filteredImageX filter image]

puts "Filtered (alternative):"
#
# Numerical output:
# puts [join [$result] \n]

puts [string repeat - 20]
foreach row [$result] {
    foreach col $row {
        puts -nonewline [expr {abs($col) > 0.5? "*": " "}]
    }
    puts ""
}
puts [string repeat - 20]