Contribution to octave forge image processing toolbox

View: New views
4 Messages — Rating Filter:   Alert me  

Contribution to octave forge image processing toolbox

by Roderick Köhle-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Dear all,

for my personal need, I developed a Exif Reader for octave, allowing to
read additional informations from Jpeg images.

The implementation is based on the format specification:
    JEITA CP-3451, Exchangeable image file format for digital still
cameras: Exif Version 2.2

I tested it with several images from my own camera and other files
obtained from Flickr. No special extensions for octave are used, my
version of octave is 3.1.55.

Hoping that this function might be useful for other people using MATLAB
/ Octave, I would like to release it as part and under the terms of the
GPL conditions of the octave-forge image processing toolbox.

Best regards,

Roderick Köhle

---

Tribulaunstr. 8, 81739 München
Email: koehle@...



## Copyright (C) 2009 Roderick Koehle    koehle(at)users.sourceforge.net
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {@var{exif} =} readexif(@var{filename}, @var{thumbnail})
## Read EXIF information from JPEG image data.
##
## The exif tag information are returned in the @var{exif} data structure.
## Integer ratios are expressed as column vector.
## For example, a focal number of 2.8 is expressed
## as FNumber=[28; 10]. Otherwise all data are returned by the type
## as specified in the IFD structures.
##
## The filename for the thumbnail image is optional.
## If given, the thumbnail jpeg image will be stored to
## file @var{thumbnail}.
##
## Reference:
## JEITA CP-3451, Exchangeable image file format for digital still cameras:
## Exif Version 2.2
##
## @seealso{imwrite, imfinfo}
## @end deftypefn

function exif = readexif(file, thumbnail)

  % Enable the debug flag to see more of the JPG sections.

  debug = false;

  in = fopen(file);
  if (in<0)
    error('File "%s" not found !', file);
  end

  s = fread(in, 1, 'uint16', 'ieee-be');

  JPEG.SOI = 0xffd8;
  JPEG.APP0 = 0xffe0;
  JPEG.APP1 = 0xffe1;
  JPEG.APP2 = 0xffe2;
  JPEG.DQT = 0xffdb;
  JPEG.DHT = 0xffc4;
  JPEG.DRI = 0xffdd;
  JPEG.SOF = 0xffc0;
  JPEG.SOS = 0xffda;
  JPEG.EOI = 0xffd9;

  % Stop if no Start of Image found

  if s~=JPEG.SOI
    error('JPEG Format error - missing start of image tag.');
  end

  exif = [];

  while ~feof(in)
    s = fread(in, 1, 'uint16', 'ieee-be');

    switch s
      case JPEG.SOI
      case JPEG.APP0
        l = fread(in, 1, 'uint16', 'ieee-be');
        if debug, printf('APP0: %i\n', l); end
        fseek(in, l-2, 'cof');
      case JPEG.APP1
        l = fread(in, 1, 'uint16', 'ieee-be');
        if debug, printf('APP1: %i\n', l); end
        app1 = fread(in, l-2, 'uchar');
        if nargin==1
          exif = parseexif(app1);
        else
          exif = parseexif(app1, thumbnail);
        end
        % stop reading further, remove following break
        % if you want to extend this parser.
        break;
      case JPEG.APP2
        l = fread(in, 1, 'uint16', 'ieee-be');
        if debug, printf('APP2: %i\n', l); end
        fseek(in, l-2, 'cof');
      case JPEG.DQT % define quantization table
        l = fread(in, 1, 'uint16', 'ieee-be');
        if debug, printf('DQT: %i\n', l); end;
        fseek(in, l-2, 'cof');
      case JPEG.DHT % define huffmann table
        l = fread(in, 1, 'uint16', 'ieee-be');
        fseek(in, l-2, 'cof');
        if debug, printf('DHT: %i\n', l); end
      case JPEG.DRI % define restart interoperability
        l = fread(in, 1, 'uint16', 'ieee-be');
        fseek(in, l-2, 'cof');
        if debug, printf('DRI: %i\n', l); end
      case JPEG.SOF % start of frame
        l = fread(in, 1, 'uint16', 'ieee-be');
        fseek(in, l-2, 'cof');
        if debug, printf('SOF: %i\n', l); end
      case JPEG.SOS % start of scan
        l = fread(in, 1, 'uint16', 'ieee-be');
        fseek(in, l-2, 'cof');
        if debug, printf('SOS: %i\n', l); end

        % JPEG compressed data comes here ...
        break;

      case JPEG.EOI % end of image
        printf('EOI');
      otherwise % Skip unknown tags
        l = fread(in, 1, 'uint16', 'ieee-be');
        if debug, printf('TAG %04X: %i\n', s, l); end
        fseek(in, l-2, 'cof');
    end
  end

  fclose(in);
end

%
% Parse EXIF APP1 section
%
% This routine will parse the APP1 section of an jpeg image.
% If a filename "thumb" is given, the tumbnail image data
% will be exported to given file.
%
%   exif = parseexif(data, thumb)
%
function exif = parseexif(data, thumb)

  id = char(data(1:6).');
  if strncmp(id, ['Exif' 0 0], 6)

    % TIFF header

    byteorder = char(data(7:8).');
    littleendian = strncmp(byteorder, 'II', 2);
    bigendian    = strncmp(byteorder, 'MM', 2);

    tag42     = intn(data(9:10), bigendian);
    offset    = intn(data(11:14), bigendian);

    if (~littleendian && ~bigendian) || tag42~=42
      error('invalid TIFF header');
    end

    % IFD fields

    exif = ifdparse(tifftags(), data, offset, bigendian);
  else
    exif = [];
  end

  % export thumbnail image

  if nargin==2 && isfield(exif, 'JPEGInterchangeFormat')
    i = exif.JPEGInterchangeFormat;
    n = exif.JPEGInterchangeFormatLength;

    jpg = data(7+i:7+i+n-1);
    out = fopen(thumb, 'w');
    if (out<0),
      error('Cannot open file "%s" for writing thumbnail image.', thumb);
    end
    fwrite(out, jpg, 'uint8');
    fclose(out);
  end
end

function ifd = ifdparse(dict, data, offset, endian)

  debug = false;

  ifd = [];

  while offset
    ifd_fields = intn(data(7+offset+(0:1)), endian);

    if debug, printf('Tag  Type Count    Offset\n'); end
    for i=1:ifd_fields
      j = 9+offset+(i-1)*12;
      ifd_tag    = intn(data(  j:j+1), endian);
      ifd_type   = intn(data(j+2:j+3), endian);
      ifd_count  = intn(data(j+4:j+7), endian);
      ifd_offset = intn(data(j+8:j+11), endian);

      name = ifdtagname(dict, ifd_tag);

      if debug,
        printf('%04x %04x %08x %08x %s : ', ifd_tag, ifd_type, ...
               ifd_count, ifd_offset, name);
      end
      if ifd_type>0
        n = ifdsize(ifd_type);

        if n*ifd_count<=4
          value = data(j+8:j+8+n*ifd_count-1);
          value = reshape(value, n, ifd_count);
        else
   a = 7+ifd_offset;
          b = 7+ifd_offset+n*ifd_count-1;
          if (a>0 && b>0 && a<=length(data) && b<=length(data))
     value = data(7+ifd_offset:7+ifd_offset+n*ifd_count-1);
            value = reshape(value, n, ifd_count);
          else
            value = [];
          end
        end
      end
   
      switch ifd_type
        case 01 % unsigned char
          ifd.(name) = uint8(value);
          if debug,
     printf('%02x ', uint8(value));
            printf('\n');
          end
        case 02 % Ascii
          ifd.(name) = char(value);
          if debug, printf('%s\n', char(value)); end
        case 03 % 16 bit unsigned int
          ifd.(name) = uintn(value, endian);
          if debug
            printf('%i ', intn(value), endian);
            printf('\n');
          end
        case 04 % 32 bit unsigned int
          ifd.(name) = uintn(value, endian);
          if debug, printf('%i\n', uintn(value, endian)); end
        case 05 % 32 bit unsigned rational
          ifd.(name) = [uintn(value(1:4,:), endian); uintn(value(5:8,:), endian)];
          if debug, printf('%i/%i\n',uintn(value(1:4), endian),uintn(value(5:8)), endian); end
        case 07 % unknown
          ifd.(name) = uint8(value);
          if debug
       printf('%02x ', value);
     printf('\n');
          end
        case 09 % 32 bit signed int
          ifd.(name) = intn(value, endian);
          if debug, printf('%i\n', intn(value, endian)); end
        case 10 % 32 bit signed rational
          ifd.(name) = [intn(value(1:4,:), endian); intn(value(5:8,:), endian)];
          if debug, printf('%i/%i\n',intn(value(1:4), endian),intn(value(5:8)), endian); end
        otherwise
          printf('%02x ', value);
          printf('\n');
      end

      switch ifd_tag
        case 0x8769, % Exif Pointer
          ifd.(name) = ifdparse(exiftags(), data, ifd_offset, endian);
        case 0x8825, % GPS Pointer
          ifd.(name) = ifdparse(gpstags(), data, ifd_offset, endian);
        case 0xa005 % Interoperatibility Pointer
          ifd.(name) = ifdparse(dict, data, ifd_offset, endian);
%        case 0x927c % Makernotes
%   ifd.(name) = ifdparse([], data, ifd_offset, endian);
        otherwise
      end
    end
    j = 9+offset+ifd_fields*12;
    ifd_next = intn(data(j:j+3), endian);

    offset = ifd_next;
  end
end

%
% Return bytelength for respective IFD type
%
function n = ifdsize(ifd_type)
  switch ifd_type
    case {1, 2, 7}, n = 1;
    case 03       , n = 2;
    case {4, 9}   , n = 4;
    case {5, 10}  , n = 8;
    otherwise     , n = 1;
  end
end


%
% Convert little endian character vector to integer
%
function y = intn(x, bigendian)
  if bigendian
    y = polycol(x, int32(256));
  else
    y = polycol(flipud(x), int32(256));
  end
end

function y = uintn(x, bigendian)
  if bigendian
     y = polycol(x, uint32(256));
  else
     y = polycol(flipud(x), uint32(256));
  end
end

%
% Use own polyval that works with integers,
% it evaluates the number polygon columnwise.
%
%   number = polycol(digits, base)
%
function y = polycol(c, x)
  y = c(1,:);
  for i=2:size(c, 1)
    y = y.*x+c(i,:);
  end
end

%
% Query EXIF IFD tagname
%
% Unfortunately, neither MATLAB nor Octave provide a hash functionality,
% so use structures as hash.
%
function name = ifdtagname(dict, key)
  k = sprintf('K%04X', key);
  if isfield(dict, k)
    name = dict.(k);
  else
    name = sprintf('tag%04X', key);
  end
end

%
% Primary image IFD tags according to Exif 2.2
%
function dict = tifftags()
  t = {

    % TIFF Tags according to EXIF2.2, additional baseline TIFF tags are marked by a '%'

    '0FE' 'NewSubfileType' %
    '0FF' 'SubfileType' %
    '100' 'ImageWidth'
    '101' 'ImageLength'
    '102' 'BitsPerSample'
    '103' 'Compression'
    '106' 'PhotometricInterpretation'
    '108' 'CellWidth' %
    '109' 'CellLength' %
    '10A' 'FillOrder' %
    '10E' 'ImageDescription'
    '10F' 'Make'
    '110' 'Model'
    '111' 'StripOffsets'
    '112' 'Orientation'
    '115' 'SamplesPerPixel'
    '116' 'RowsPerStrip'
    '117' 'StripByteCounts'
    '118' 'MinSampleValue' %
    '119' 'MaxSampleValue' %
    '11A' 'XResolution'
    '11B' 'YResolution'
    '11C' 'PlanarConfiguration'
    '120' 'FreeOffsets' %
    '121' 'FreeByteCounts' %
    '122' 'GrayResponseUnit' %
    '123' 'GrayResponseCurve' %
    '128' 'ResolutionUnit'
    '12D' 'TransferFunction'
    '131' 'Software'
    '132' 'DateTime'
    '13B' 'Artist'
    '13C' 'HostComputer' %
    '13E' 'WhitePoint'
    '13F' 'PrimaryChromaticities'
    '140' 'ColorMap' %
    '152' 'ExtraSamples' %
    '201' 'JPEGInterchangeFormat'
    '202' 'JPEGInterchangeFormatLength'
    '211' 'YCbCrCoefficients'
    '212' 'YCbCrSubSampling'
    '213' 'YCbCrPositioning'
    '214' 'ReferenceBlackWhite'
    '8298' 'Copyright'
    '8769' 'Exif IFD Pointer'
    '8825' 'GPS Info IFD Pointer'
  };

  dict = [];
  for i=1:size(t,1)
    key   = sprintf('K%04X', hex2dec(t{i,1}));
    value = t{i,2};
    dict.(key) = strrep(value, ' ', '_');
  end
end

%
% EXIF private tags
%
function dict = exiftags()
  t = {

    % EXIF Tags

    '829A' 'ExposureTime'
    '829D' 'FNumber'
    '8822' 'ExposureProgram'
    '8824' 'SpectralSensitivity'
    '8827' 'ISOSpeedRatings'
    '8828' 'OECF'
    '9000' 'ExifVersion'
    '9003' 'DateTimeOriginal'
    '9004' 'DateTimeDigitized'
    '9101' 'ComponentsConfiguration'
    '9102' 'CompressedBitsPerPixel'
    '9201' 'ShutterSpeedValue'
    '9202' 'ApertureValue'
    '9203' 'BrightnessValue'
    '9204' 'ExposureBiasValue'
    '9205' 'MaxApertureValue'
    '9206' 'SubjectDistance'
    '9207' 'MeteringMode'
    '9208' 'LightSource'
    '9209' 'Flash'
    '920A' 'FocalLength'
    '9214' 'SubjectArea'
    '927C' 'MakerNote'
    '9286' 'UserComment'
    '9290' 'SubsecTime'
    '9291' 'SubsecTimeOriginal'
    '9292' 'SubsecTimeDigitized'
    'A000' 'FlashpixVersion'
    'A001' 'ColorSpace'
    'A002' 'PixelXDimension'
    'A003' 'PixelYDimension'
    'A004' 'RelatedSoundFile'
    'A005' 'Interoperatibility IFD Pointer'
    'A20B' 'FlashEnergy'
    'A20C' 'SpatialFrequencyResponse'
    'A20E' 'FocalPlaneXResolution'
    'A20F' 'FocalPlaneYResolution'
    'A210' 'FocalPlaneResolutionUnit'
    'A214' 'SubjectLocation'
    'A215' 'ExposureIndex'
    'A217' 'SensingMethod'
    'A300' 'FileSource'
    'A301' 'SceneType'
    'A302' 'CFAPattern'
    'A401' 'CustomRendered'
    'A402' 'ExposureMode'
    'A403' 'WhiteBalance'
    'A404' 'DigitalZoomRatio'
    'A405' 'FocalLengthIn35mmFilm'
    'A406' 'SceneCaptureType'
    'A407' 'GainControl'
    'A408' 'Contrast'
    'A409' 'Saturation'
    'A40A' 'Sharpness'
    'A40B' 'DeviceSettingDescription'
    'A40C' 'SubjectDistanceRange'
    'A420' 'ImageUniqueID'

    % Interoperatibility tags

    '001' 'InteroperatibilityIndex'
    '002' 'InteroperatibilityVersion'
    '1000' 'RelatedImageFileFormat'
    '1001' 'RelatedImageWidth'
    '1002' 'RelatedImageLength'
  };

  dict = [];
  for i=1:size(t,1)
    key   = sprintf('K%04X', hex2dec(t{i,1}));
    value = t{i,2};
    dict.(key) = strrep(value, ' ', '_');
  end
end

%
% EXIF GPS tags
%
function dict = gpstags()
  t = {
    0 'GPSVersionID'
    1 'GPSLatitudeRef'
    2 'GPSLatitude'
    3 'GPSLongitudeRef'
    4 'GPSLongitude'
    5 'GPSAltitudeRef'
    6 'GPSAltitude'
    7 'GPSTimeStamp'
    8 'GPSSatellites'
    9 'GPSStatus'
    10 'GPSMeasureMode'
    11 'GPSDOP'
    12 'GPSSpeedRef'
    13 'GPSSpeed'
    14 'GPSTrackRef'
    15 'GPSTrack'
    16 'GPSImgDirectionRef'
    17 'GPSImgDirection'
    18 'GPSMapDatum'
    19 'GPSDestLatitudeRef'
    20 'GPSDestLatitude'
    21 'GPSDestLongitudeRef'
    22 'GPSDestLongitude'
    23 'GPSDestBearingRef'
    24 'GPSDestBearing'
    25 'GPSDestDistanceRef'
    26 'GPSDestDistance'
  };

  dict = [];
  for i=1:size(t,1)
    key   = sprintf('K%04X', t{i,1});
    value = t{i,2};
    dict.(key) = strrep(value, ' ', '_');
  end
end


------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
Octave-dev mailing list
Octave-dev@...
https://lists.sourceforge.net/lists/listinfo/octave-dev

Re: Contribution to octave forge image processing toolbox

by Søren Hauberg :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

ons, 23 09 2009 kl. 12:07 +0200, skrev Roderick Köhle:
> for my personal need, I developed a Exif Reader for octave, allowing to
> read additional informations from Jpeg images.

Cool! I've added this to the 'image' package; it will be part of the
next release.

Søren


------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
Octave-dev mailing list
Octave-dev@...
https://lists.sourceforge.net/lists/listinfo/octave-dev

Re: Contribution to octave forge image processing toolbox

by Paul Sundvall :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Speaking of releases, what does the process of releasing new package
versions look like? Should all packages be released at the same time?
Is it the maintainer of each package that decides?
At which level should the changelog be?

The specific reason I ask is that I think it would be nice to bump the
version number of octave-sockets as the documentation has been updated
and some bugs are removed.
Does anyone disagree if I do this for the octave-sockets package?

How and when are the documentation and download pages at
http://octave.sourceforge.net/packages.html updated? Does the update
happen automagically when the version number in the repository changes?

Paul Sundvall

Søren Hauberg skrev:
> ons, 23 09 2009 kl. 12:07 +0200, skrev Roderick Köhle:
>> for my personal need, I developed a Exif Reader for octave, allowing to
>> read additional informations from Jpeg images.
>
> Cool! I've added this to the 'image' package; it will be part of the
> next release.
>
> Søren
>


------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
Octave-dev mailing list
Octave-dev@...
https://lists.sourceforge.net/lists/listinfo/octave-dev

Release System [was: Re: Contribution to octave forge image processing toolbox]

by Søren Hauberg :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

ons, 23 09 2009 kl. 17:32 +0200, skrev Paul Sundvall:
> Speaking of releases, what does the process of releasing new package
> versions look like?

Short answer: it's a mess! For details, see the 'admin/release.sh'
script.

> Should all packages be released at the same time?

IMHO they shouldn't, but with the current system they are.

> Is it the maintainer of each package that decides?

At the moment, I basically decide when releases should happen. Each
release requires quite a bit of (error-prone) work on my behalf.

There is work going on (see the 'generate_html' package for details) on
changing the release system, but work is going very slowly due to lack
of time.

> At which level should the changelog be?

We autogenerate changelogs from SVN commit messages.

> The specific reason I ask is that I think it would be nice to bump the
> version number of octave-sockets as the documentation has been updated
> and some bugs are removed.
> Does anyone disagree if I do this for the octave-sockets package?

I wouldn't mind this, just check that it haven't been bumped already
first :-)

> How and when are the documentation and download pages at
> http://octave.sourceforge.net/packages.html updated? Does the update
> happen automagically when the version number in the repository changes?

Sadly no. The documentation is generated during the release process. See
the 'admin/release.sh' script for the details.

Søren


------------------------------------------------------------------------------
Come build with us! The BlackBerry® Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9-12, 2009. Register now!
http://p.sf.net/sfu/devconf
_______________________________________________
Octave-dev mailing list
Octave-dev@...
https://lists.sourceforge.net/lists/listinfo/octave-dev