Opening Sourcetree from TextMate

My goto editor these days is TextMate. (The recent 2.0 releases have been really great). Like many developers, I use git for source control. Most of the time I use git on the command line, but once in a while I'll open up a project in the very awesome, and very free SourceTree VCS Gui from Atlassian. To simplify opening a project in SourceTree when working from TextMate I added a command via TextMate's extremely simple and flexible Bundles. (Which, due to the simplicity and flexibility of bundle's in TextMate, just tromps on anything I've seen in any other text editor, sorry Sublime Text.).

First, you will need to install SourceTree. Once installed, launch it and select SourceTree > Install Command Line Tools to install the stree shell command.

To add the command to TextMate, do the following:

  1. In TextMate, go to Bundles > Edit Bundles
  2. Select the Bundle that you want to add your command to. I created one with my own name, but you can use existing ones.
  3. Hit [command]+N to open a dialog to Create a New Item
  4. In the Create New Item dialog choose Command
  5. In the sidebar, name it Open With SourceTree. Add your favorite key equivalent (I use [command]+. )
  6. Set Save to Nothing
  7. Set Input to Nothing
  8. Set Output to Discard
  9. In the script shell at the bottom enter the following:
    
    #!/usr/bin/env bash
    if [ -d $TM_DIRECTORY ] ; then
      cd $TM_DIRECTORY 
    elif [ -d $TM_PROJECT_DIRECTORY ] ; then
      cd $TM_PROJECT_DIRECTORY
    fi
      
    GIT_ROOT=$(git rev-parse --show-toplevel)
    if [ -d $GIT_ROOT ] ; then
      stree $GIT_ROOT
    fi
      
And that's it. Enjoy.

Strict Coding Style SBT project (using giter8)

At this moment ScalaDays is going on in nearby San Francisco. I came across a Toward a Safer Scala talk that emphasized writing safer Scala using strict static code analysis. After watching the talk I put together a giter8 template that implements their recommendations. If you have giter8 installed you can try out the basicscala template using:

g8 hohonuuli/basicscala.g8
Here's some things that this template provides:
  • Formats source files on compile using Scalariform
  • Adds strict scala compiler flags in build.sbt
  • Build fails on compiler warnings
  • Adds Wartremover to Scala's linter. You can run lint and wartremover in sbt using 'lint:compile'.
  • Includes junit and scalatest
  • Includes scala-style for checking code sytle. You will need to run 'sbt scalastyleGenerateConfig' to generate a scalastyle-config file. After which, you can run 'sbt scalastyle' to check your projects.

Reading EXIF image metadata using Apache commons-imaging and Scala

I'm currently working on a project that has to watermark a few hundred thousand images and add some EXIF metadata to the images. For various reasons, JVM-based languages work best for this problem at work, so I've spent some time learning Apache's commons-imaging libraries as they support reading and writing EXIF metadata in Java. Using exiftool as 'truth', I worked through writing the metadata needed using first exiftool then commons-imaging and comparing the results. I ran into a few issues related to TIFF metadata directories that I needed to debug. In, order to do this I created a Scala script that dumps out EXIF info along with the tag id and directory id. In order to get this example to work you will need the following:
  1. Install SBT. On Macs, you can use Homebrew to install it.
  2. Create a shell script named scalas with the following contents: scalas
    
    #!/usr/bin/env bash
    
    # This script calls SBT and executes a scala script that requires external dependencies
    # The build details are added as a comment at the top of the script
    # You can set SBT_HOME or hard code the path to the directory where SBT is installed.
    # If you installed SBT using homebrew then add this to your .bashrc file:
    #
    # export SBT_HOME='/usr/local/opt/sbt/libexec'
    
    java -Dsbt.main.class=sbt.ScriptMain -Dsbt.boot.directory=$HOME/.sbt/boot -jar $SBT_HOME/sbt-launch.jar "$@"
       
Using scalas we can run scripts with SBT settings passed in. This allows us to link to 3rd party jars from our scripts without having to hard code paths. Now create a file named readexif and add the following:
 
#!/usr/bin/env scalas

/*
 Dump out exif metadata

 Usage: readexif filename
*/

/***
scalaVersion := "2.11.5"

libraryDependencies ++= Seq(
  "org.apache.commons" % "commons-imaging" % "1.0-SNAPSHOT")

resolvers in ThisBuild ++= Seq(Resolver.mavenLocal,
    "mbari-maven-repository" at "https://mbari-maven-repository.googlecode.com/svn/repository",
    "Apache Development Snapshot Repository" at "https://repository.apache.org/content/repositories/snapshots/")

*/

import org.apache.commons.imaging.common.GenericImageMetadata.GenericImageMetadataItem
import org.apache.commons.imaging.common.ImageMetadata.ImageMetadataItem
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata.TiffMetadataItem
import org.apache.commons.imaging.Imaging
import java.io.File
import scala.collection.JavaConverters._

val file = new File(args(0))
val metadata = Imaging.getMetadata(file)

if (metadata == null) {
  println("\tNo image metadata was found")
  System.exit(0)
}

println(s"  -- ALL EXIF info (using ${metadata.getClass.getName})")
println(s"  (tag    -- directory )")
metadata match {
  case j: JpegImageMetadata => dump(j)
}

def dump(j: JpegImageMetadata): Unit = {
  j.getItems.asScala.foreach {i => 
    i match {
      case g: GenericImageMetadataItem => printItem(g)
      case _ => print("      $i")
    }
  }
}

def printItem(i: GenericImageMetadataItem): Unit = {
  val s = s"${i.getKeyword} : ${i.getText}"
  val tagString = i match {
    case tmi: TiffMetadataItem => {
      val tmi = i.asInstanceOf[TiffMetadataItem]
      val tiffField = tmi.getTiffField
      val tag = tiffField.getTag
      val directoryType = tiffField.getDirectoryType
      f"  (0x${tag}%04x -- 0x${directoryType}%08x)  $s"
    }
    case _ => s
  }
  println(tagString)
}

Here's example output from readexif somefile.jpg:
  -- ALL EXIF info (using org.apache.commons.imaging.formats.jpeg.JpegImageMetadata)
  (tag    -- directory )
  (0x8298 -- 0x00000000)  Copyright : 'Copyright 2003 Monterey Bay Aquarium Research Institute'
  (0x8769 -- 0x00000000)  ExifOffset : 108
  (0x8825 -- 0x00000000)  GPSInfo : 192
  (0x882a -- 0xfffffffe)  TimeZoneOffset : 0
  (0x9003 -- 0xfffffffe)  DateTimeOriginal : '2014:01:28 10:11:12'
  (0x9004 -- 0xfffffffe)  DateTimeDigitized : '2015:01:01 00:00:00'
  (0x0001 -- 0xfffffffd)  GPSLatitudeRef : 'N'
  (0x0002 -- 0xfffffffd)  GPSLatitude : 40, 43, 1/2147483647 (0)
  (0x0003 -- 0xfffffffd)  GPSLongitudeRef : 'W'
  (0x0004 -- 0xfffffffd)  GPSLongitude : 74, 0, 0
  (0x0005 -- 0xfffffffd)  GPSAltitudeRef : 1
  (0x0006 -- 0xfffffffd)  GPSAltitude : 4203/10 (420.3)
  (0x001b -- 0xfffffffd)  GPSProcessingMethod : 'MANUAL'
Compared to the output of exiftool somefile.jpg:
ExifTool Version Number         : 9.76
File Name                       : WriteExif01$-01_45_25_18-external.jpg
Directory                       : target
File Size                       : 56 kB
File Modification Date/Time     : 2015:02:04 10:53:09-08:00
File Access Date/Time           : 2015:02:04 13:56:01-08:00
File Inode Change Date/Time     : 2015:02:04 10:53:09-08:00
File Permissions                : rw-r--r--
File Type                       : JPEG
MIME Type                       : image/jpeg
JFIF Version                    : 1.02
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Exif Byte Order                 : Little-endian (Intel, II)
Copyright                       : Copyright 2003 Monterey Bay Aquarium Research Institute
Time Zone Offset                : 0
Date/Time Original              : 2014:01:28 10:11:12
Create Date                     : 2015:01:01 00:00:00
GPS Version ID                  : 2.3.0.0
GPS Latitude Ref                : North
GPS Longitude Ref               : West
GPS Altitude Ref                : Below Sea Level
GPS Processing Method           : MANUAL
Image Width                     : 640
Image Height                    : 486
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
GPS Altitude                    : 420.3 m Below Sea Level
GPS Latitude                    : 40 deg 43' 0.00" N
GPS Longitude                   : 74 deg 0' 0.00" W
GPS Position                    : 40 deg 43' 0.00" N, 74 deg 0' 0.00" W
Image Size                      : 640x486

Creating a PNG testing image in Matlab

I needed to create a small test image with known pixel values. The easiest way for me was to fire up Matlab and use it's built in commands to generate a small PNG image. I'm just documenting the steps here for the next time I need to do such a thing.

The Code

Here's the code:

%% Creating a 20x20 pixel image

% Here's a linear ramp of 400 samples
r = 0:399;

% reshape the ramp to be a row-oriented 20x20 matrix
% i.e. r(1, 1) = 0, r(1, 2) = 1 ...
r = reshape(r, 20, 20)';

% Create other color arrays
g = r / 2;
b = flipud(fliplr(r));

% Stuff them all into one 20x20x3 matrix
i = zeros(20, 20, 3);
i(:, :, 1) = r;
i(:, :, 2) = g;
i(:, :, 3) = b;

% Normalize to byte values
ui = uint8(i / max(max(max(i))) * 255); 

% write it out
imwrite(ui, 'smalltestimage.png', 'PNG');

The Image

And Voila ...

The Pixel Values

And for the record here's the actual pixel values in ui

>> ui

ui(:,:,1) =

    0    1    1    2    3    3    4    4    5    6    6    7    8    8    9   10   10   11   12   12
   13   13   14   15   15   16   17   17   18   19   19   20   20   21   22   22   23   24   24   25
   26   26   27   27   28   29   29   30   31   31   32   33   33   34   35   35   36   36   37   38
   38   39   40   40   41   42   42   43   43   44   45   45   46   47   47   48   49   49   50   50
   51   52   52   53   54   54   55   56   56   57   58   58   59   59   60   61   61   62   63   63
   64   65   65   66   66   67   68   68   69   70   70   71   72   72   73   73   74   75   75   76
   77   77   78   79   79   80   81   81   82   82   83   84   84   85   86   86   87   88   88   89
   89   90   91   91   92   93   93   94   95   95   96   97   97   98   98   99  100  100  101  102
  102  103  104  104  105  105  106  107  107  108  109  109  110  111  111  112  112  113  114  114
  115  116  116  117  118  118  119  120  120  121  121  122  123  123  124  125  125  126  127  127
  128  128  129  130  130  131  132  132  133  134  134  135  135  136  137  137  138  139  139  140
  141  141  142  143  143  144  144  145  146  146  147  148  148  149  150  150  151  151  152  153
  153  154  155  155  156  157  157  158  158  159  160  160  161  162  162  163  164  164  165  166
  166  167  167  168  169  169  170  171  171  172  173  173  174  174  175  176  176  177  178  178
  179  180  180  181  182  182  183  183  184  185  185  186  187  187  188  189  189  190  190  191
  192  192  193  194  194  195  196  196  197  197  198  199  199  200  201  201  202  203  203  204
  205  205  206  206  207  208  208  209  210  210  211  212  212  213  213  214  215  215  216  217
  217  218  219  219  220  220  221  222  222  223  224  224  225  226  226  227  228  228  229  229
  230  231  231  232  233  233  234  235  235  236  236  237  238  238  239  240  240  241  242  242
  243  243  244  245  245  246  247  247  248  249  249  250  251  251  252  252  253  254  254  255


ui(:,:,2) =

    0    0    1    1    1    2    2    2    3    3    3    4    4    4    4    5    5    5    6    6
    6    7    7    7    8    8    8    9    9    9   10   10   10   11   11   11   12   12   12   12
   13   13   13   14   14   14   15   15   15   16   16   16   17   17   17   18   18   18   19   19
   19   19   20   20   20   21   21   21   22   22   22   23   23   23   24   24   24   25   25   25
   26   26   26   27   27   27   27   28   28   28   29   29   29   30   30   30   31   31   31   32
   32   32   33   33   33   34   34   34   35   35   35   35   36   36   36   37   37   37   38   38
   38   39   39   39   40   40   40   41   41   41   42   42   42   43   43   43   43   44   44   44
   45   45   45   46   46   46   47   47   47   48   48   48   49   49   49   50   50   50   50   51
   51   51   52   52   52   53   53   53   54   54   54   55   55   55   56   56   56   57   57   57
   58   58   58   58   59   59   59   60   60   60   61   61   61   62   62   62   63   63   63   64
   64   64   65   65   65   66   66   66   66   67   67   67   68   68   68   69   69   69   70   70
   70   71   71   71   72   72   72   73   73   73   73   74   74   74   75   75   75   76   76   76
   77   77   77   78   78   78   79   79   79   80   80   80   81   81   81   81   82   82   82   83
   83   83   84   84   84   85   85   85   86   86   86   87   87   87   88   88   88   89   89   89
   89   90   90   90   91   91   91   92   92   92   93   93   93   94   94   94   95   95   95   96
   96   96   97   97   97   97   98   98   98   99   99   99  100  100  100  101  101  101  102  102
  102  103  103  103  104  104  104  104  105  105  105  106  106  106  107  107  107  108  108  108
  109  109  109  110  110  110  111  111  111  112  112  112  112  113  113  113  114  114  114  115
  115  115  116  116  116  117  117  117  118  118  118  119  119  119  120  120  120  120  121  121
  121  122  122  122  123  123  123  124  124  124  125  125  125  126  126  126  127  127  127  128


ui(:,:,3) =

  255  254  254  253  252  252  251  251  250  249  249  248  247  247  246  245  245  244  243  243
  242  242  241  240  240  239  238  238  237  236  236  235  235  234  233  233  232  231  231  230
  229  229  228  228  227  226  226  225  224  224  223  222  222  221  220  220  219  219  218  217
  217  216  215  215  214  213  213  212  212  211  210  210  209  208  208  207  206  206  205  205
  204  203  203  202  201  201  200  199  199  198  197  197  196  196  195  194  194  193  192  192
  191  190  190  189  189  188  187  187  186  185  185  184  183  183  182  182  181  180  180  179
  178  178  177  176  176  175  174  174  173  173  172  171  171  170  169  169  168  167  167  166
  166  165  164  164  163  162  162  161  160  160  159  158  158  157  157  156  155  155  154  153
  153  152  151  151  150  150  149  148  148  147  146  146  145  144  144  143  143  142  141  141
  140  139  139  138  137  137  136  135  135  134  134  133  132  132  131  130  130  129  128  128
  127  127  126  125  125  124  123  123  122  121  121  120  120  119  118  118  117  116  116  115
  114  114  113  112  112  111  111  110  109  109  108  107  107  106  105  105  104  104  103  102
  102  101  100  100   99   98   98   97   97   96   95   95   94   93   93   92   91   91   90   89
   89   88   88   87   86   86   85   84   84   83   82   82   81   81   80   79   79   78   77   77
   76   75   75   74   73   73   72   72   71   70   70   69   68   68   67   66   66   65   65   64
   63   63   62   61   61   60   59   59   58   58   57   56   56   55   54   54   53   52   52   51
   50   50   49   49   48   47   47   46   45   45   44   43   43   42   42   41   40   40   39   38
   38   37   36   36   35   35   34   33   33   32   31   31   30   29   29   28   27   27   26   26
   25   24   24   23   22   22   21   20   20   19   19   18   17   17   16   15   15   14   13   13
   12   12   11   10   10    9    8    8    7    6    6    5    4    4    3    3    2    1    1    0