#!/bin/sh # # kernel2image [option] {kernel_string} [output_file] # # Given a kernel definition, extract the kernel from IM and create an image or # magick montage of the kernels that IM generates. # # By default the raw images are generated from the kernel (with 'nan' values # being made transparent) are written to the file specified. If multiple # kernels are defined by the given {kernel_string} multiple images will be # output. If no {output_file} is given the script uses "show:". # # The other options, expecially the first option listed will enlarge and # 'pretty' up the kernel image to make it easier to see, and to mark the # (usally central) "origin" of the kernel. It also provides a magick montage of # single or multiple kernels (with or without shadow), on a background # appropriate for IM Examples. See example commands below. # # Options... # # -{scale} Enlarge the kernel image by this much, mark origin # -{scale}.{gap} Enlarge the kernel with inter-pixel gap # # -s {opt} Kernel Scaling/Normalization Kernel Options # -n Normalize/Bias the kernel values into a black/white range # -k {num} Limit a multi-kernel list to to just this kernel. # The first kernel = 0 # -g {gamma} Gamma adjustment, (default=1.8 none=1) # # -ms Montage with Shadow Effects # -mn Montage without Shadow Effects (for convolve kernels) # -mt {tile} Montage tiling option # -ml {label} Montage labeling string, you can use the following specials # %[input] given input kernel defintion # %[number] kernel number of a multi-kernel list # %[name] builtin kernel name + rotation # %[geometry] kernels geometry (size and origin) # -mg Add Geometry to the magick montage label as a extra line # # -bc {color} Color of Background Page (default: "LightSteelBlue") # -gc {color} Color of Inter-Pixel Gaps (default: "None") # -nc {color} Color of Nan Values (default: "None") # -kc {color,color} Color Range for kernel Values (default: "Black,White") # -cc1 {color} Color of Origin Mark #1 (default: "Black") # -cc2 {color} Color of Origin Mark #2 (default: "White") # # -d Turn on debugging output (to stderr) # # Example Commands... # # Large Raw Diamond kernel (save the raw 101x101 pixel image to given file) # kernel2image Diamond:50 diamond.gif # # Disk Morphology Shape Kernel... # kernel2image -10.1 -m Disk # # Peaks HitAndMiss Kernel # kernel2image -10.1 -m Peaks # # Plus Kernel with all the colors redefined from defaults # kernel2image -20.2 -m -kc Black,Gold -nc Grey \ # -bc Wheat -gc Peru -cc1 Sienna -cc2 None Plus # # LineEnds Kernels (8) used for Pruning (Thinning) Skeletons # kernel2image -20.2 -mt x1 LineEnds # # Skeleton Kernel #4, a corner kernel used for "Thinning" shapes # kernel2image -30.2 -k 4 -m Skeleton # # ConvexHull Thickening Kernels (12), in an array, without labels # kernel2image -20.2 -mt x2 -ml '' ConvexHull # # Gaussian Convolution Kernel (image result normalized, no magick montage shadow) # kernel2image -10.1 -mn -mg -n Gaussian:4x1 # # Gaussian Convolution Kernel again, but no gamma adjust # kernel2image -10.1 -mn -mg -n -g 1 Gaussian:4x1 # # A Large Perfect Gaussian Image (values were provided as floats) # kernel2image -n -g 1 Gaussian:0x30 # # All 9 FreiChen Kernels with normalization and biasing to prevent clipping. # Results will vary on each individual kernel # kernel2image -20.2 -ml '' -n FreiChen:10 # # Side-by-side Comparison of a LOG and DOG Convolution Kernels # With user defined magick montage labeling to simplify things. # kernel2image -10.1 -n -mn -ml '%[name]\n%[geometry]' \ # 'Log:5,2 ; Dog:5,1.8,2.4' # ### # # Anthony Thyssen May 2010 # PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path PROGDIR=`dirname $PROGNAME` # extract directory of program PROGNAME=`basename $PROGNAME` # base name of program Usage() { # Report error and Synopsis line only echo >&2 "$PROGNAME:" "$@" sed >&2 -n '1,2d; /^###/q; /^#/!q; /^#$/q; s/^# */Usage: /p;' \ "$PROGDIR/$PROGNAME" exit 10; } Help() { # Output Full header comments as documentation sed >&2 -n '1d; /^###/q; /^#/!q; s/^#//; s/^ //; p' \ "$PROGDIR/$PROGNAME" exit 10; } # Initialization scale=1 gamma_adjust=1.8 montage_tile='0x0' montage_label='undef' montage_shadow='-shadow' page_bg_color="LightSteelBlue" kernel_scale=1.0 kernel_colors="Black,White" gap_color="None" nan_color="None" circle_color1="Black" circle_color2="White" while [ $# -gt 0 ]; do case "$1" in --help|--doc*) Help ;; -[0-9]*.[0-9]*) scale=`expr "$1" : '-\([0-9]*\).'` || Usage "Bad Enlargement Option" gap=`expr "$1" : '-[0-9]*.\([0-9]*\)$'` || Usage "Bad Enlargement Option" scale=`expr $scale - $gap` || Usage "Bad Enlargement Option" ;; -[0-9]*) scale=`expr "$1" : '-\([0-9]*\)$'` || Usage "Bad Enlargement Option" ;; -s) shift; kernel_scale="$1" ;; -n) normalize=true ;; -k) shift; kernel_search="$1" ;; -g) shift; gamma_adjust="$1" ;; -m) montage=true ;; -ms) montage=true; montage_shadow='-shadow' ;; -mn) montage=true; montage_shadow='' ;; -mg) montage=true; montage_geometry_add=true ;; -mt) montage=true; shift; montage_tile="$1" ;; -ml) montage=true; shift; montage_label="$1" ;; -bc) shift; page_bg_color="$1" ;; -gc) shift; gap_color="$1" ;; -nc) shift; nan_color="$1" ;; -kc) shift; kernel_colors="$1" ;; -cc1) shift; circle_color1="$1" ;; -cc2) shift; circle_color2="$1" ;; -d) DEBUG=true; ;; --) shift; break ;; # end of user options -*) Usage "Unknown option \"$1\"" ;; *) break ;; # end of user options esac shift # next option done [ $# -eq 0 ] && Usage "Missing {kernel_string} Argument" [ $# -gt 2 ] && Usage "To many Arguments" "$@" kernel_string="$1"; output_image="$2"; [ $# -eq 1 ] && output_image="show:" #--------------------------------------------------------- # Quick test - mutilple kernels? kernel_output=`magick xc: -define morphology:showKernel=1 \ -define convolve:scale="$kernel_scale" \ -morphology Convolve:0 "$kernel_string" \ null: 2>&1 ` #echo >&2 "$kernel_output" kernel_count=`echo "$kernel_output" | grep -c '^Kernel'` if [ "$kernel_count" -eq 0 ]; then echo >&2 "$PROGNAME: Error no kernels found using \"$kernel_string\"" echo >&2 "$kernel_output" exit 10; fi if [ "X$montage_label" = "Xundef" ]; then if [ "$kernel_count" -eq 1 ]; then montage_label='%[input]' # one kernel else montage_label='%[input] #%[number]' # multi-kernel fi fi if [ "$montage_geometry_add" ]; then montage_label="${montage_label}\n%[geometry]" fi #--------------------------------------------------------- # Loop through the posibily multi-kernel list specified # Pipe the kernel output into loop. # WARNING: variable changes inside a shell pipeline-loop is not preserved # Perhaps proper string parsing will be done to prevent this. echo "$kernel_output" | while true; do # ------------ Read and Parse the Kernel Header ------------ # get the two header lines for this kernel kernel1='' read kernel1 # read the headers read kernel2 if [ "$DEBUG" ]; then echo >&2 "----" echo >&2 "$kernel1" echo >&2 "$kernel2" fi if [ "X$kernel1" = 'X' ]; then break; # end of kernel list fi if echo "$kernel1" | grep -qv 'Kernel'; then echo >&2 "$PROGNAME: Invalid Kernel Header! Parse Failure." echo >&2 "$kernel1" break fi kernel_number=`echo "$kernel1" | sed 's/.* #\([0-9]*\) .*/\1/'` [ "X$kernel_number" = "X" ] && kernel_number=0 #echo >&2 "kernel_number = \"$kernel_number\"" kernel_name=`echo "$kernel1" | sed 's/[^"]*"\([^"]*\)".*/\1/'` #echo >&2 "kernel_name = \"$kernel_name\"" kernel_geometry=`echo "$kernel1" | sed 's/.*size \([^ ]*\) .*/\1/'` width=`echo "$kernel_geometry" | sed 's/^\([0-9][0-9]*\)x.*/\1/'` height=`echo "$kernel_geometry" | sed 's/.*x\([0-9][0-9]*\).*/\1/'` origin_x=`echo "$kernel_geometry" | sed 's/.*[+-]\([0-9][0-9]*\)[+-].*/\1/'` origin_y=`echo "$kernel_geometry" | sed 's/.*[+-]\([0-9][0-9]*\)$/\1/'` #echo >&2 "\"$kernel_geometry\" ==> $width $height $origin_x $origin_y" # skip kernels we are not interested in if [ "X$kernel_search" != "X" ]; then #echo >&2 " \"$kernel_number\" == \"$kernel_search\"" if [ "$kernel_number" -lt "$kernel_search" ]; then i=1 while [ $i -le $height ]; do i=`expr $i + 1` read kernel_values done continue # lets try the next kernel fi if [ "$kernel_number" -gt "$kernel_search" ]; then break; # we are finished fi fi #echo >&2 "FOUND" min_value=0 max_value=1 if [ "$normalize" ]; then min_value=`echo "$kernel1" | sed 's/.*values from \([^ ]*\).*/\1/'` max_value=`echo "$kernel1" | sed 's/.*to \([^ ]*\)$/\1/'` [ "$min_value" = "$max_value" ] && min_value=0 # fix flat shape kernel #echo >&2 "min,max = $min_value,$max_value" fi # ------ Read and magick Kernle values into an image ------ # Now magick the kernel values into a PPM image. # As NetPBM images can not handle transparency, any 'Nan' values are used to # create a transparency mask in the Green Channel, while the actual kernel # values are stored in the red channel. This will be fixed up later, once # the initial image has been read into ImageMagick. ( echo "P3 $width $height 65535" # for i in `seq $height`; do # WARNING: this fails for MacOSX i=1 while [ $i -le $height ]; do read kernel_values #echo >&2 "line $i/$height => $kernel_values" # sed 's/.*: *//; s/ */\n/g' | # This fails on MacOSX echo "$kernel_values" | sed 's/.*: *//; s/ */@/g' | tr '@A-Z' '\012a-z' | awk '/nan/ { print 0, 0, 0; next; } { value=($1 - '$min_value')/('$max_value' - '$min_value'); if ( value > 1.0 ) value = 1.0; if ( value < 0.0 ) value = 0.0; print int(value*65535+0.5), 65535, 0; }' - i=`expr $i + 1` done ) | #cat; exit 10 # Adjust red channel (kernel values) into a greyscale image # and use the green channel as a alpha transparency Mask. # Also set the special purpose meta-data strings for magick montage labeling. magick ppm:- -channel RG -separate +channel \ -compose CopyOpacity -composite -compose Over \ -gamma "$gamma_adjust" \ -channel RGB +level-colors "$kernel_colors" -channel RGBA \ -bordercolor "$nan_color" -border 0 \ -set input "$kernel_string" \ -set number "$kernel_number" \ -set name "$kernel_name" \ -set geometry "$kernel_geometry" \ -scale "${scale}00%" miff:- | if [ "$scale" -gt 3 ]; then # add the origin marker center_x=`magick xc: -format "%[fx: ($origin_x+.5)*$scale-.5 ]" info:` center_y=`magick xc: -format "%[fx: ($origin_y+.5)*$scale-.5 ]" info:` #echo >&2 "center = $center_x $center_y" radius1=`magick xc: -format "%[fx: int($scale*.29-.5) ]" info:` radius2=`magick xc: -format "%[fx: $radius1+1 ]" info:` #echo >&2 "radius = $radius1 $radius2" magick - -fill none -strokewidth 0.5 \ -draw "translate $center_x,$center_y \ stroke $circle_color2 circle 0,0 $radius1,$radius1 \ stroke $circle_color1 circle 0,0 $radius1,$radius2 " \ miff:- | # Add inter-pixel gaps and colors (crop-splice-append technique) if [ "$gap" ]; then magick - -background ${gap_color} \ -crop 0x${scale} -splice 0x${gap} -append -chop 0x${gap} \ -crop ${scale}x0 -splice ${gap}x0 +append -chop ${gap}x0 \ -bordercolor ${gap_color} -compose Copy -border ${gap} \ +compose miff:- else cat # pipe the image on fi else cat # pipe the images on fi done | #cat; exit 0 # ---------- output all kernel images as requested ----------- if [ "$montage" ]; then magick montage - -set label "$montage_label" \ -geometry +5+5 $montage_shadow \ -tile "$montage_tile" -background "$page_bg_color" \ "$output_image" else magick - "$output_image" fi exit 0