ReadTIFFpackage

Currently this defines an almost-baseline 
TIFF Class G image file reader

by Simon Chandler

4/4/95

version 1.2

From the Author

If you have any comments about this package I'd like to hear them. For example, has the ReadTIFFg function failed to read one of your grayscale TIFF files?  If you could send me details of the failure (e.g., any warning messages, the ReadTIFFpackage version number etc.) together with the image file then I'll try to fix it.  I'd also like to hear from you if you can tell me how to improve the code - I'm not a programmer and know bits of it are klutzy.

You can contact me by e-mail at :

   simonc@bri.hp.com

by post at :

   Dr Simon Chandler
   Hewlett-Packard Ltd (CPB)
   Filton Road
   Stoke Gifford
   Bristol
   BS12 6QZ

or by phone :

  +44 (272) 228109

I look forward to hearing from you.

Abstract

This package defines a function, ReadTIFFg, that can read a grayscale image stored in the TIFF file format and return a 2-D matrix (i.e., a List of Lists) representing the image's bitmap.  By using a MathLink routine to read the binary files a large image file (>100K) can be read-in in under 10 seconds.

The ReadTIFFg function can currently (v1.x) handle only bilevel (TIFF Class B) and grayscale (TIFF Class G) images that are saved to the file WITHOUT INTERNAL DATA COMPRESSION.  With this exception, ReadTIFFg satisfies the requirements of a format compliant 'baseline' TIFF Class G file reader, and has been tested on files created by the many applications [e.g., xv (on Unix), MacCubeView (on Mac), DigitalDarkroom (Mac), NIH-Image (Mac), XlateGraf (Mac), SuperConvert (Mac), Imagery (Mac), Optimas (PC)].  ReadTIFFg may also, in many cases, successfully read files that violate the TIFF standard in some way.

Several variables containing useful information about the image (e.g., its height and width) and the file's structure are set when the file is read-in, and these may be examined by setting the functions options.

For detailed information, see the extensive documentation at the start of my notebook, ReadTIFFpackage.m.  This includes a summary of the TIFF file format.

ReadTIFFpackage` needs access to the FastBinaryFiles` package written by Todd Gayley at Wolfram Research (tgayley@wri.com).  Documentation at the start of the ReadTIFFpackage.m notebook describes how to obtain FastBinaryFiles` and install it for use by the ReadTIFFpackage`.

After writing ReadTIFFpackage I found out that Jeffrey Adams (jeff@tiag.com) is about to release to the MathSource a more complete TIFF file reader and writer.  This should be able to handle 1, 2, 4, 8, 24 and 32 bit GrayLevel, RGBColor and CMYKColor TIFF files.  His package is called Graphics`Tiff` and the functions are ReadTIFF and WriteTIFF.  I've decided to go ahead and release my package, since his uses MathLink with NEXTSTEP specific code, so until his code is ported to other platforms my ReadTIFFpackage` may prove useful to some users.  To avoid any confusion between his ReadTIFF function and mine (which was called ReadTIFF) I've changed the name of my function to ReadTIFFg.  This also highlights the fact that it is only designed to work on grayscale (Class G) images.

History

Name : ReadTIFFpackage.m
Author : Dr Simon Chandler
Institution : Hewlett-Packard Ltd (CPB), Bristol, UK

First released : 13/6/94, version 1.0

Modifications : 7/7/94, version 1.1
Changes : Found out that Jeffrey Adams (jeff@tiag.com) is about to release to MathSource a more complete TIFF file reader and writer.  This should be able to handle 1, 2, 4, 8, 24 and 32 bit GrayLevel, RGBColor and CMYKColor TIFF files.  His package is called Graphics`Tiff` and the functions are ReadTIFF and WriteTIFF.  I've decided to still release my package, since his uses MathLink with NEXTSTEP specific code.  Until his code is ported to other platforms my readTIFFpackage` may prove useful to some users.  I will 'advertise' the fact that Jeff's package should soon be released.  To avoid any confusion between his ReadTIFF function and mine (which was called readTIFF) I've changed the name of my function to readTIFFg.  This also highlights the fact that it is only designed to work on grayscale (Class G) images.   There have also been a few minor documentation changes.

Modifications : 4/4/95, version 1.2
Changes : Changed function name from readTIFFg to ReadTIFFg at advise of Wolfram Research. Added legal notices. Added more documentation on how to load ReadTIFFpackage.


Notes on current problems : None known

Legal Notice

This Mathematica package is experimental and the author and copyright owner shall have no obligation to maintain or support it.  The author and copyright owner makes no express or implied warranty of any kind with respect to this software, and shall not be liable for any direct, indirect, special, incidental or consequential damages (whether based on contract, tort or any other legal theory) arising in any way from use of the software.

Everyone is granted permission to copy, modify and redistribute this Mathematica package, provided:

 1.  All modified copies shall carry a prominant notice stating who made the last 
      modification and the date of such modification.

 2.  No charge is made for this software or works derived from it. This clause shall not be 
      construed as constraining other software distributed on the same medium as this 
      software, nor is a distribution fee considered a charge.

     Author: Simon Chandler, simonc@bri.hp.com
     Date: 11/7/94
     Copyright Holder:  Hewlett-Packard

Thanks to Prem Chawla (prem@wri.com) and Susan Zukosky (susanz@wri.com) for helping me with this legal stuff.

Background on the TIFF Image File Format

This section presents notes taken from Aldus/Microsoft Technical Memoranda "TIFF 5.0" (dated 8/8/88) and "TIFF Revision 6.0" (dated June 1992).

Full documentation about the TIFF format specification is available by anonymous FTP from  zippy.nimh.nih.gov  in  /pub/image/documents/  or from

	   Aldus Developers Desk
	   Aldus Corporation
	   411 First Avenue South
	   Suite 200
	   Seattle, WA  98104
	   (206) 622-5500

This summary was compiled by Simon Chandler, 10/6/94.  Apologies to Aldus and Microsoft for my severe editing.

Abstract

This documentation begins with an overview of TIFF; a 'tag' based file format for digital image data. The TIFF Class G restricted sub-format is then introduced, with a detailed description of its required fields. Requirements for all 'baseline' TIFF readers are then outlined. Finally, limitations of the (slightly sub-baseline) TIFF Class G reading routine for Mathematica, ReadTIFFg, will be described.

Tag Structure Rationale

A file format is defined by both form (structure) and content. It is the content that we are ultimately interested in - the structure merely tells us how to find it - yet the structure deserves serious consideration for a number of reasons. Since the structure used by TIFF departs significantly from several other approaches, it may be useful to discuss the rationale behind it.

The simplest, most straightforward structure for something like an image file is a positional format. In a positional scheme, the location of the data defines what the data means. For example, the field for 'number of rows' might begin at byte offset 30 in the image file.

This approach is simple and easy to implement and is perfect for static environments. But if a significant amount of ongoing change must be accommodated, subtle problems begin to appear. For example, suppose that a field must be superseded by a new, more general field. You could bump a version number to flag the change. Then new software has no problem doing something sensible with old data, and all old software will reject the new data, even software that didn't care about the old field. This may seem like no more than a minor annoyance at first glance, but causing old software to break more often than it would really need to can be very costly and, inevitably, causes much gnashing of teeth among customers.

Furthermore, it can be avoided. One approach is to store a 'valid' flag bit for each field. Now you don't have to bump the version number, as long as you can put the new field somewhere that doesn't disturb any of the old fields. Old software that didn't care about that old field anyway can continue to function. (Old software that did care will of course have to give up, but this is an unavoidable price to be paid for the sake of progress, barring total omniscience.)

Another problem that crops up frequently is that certain fields are likely to make sense only if other fields have certain values. This is not such a serious problem in practice; it just makes things more confusing. Nevertheless, we note that the 'valid' flag bits described in the previous paragraph can help to clarify the situation.

Field-dumping programs can be very helpful for diagnostic purposes (see, for example, the report option to the Mathematica  ReadTIFFg function). A desirable characteristic of such a program is that it doesn't have to know much about what it is dumping. In particular, it would be nice if the program could dump ASCII data in ASCII format, integer data in integer format, and so on, without having to teach the program about new fields all the time. So maybe we should add a 'data type' component to our fields, plus information on how long the field is, so that our dump program can walk through the fields without knowing what the fields mean.

But note that if we add one more component to each field, namely a tag that tells what the field means, we can dispense with the 'valid' flag bits, and we can also avoid wasting space on the non-valid fields in the file. Simple image creation applications can write out several fields and be done.

We have now derived the essentials of a tag-based image file format.

The TIFF Structure

TIFF is a very rich format designed for the exchange of image data between many types of application (e.g., scanners, digitizers, painting programs). In TIFF, individual fields are identified with a unique tag. This allows particular fields to be present or absent from the file as required by the application.
 
A TIFF file begins with an 8-byte image file header that points to one or more image file directories (IFDs). The image file directories contain information about the images, as well as pointers to the actual image data.

Image file header

A TIFF file begins with an 8-byte image file header, containing the following information:

Bytes 0-1:  The first word of the file specifies the byte order used within the file. Legal values are:

        II  (hex 4949)
        MM  (hex 4D4D)

In the II format, byte order is always from least significant to most significant, for both 16-bit and 32-bit integers. In the MM format, byte order is always from most significant to least significant. In both formats, character strings are stored into sequential byte locations.

Bytes 2-3:  The second word of the file is the TIFF 'version number'. The TIFF version number (42) has never changed, and probably never will (unless there are radical changes in the format). The number 42 was chosen for its deep philosophical significance. It can and should be used as additional verification that this is indeed a TIFF file. 

Bytes 4-7:  This long word contains the offset (in bytes) of the first Image File Directory, which may be at any location in the file after the header. In particular, an Image File Directory may follow the image data it describes. Readers must simply follow the pointers, wherever they may lead.

Image file directory

An Image File Directory (IFD) consists of a 2-byte count of the number of entries (i.e., the number of fields), followed by a sequence of 12-byte field entries, followed by a 4-byte offset of the next Image File Directory (or 0 if there are no further IFDs).

Each 12-byte IFD entry has the following format:

Bytes 0-1:     contain the Tag for the field.
Bytes 2-3:     contain the field Type.
Bytes 4-7:     contain the Length of the field. (Comment: 'Count' or  'ValueCount' may have been   
                      better terms, and the latter has been used in the ReadTIFFg program's code.) 
Bytes 8-11:   contain the ValueOffset, the file offset (in bytes from start of file) of the Value for 
                      the field.  This file offset may point to anywhere in the file, including after the image
                      data.

In order to save time and space, the ValueOffset is interpreted to contain the Value instead of just pointing to it if the Value fits into 4 bytes. If the Value is less than 4 bytes, it is left-justified within the 4-byte ValueOffset. Whether or not the Value fits within 4 bytes is determined by looking at the Type and Length of the field.

The Length is specified in terms of the data type, not the total number of bytes. A single 16-bit word (SHORT) has a Length of 1, not 2, for example. The data types and their lengths are:

1 = BYTE      An 8-bit unsigned integer.
2 = ASCII     8-bit bytes that store ASCII codes; the last byte must be null.
3 = SHORT     A 16-bit (2-byte) unsigned integer.
4 = LONG      A 32-bit (4-byte) unsigned integer.
5 = RATIONAL  Two LONG's: the first represents the numerator of a fraction, the second the    
              denominator.

The value of the Length part of an ASCII field entry includes the null.

TIFF currently allows more than one valid type for some fields. For example, ImageWidth can have the types SHORT or LONG.

Note that there may be more than one IFD. Each IFD is said to define a 'subfile'. One potential use of subsequent subfiles is to describe a 'sub-image' that is somehow related to the main image, such as a reduced resolution version of the image.

TIFF Classes

TIFF was designed to be powerful and flexible at the expense of simplicity. It takes a fair amount of effort to handle all the options currently defined in the TIFF specification. TIFF Classes are restricted TIFF subsets that were introduced to simplify the job of the TIFF implementor.

So far, four TIFF Classes have been defined:

    Class B (TIFF B) for bilevel (1-bit) images
    Class G (TIFF G) for grayscale images
    Class P (TIFF P) for palette color images
    Class R (TIFF R) for RGB full color images

The term "Class X" refers to all classes.

TIFF Classes have a defined subset of "required fields" that must be present in all files of that class. The following section describes the required fields for TIFF Class G. The documentation for each field contains the name of the field (quite arbitrary, but convenient), the Tag value, the field Type, the number of Values (N) expected, and the default, if any.

ImageWidth

Tag = 256  (100)  
Type = SHORT or LONG
N = 1

The image's width, in pixels (X: horizontal). The number of columns in the image.

No default. See also ImageLength.

ImageLength

Tag = 257  (101)
Type = SHORT or LONG
N = 1

The image's length (height) in pixels (Y: vertical). The number of rows (sometimes described as 'scan lines') in the image.

No default. See also ImageWidth.

BitsPerSample

Tag = 258  (102)
Type = SHORT
N = SamplesPerPixel (default = 1)

Number of bits per sample. Note that this tag allows a different number of bits per sample for each sample corresponding to a pixel. For TIFF Class G images BitsPerSample may be 4 or 8.

No default.  See also SamplesPerPixel in the full TIFF specification.

Compression

Tag = 259  (103)
Type = SHORT
N = 1

Compression == 1 when the data is not compressed. For Class G, bytes are stored as a simple array of type BYTE. Rows are required to begin on byte boundaries. Therefore, the number of bytes per row is:
 
 (ImageWidth * BitsPerSample + 7) / 8  
 
assuming integer arithmetic.
    
When Compression is not equal to 1 a data compression scheme has been applied to the raster image data, as pointed to by StripOffsets. All other TIFF information is unaffected. The several possible data compression schemes are discussed in the full TIFF specification.

Baseline TIFF X readers MUST handle several possible compression schemes, and the TIFF specification recommends that compression is used.

Default = 1.

PhotometricInterpretation

Tag = 262  (106)
Type = SHORT
N = 1

value = 0 :
For bilevel and grayscale images: 0 is imaged as white, 2**BitsPerSample-1 is imaged as black. If GrayResponseCurve exists (not required for baseline TIFF G), it overrides the PhotometricInterpretation value, although it is safer to make them match, since some applications (including ReadTIFFg) may ignore GrayResponseCurve.

value = 1 :
For bilevel and grayscale images: 0 is imaged as black, 2**BitsPerSample-1 is imaged as white. Above comments about GrayResponseCurve apply.

Values of 2, 3 and 4 apply to RGB, palette colour and transparency mask images respectively so are not valid in TIFF class G image files.

PhotometricInterpretation has no default value and is required, so should strictly be defined in each TIFF G file. Applications should not default to whatever they want !  PhotometricInterpretation = 1 is recommended for grayscale images.

StripOffsets

Tag = 273  (111)
Type = SHORT or LONG 
N = StripsPerImage for PlanarConfiguration == 1. 
N = SamplesPerPixel * StripsPerImage for PlanarConfiguration == 2.

For each strip, the byte offset of that strip. The offset is specified with respect to the beginning of the TIFF file. Note that this implies that each strip has a location independent of the locations of other strips. This feature may be useful for editing applications. This field is the only way for a reader to find the image data, and hence must exist.

Note that either SHORT or LONG values can be used to specify the strip offsets. SHORT values may be used for small TIFF files.  It should be noted, however, that earlier TIFF specifications required LONG strip offsets and that some software may not expect SHORT values.

No default.  See also StripByteCounts, RowsPerStrip (and in full TIFF specification, SamplesPerPixel and PlanarConfiguration).  The pseudo-variable StripsPerImage is described in documentation for RowsPerStrip.

RowsPerStrip

Tag = 278  (116)
Type = SHORT or LONG
N = 1

The number of rows per strip. The image data is organized into strips for fast access to individual rows when the data is compressed; though this field is valid even if the data is not compressed.

RowsPerStrip and ImageLength together give the number of strips in the entire image, from:
 
 StripsPerImage = (ImageLength + RowsPerStrip - 1) / RowsPerStrip
 
assuming integer arithmetic.

Note that either SHORT or LONG values can be used to specify RowsPerStrip. SHORT values may be used for small TIFF files.  It should be noted, however, that earlier TIFF specification revisions required LONG values and that some software may not expect SHORT values.

Default is 2**32 - 1, which is effectively infinity. That is, the entire image is one strip. A single strip is not recommended, however. RowsPerStrip is usually chosen so that each strip is about 8K bytes, since it makes buffering simpler for readers.

See also ImageLength, StripOffsets, StripByteCounts.

StripByteCounts

Tag = 279  (117)
Type = SHORT or LONG
N = StripsPerImage for PlanarConfiguration == 1. 
N = SamplesPerPixel * StripsPerImage for PlanarConfiguration == 2.

For each strip, the number of bytes in that strip. Many existing TIFF images do not contain StripByteCounts, because, in a strict sense, they are unnecessary. However, the existence of this field greatly simplifies the chore of buffering compressed data, if the strip size is reasonable. For this reason, StripByteCounts is required in all TIFF X files.

No default.  See also StripOffsets, RowsPerStrip (and in full TIFF specification, SamplesPerPixel and PlanarConfiguration).  The pseudo-variable StripsPerImage is described in documentation for RowsPerStrip.

XResolution

Tag = 282  (11A)
Type = RATIONAL
N = 1

The number of pixels per ResolutionUnit in the X direction, i.e., in the ImageWidth direction. It is, of course, not mandatory that the image be actually printed at the size implied by this parameter. It is up to the application to use this information as it wishes. Note that the X and Y resolutions may be unequal. A TIFF reader must be able to handle this case.  In many cases XResolution is set to zero when the image size is uncalibrated.

No default.  See also YResolution, ResolutionUnit.

YResolution

Tag = 283  (11B)
Type = RATIONAL
N = 1

The number of pixels per ResolutionUnit in the Y direction, i.e., in the ImageLength direction. Note that the X and Y resolutions may be unequal. A TIFF reader must be able to handle this case. In many cases YResolution is set to zero when the image size is uncalibrated. 

No default.  See also XResolution, ResolutionUnit.

ResolutionUnit

Tag = 296 (128)
Type = SHORT
N = 1

To be used with XResolution and YResolution.

value = 1 :
No absolute unit of measurement. Used for images that may have a non-square aspect ratio, but no meaningful absolute dimensions. The drawback of ResolutionUnit=1 is that different applications will import the image at different sizes. Even if the decision is quite arbitrary, it might be better to use dots per inch or dots per centimeter, and pick XResolution and YResolution such that the aspect ratio is correct and the maximum dimension of the image is about four inches (the 'four' is quite arbitrary). In many cases XResolution and YResolution are set to zero when the image size is uncalibrated. 

value = 2 : Inch. 

value = 3 : Centimeter.

TIFF readers must be prepared to handle all three values for ResolutionUnit.

Default is 2.  See also XResolution, YResolution.

Baseline TIFF Readers

The following are required characteristics of all 'baseline' TIFF Class X file readers.

Options. Where there are options TIFF Class X readers must be able to handle all of them.

Defaults. TIFF X writers may, but are not required, to write out a field that has a default value, if the default value is the one desired. TIFF X readers must be prepared to handle either situation, with the default value used if the field does not exist.

Non-required and unknown fields. TIFF X readers must be prepared to encounter and ignore non-required fields and private fields not described in the TIFF specification. TIFF X writers are allowed to write fields such as Make, Model, DateTime, and so on, and TIFF X readers can certainly make use of such fields if they exist. TIFF X readers must not, however, refuse to read the file if such optional fields do not exist.

MM and II byte order. TIFF X readers must be able to handle both byte orders. 

Multiple subfiles. TIFF X readers must be prepared for multiple images (i.e., subfiles) per TIFF file, although they are not required to do anything with any image after the first one.

Value Type. TIFF readers should check the type to ensure that it is valid for the tag. TIFF currently allows more than one valid type for some fields. For example, ImageWidth and ImageLength can have types SHORT or LONG. A baseline TIFF reader must be prepared to encounter and skip over (gracefully) unexpected field types, such as floating-point data. It must not expect BYTE, ASCII, SHORT, LONG and RATIONAL to be a complete list of field types.

Info on the ReadTIFFpackage` and the ReadTIFFg Function

Limitations of the ReadTIFFg Mathematica Function

In all respects other than the following the ReadTIFFg function works as a "law abiding" baseline TIFF Class G reader:

ReadTIFFg cannot handle compressed TIFF files. This was done on purpose because I needed to speed up the writing of ReadTIFFg, and because I think the need for decompression can be avoided; I think most users of ReadTIFFg can be forewarned not to compress their images.

How to use the ReadTIFFpackage

First, put the file ReadTIFFpackage somewhere that your Mathematica Kernel can access it, i.e., somewhere that is included in the $Path variable. You will then (after a few more steps - see below) be able to load the package by simply typing

   Needs["ReadTIFFpackage`"]

Alternatively, you can put the ReadTIFFpackage somewhere that is not explicitly on the Mathematica $Path but you must then use a more complete pathname when loading it. For example, if the following directory 

   /users/coreResearch/math/PackagesExtension/

is in $Path and you create in it a new directory 

   /users/coreResearch/math/PackagesExtension/graphics

into which you put the ReadTIFFpackage, then you will have to load ReadTIFFpackage using

   Needs["graphics`ReadTIFFpackage`"]

This will produce a warning message

because the context added to $ContextPath is simply ReadTIFFpackage` (rather than the expected graphics`ReadTIFFpackage). This is nothing to worry about.

ReadTIFFpackage needs access to the FastBinaryFiles` package written by Todd Gayley at Wolfram Research (tgayley@wri.com).  For details on this package see "The Mathematica Journal", Vol. 4, No. 2, Spring 1994, pp.44-51.  The package can be obtained from the MathSource archive, where the package is item number 0206-783.  Get it from the mail server using this number, or by anonymous ftp (to mathsource.wri.com) from the /pub/NumberedItems/ directory.

To give your Mathematica kernel access to the FastBinaryFiles` package you will need to either manually load it or make a small change to the ReadTIFFpackage.m file in order to automatically load it.

To manually load FastBinaryFiles`

Before loading the ReadTIFFpackage with

   Needs["ReadTIFFpackage`"],

manually install the FastBinaryFiles` package.  This is done with the Install command, using an argument which is the full pathname to the MathLink binary executable file for FastBinaryFiles` (if you don't know what this means see the FastBinaryFiles package documentation or the Mathematica Journal article mentioned above).  The ReadTIFFpackage will detect that FastBinaryFiles` has been loaded and should load successfully itself.

To automatically load FastBinaryFiles`

In the section of this ReadTIFFpackage.m file entitled

  "Implementation of the Package: 
    set up the package context, including any imports, installs etc"

(which is only about one page on from here) change the variable binaryFilename to be the full pathname to where you keep the MathLink binary executable file for FastBinaryFiles` (again, if you don't know what this means see the FastBinaryFiles package documentation or the Mathematica Journal article mentioned above).

On my computer system the executable is 

  /users/coreResearch/math/PackagesExtension/fileRoutines/binaryFiles/binary
      
so, by default, the code sets the variable to
      
binaryFilename =
"/users/coreResearch/math/PackagesExtension/fileRoutines/binaryFiles/binary";

If this correctly points to the executable then the ReadTIFFpackage will automatically load FastBinaryFiles` if required.

If either of these have been done then the ReadTIFFpackage will load successfully and the 
ReadTIFFg function will be available.  For more help type ? ReadTIFFg after loading the package.  
The following lines give an example of importing the package, loading an image and plotting it.

			   Needs["ReadTIFFpackage`"];
			
			SetDirectory["/users/simonc/experiments/imageProc/l#.tif/"];
			
			myImage =    
			 ReadTIFFg[
			    "theImage.tif",
			    report -> True,
			    ignoreNonBaseline->False
			 ]
			
			ListDensityPlot[
			   myImage,
			   Mesh -> False
			];

Implementation of the Package

In early versions of this package (including v1.2) I will leave in numerous comments and commented-out pieces of code that proved useful during development and debug.  The commented-out parts are shown in this colour, which on my Mac is BLUE.  The comments are in this colour, which on my Mac is MAGENTA.  I may take these out at some future date but I doubt it. Should I ?

set up the package context, including any imports, installs etc

Because the FastBinaryFiles` package is actally a MathLink program it can't be imported into this package in the usual way (see Programming in Mathematica by Roman Maeder sections 2.2.1 and 2.2.2).  The following section checks to see if it is already imported, in which case it just starts the definition of the ReadTIFFpackage.  If it is not already known then we try to install the MathLink executable associated with the FastBinaryFiles` package from a default location, specified by the following variable, binaryFilename.

binaryFilename = "/users/coreResearch/math/PackagesExtension/fileRoutines/binaryFiles/binary";

If[
   Not[
      MemberQ[
         $ContextPath,
         "FastBinaryFiles`"
      ]
   ],
   If[
      FileType[binaryFilename] === File,
   (* it's there - so do it *)
      link = Install[binaryFilename],
   (* else, say its not and quit *)
			   Print["The ReadTIFFpackage` needs access to the FastBinaryFiles` package."];
      Print["Either manually 'Install[]' the relevant MathLink executable file"];
      Print["before using Needs[\"ReadTIFFpackage`\"], or move the "];
      Print["executable to the following location :"];
			   Print[""];
			   Print[binaryFilename];
			   Print[""];
			   Print["If this file exists then the FastBinaryFiles` package will be"];
			   Print["loaded automatically by the ReadTIFFpackage. I've already looked"];
      Print["there and the file doesn't exist; the ReadTIFFpackage can't load."];
      Print["For more information see documentation at the beginning of the file"];
      Print["\"ReadTIFFpackage.m\""];
      Print["\n\nTerminating the loading of ReadTIFFpackage.m.  Bye Bye"];
      Abort[]
   ]
];

BeginPackage[ "ReadTIFFpackage`" ]

If we get to this point then the FastBinaryFiles` package has loaded or was already loaded.  We need to make this known to trhe Kernel inside the BeginPackage definition of ReadTIFFpackage, so we need to add the packages name to the context path.

PrependTo[$ContextPath,"FastBinaryFiles`"];

This is all a bit klutzy, since importing required packages is usually handled by Needs[].  It is because I'm installing a MathLink program that I've had to do all this manually.  I'll ask the author of FastBinaryFiles, Todd Gayley, if he can think of a better way of importing it into other packages.

usage messages

for the context itself

ReadTIFFpackage::usage = "This package defines a function, ReadTIFFg, that can import into Mathematica a grayscale image stored in the TIFF format. \n\nReadTIFFg[filename_String] reads-in the TIFF file and returns a 2-D matrix (i.e., a List of Lists) representing the image's bitmap. In addition, several variables containing useful information about the image (e.g., its height and width) are set when the file is read-in. These variables all start with \"tiff\"; see ? tiff*.  \n\nThe ReadTIFFg function can currently (v1.x) handle only bilevel (TIFF Class B) and grayscale (TIFF Class G) images that are saved to the file WITHOUT INTERNAL DATA COMPRESSION.  With this exception, ReadTIFFg satisfies the requirements of a format compliant 'baseline' TIFF Class G file reader, and has been successfully tested on files created by the following applications: xv (on Unix), MacCubeView (on Mac), DigitalDarkroom (Mac), NIH-Image (Mac), XlateGraf (Mac), SuperConvert (Mac), Imagery (Mac), Optimas (PC). \n\nFor detailed information, see the documentation given at the start of the \"ReadTIFFpackage`\" notebook. \n\nSee also ? ReadTIFFg, ? tiff*, ? tiffImageWidth, ? tiffImageLength, ? tiffBitsPerSample, ? tiffCompression, ? tiffPhotometricInterpretation, ? tiffStripOffsets, ? tiffRowsPerStrip, ? tiffStripByteCounts, ? tiffXResolution, ? tiffYResolution and ? tiffResolutionUnit";

for exported function(s)

ReadTIFFg::usage = "ReadTIFFg[filename_String] reads a grayscale image stored in the TIFF file format, and returns the 2-D matrix (i.e., a List of Lists) representing the image's bitmap. For detailed information see ? ReadTIFFpackage";

for exported variables/values

tiffImageWidth::usage = "tiffImageWidth (an integer) is the image's horizontal width in pixels. Equivalently, it is the number of columns in the bitmap image.  \n\nSee also ? tiffImageLength.";

tiffImageLength::usage ="tiffImageLength (an integer) is the image's length (height) in pixels. Equivalently, it is the number of rows (sometimes described as 'scan lines') in the bitmap image.  \n\nSee also ? ImageWidth.";

tiffBitsPerSample::usage = "tiffBitsPerSample (an integer: 4 or 8) is the number of bits per sample. For TIFF Class G images BitsPerSample may be 4 or 8. \n\nSee also \"SamplesPerPixel\" in the full TIFF specification.";

tiffCompression::usage = "When tiffCompression equals 1 the image data is not compressed and the ReadTIFFg function will be able to import the image into Mathematica.  When tiffCompression is not equal to 1 a data compression scheme has been applied (internally) to the image data and it cannot be read-in.  Format compliant 'baseline' TIFF file readers are required to handle several possible compression schemes; however, the ReadTIFFg Mathematica function cannot currently do this (version 1.x, N.B., this is the only shortfall from being a fully compliant TIFF Class G reader).  I'm hoping that most users of the ReadTIFFg package will be forewarned, so will be able to avoid using compressed images. If you do find lack of a decompression facility a big problem then contact the author (simonc@bri.hp.com) for advice. If you're running out of disk space you can always compress the image files externally (e.g., using Unix 'compress' command) then uncompress them (either manually or by calling the Unix command from within Mathematica) before using the ReadTIFFg function.";

tiffPhotometricInterpretation::usage = "tiffPhotometricInterpretation (an integer: 0-4) determines how a pixel's color is calculated from its value.  For 8-bit grayscale images the color varies linearly from white to black - with 253 intermediate shades of gray.  When tiffPhotometricInterpretation = 0 then a pixel with value 0 is imaged as white, whilst one with value (2**BitsPerSample-1) is imaged as black; this may be called \"white-is-zero format\".  When tiffPhotometricInterpretation = 1 the system is reversed and pixels with value (2**BitsPerSample-1) are imaged as white whilst pixels with value 0 are imaged as black; this may be called \"black-is-zero format\".\n\nNote: the ReadTIFFg program does not handle the IFD field,  \"GrayResponseCurve\" (see full TIFF specification).\n\nNote:  tiffPhotometricInterpretation values of 2, 3 and 4 apply to non-grayscale images so are not valid in Class G files.  I originally allowed the pixel values of colour images to be read-in, even though they were incorrectly interpreted. This caused a few programming problems and also seemed silly so, in the end, I decided to reject colour images completely. If you do need to load a colour image as grayscale there are several powerful image manipulation programs that can do this properly (e.g., xv on Unix).";

tiffStripOffsets::usage = "tiffStripOffsets is a List of integer values that, for each strip, gives the byte offset of that strip from the beginning of the file; the Nth element in tiffStripOffsets is the offset of the Nth strip.\n\nSee also ? tiffStripByteCounts, ? tiffRowsPerStrip, ? tiffStripsPerImage (and, in the full TIFF specification, \"SamplesPerPixel\" and \"PlanarConfiguration\").  The pseudo-variable \"StripsPerImage\" is described in ? tiffRowsPerStrip.";

tiffRowsPerStrip::usage = "tiffRowsPerStrip (an integer) is the number of rows in each strip (other than the last, which may contain fewer rows). The image data is organized into strips for fast access to individual rows when the data is compressed; though this field is valid even if the data is not compressed.  The image file's fields tiffRowsPerStrip and tiffImageLength together give the number of strips in the entire image, see ? tiffStripsPerImage. \n\nSee also ? tiffImageLength, ? tiffStripOffsets, ? tiffStripByteCounts.";

tiffStripByteCounts::usage = "tiffStripByteCounts is a List of integer values that gives the number of bytes in each strip; the Nth element in tiffStripByteCounts is the number of rows in the Nth strip. Many existing TIFF images do not contain this field because it is not strictly necessary; however, tiffStripByteCounts IS required in all TIFF G files.  If the tiffStripByteCounts field is missing the image cannot be read by the Mathematica ReadTIFFg function.\n\nSee also ? tiffStripOffsets, ? tiffRowsPerStrip, ? tiffStripsPerImage (and, in the full TIFF specification, \"SamplesPerPixel\" and \"PlanarConfiguration\").  The pseudo-variable \"StripsPerImage\" is described in ? tiffRowsPerStrip.";

tiffStripsPerImage::usage = "tiffStripsPerImage is NOT an IFD field. It is merely a value that any TIFF reader must calculate in order to check the number of tiffStripOffsets and the number of tiffStripByteCounts.\n\n tiffStripsPerImage = Floor[(tiffImageLength+tiffRowsPerStrip-1)/tiffRowsPerStrip] \n\nSee also ? tiffImageLength, ? tiffRowsPerStrip, ? tiffStripOffsets, ? tiffStripByteCounts.";

tiffXResolution::usage = "tiffXResolution (an integer) is the number of pixels per tiffResolutionUnit in the X direction, i.e., in the tiffImageWidth direction. In many cases tiffXResolution is set to zero when the image size is uncalibrated.\n\nSee also ? tiffYResolution, ? tiffResolutionUnit.";

tiffYResolution::usage = "tiffYResolution (an integer) is the number of pixels per tiffResolutionUnit in the Y direction, i.e., in the tiffImageLength direction. In many cases tiffYResolution is set to zero when the image size is uncalibrated.\n\nSee also ? tiffXResolution, ? tiffResolutionUnit.";

tiffResolutionUnit::usage = "tiffResolutionUnit (an integer:1,2 or 3) is used with tiffXResolution and tiffYResolution.  When tiffResolution = 1 there is no absolute unit of measurement; however, tiffXResolution and tiffYResolution may be different indicating that the image has a non-square aspect ratio but no meaningful absolute dimensions.  In many cases tiffXResolution and tiffYResolution are set to zero when the image size is truely uncalibrated. When tiffResolution = 2 the resolution unit is an inch.  When tiffResolution = 3 the resolution unit is a centimeter. \n\nSee also ? tiffXResolution, ? tiffYResolution.";

tiffArtist::usage = "tiffArtist is an optional ASCII string containing information about the person who created the image. Copyright information may also be written here. This field is often placed immediately after the 8-byte TIFF header.\n\nSee also tiffCopyright.";

tiffCopyright::usage = "tiffCopyright is an optional ASCII string containing copyright information about the image.\n\nSee also tiffArtist.";

tiffHostComputer::usage = "tiffHostComputer is an optional ASCII string containing information about the computer used to create the image file.\n\nSee also tiffMake, tiffModel, tiffSoftware.";

tiffImageDescription::usage = "tiffImageDescription is an optional ASCII string containing information about the image (e.g., \"1988
company picnic\"). This is what the newspaper and magazine industry call a \"slug\".";

tiffMake::usage = "tiffMake is an optional ASCII string containing information about the manufacturer of the scanner, video digitizer, or whatever.\n\nSee also tiffModel, tiffSoftware, tiffHostComputer.";

tiffModel::usage = "tiffModel is an optional ASCII string containing information about the the model name/number of the scanner, video digitizer, or whatever. This tag is intended for user information only.\n\nSee also tiffMake, tiffSoftware, tiffHostComputer.";

tiffMaxSampleValue::usage = "tiffMaxSampleValue (an integer) is the maximum used sample value in the image.  For example, if the image consists of 6-bit data low-order-justified into 8-bit bytes, MaxSampleValue will be no greater than 63. This field is not to be used to affect the visual appearance of the image when displayed.  Nor should the values of this field affect the interpretation of any other field. Use it for statistical purposes only.\n\nSee also tiffMinSampleValue.";

tiffMinSampleValue::usage = "tiffMinSampleValue (an integer) is the minimum used sample value in the image.  This field is not to be used to affect the visual appearance of the image when displayed.  Nor should the values of this field affect the interpretation of any other field. Use it for statistical purposes only.\n\nSee also tiffMaxSampleValue.";

tiffNewSubfileType::usage = "The value of tiffNewSubfileType (an integer) is a general indication of the kind of data that is contained in the subfile.  This field is made up of a set of 32 flag bits. Unused bits are 0. Bit 0 is the low-order bit. Currently defined values are: \n\nBit 0 is 1 if the image is a reduced resolution version of another image in the TIFF file; else the bit is 0. \n\nBit 1 is 1 if the image is a single page of a multi-page image (see the \"PageNumber\" tag description in the full TIFF specification); else the bit is 0. \n\nBit 2 is 1 if the image defines a transparency mask for another image in this TIFF file. The tiffPhotometricInterpretation value must then be 4,
designating a transparency mask.\n\ntiffNewSubfileType replaces the old \"SubfileType\" field (see full TIFF specification), due to limitations in the definition of that field.";

tiffOrientation::usage = "tiffOrientation is an integer (1-8) representing the orientation of the image. The standard/default orientation of a TIFF image is with the 0th row representing the visual top of the image, and the 0th column representing the visual left-hand side of the image. Other orientations are possible, and these are indicated by a non-zero value of tiffOrientation. This version of the Mathematica ReadTIFFpackage (v1.x) does not support non-default orientations, however, it should be easy enough to get Mathematica to rotate (etc) the List returned by the ReadTIFFg function, so converting it to the correct orientation. Possible image orientations are \n\ntiffOrientation = 1: The 0th row represents the visual top of the image, and the 0th column represents the visual left hand side. \n\ntiffOrientation = 2: The 0th row represents the visual top of the image, and the 0th column represents the visual right hand side. \n\ntiffOrientation = 3: The 0th row represents the visual bottom of the image, and the 0th column represents the visual right hand side. \n\ntiffOrientation = 4: The 0th row represents the visual bottom of the image, and the 0th column represents the visual left hand side. \n\ntiffOrientation = 5: The 0th row represents the visual left hand side of the image, and the 0th column represents the visual top. \n\ntiffOrientation = 6: The 0th row represents the visual right hand side of the image, and the 0th column represents the visual top. \n\ntiffOrientation = 7: The 0th row represents the visual right hand side of the image, and the 0th column represents the visual bottom. \n\ntiffOrientation = 8: The 0th row represents the visual left hand side of the image, and the 0th column represents the visual bottom.";

tiffSoftware::usage = "tiffSoftware is an optional ASCII string containing information about the name and release number of the software package that created the image. This tag is intended for user information only.\n\nSee also tiffMake, tiffModel, tiffHostComputer.";

tiffDateTime::usage = "tiffDateTime is an optional ASCII string containing information about the date and time of image creation. The format of this string is YYYY:MM:DD HH:MM:SS, with hours on a 24-hour clock, and one space character between the date and the time. The length of the string, including the null character at the end, is always 20 bytes.\n\nSee also tiffImageDescription.";

tiffSamplesPerPixel::usage = "tiffSamplesPerPixel (an integer) is the number of samples per pixel. SamplesPerPixel is 1 for bilevel, grayscale, and palette color images. SamplesPerPixel is 3 for RGB images. \n\nSee also tiffBitsPerSample, tiffPhotometricInterpretation.";

tiffDocumentName::usage = "tiffDocumentName is an optional ASCII string containing the name of the document from which the image was scanned. \n\nSee also tiffPageName.";

tiffPageName::usage = "tiffPageName is an optional ASCII string containing the name of the page from which this image was scanned. \n\nSee also tiffDocumentName.";

for options to exported functions

report::usage = "This option is included as a 'field-dumping' utility to give technical users (e.g., the author of ReadTIFFg) a diagnostic tool with which to examine the guts of a TIFF file. Most users of ReadTIFFg will not be interested in this option, and some knowledge of the TIFF specification is required in order to understand its output. \n\nreport -> True, prints a description of entries (aka 'fields') in the Image File Directory (IFD), including their tag numbers and their values.  Fields that are 'required' by Class G are always printed when report -> True.  When ignoreNonBaseline -> False (in addition to report->True) a report will also be printed for other, non-required fields that are 'known' by the Mathematica code that defines the ReadTIFFg package.  To make a field 'known', just fill in an entry for it in the List variable, nonbaselineTIFFparameters, found in the \"definition of local (static) variables\" section of the \"ReadTIFFpackage.m\" notebook.  'Unknown' fields are not printed by the report option, however a Message[] is printed about them whenever ignoreNonBaseline -> False\n\nThe default value for the report option is False. See also ? ignoreNonBaseline, ? ReadTIFFg, ? ReadTIFFpackage.";

ignoreNonBaseline::usage = "This option is included to allow technical users (e.g., the author of ReadTIFFg) to turn-on handling of non-required fields in a TIFF Class G file.  Most users of ReadTIFFg will not be interested in this facility, and some knowledge of the TIFF specification is probably required in order to use it. \n\nignoreNonBaseline -> True, the default setting, causes all entries in the Image File Directory (IFD) that are not required for TIFF Class G images to be totally ignored.  With ignoreNonBaseline -> False non-required fields are read and, if necessary and possible, are processed.  Note: with ignoreBaseline -> True a warning will not be given if the IFD contains unknown fields.  \n\nThe default value for the ignoreNonBaseline option is True. See also ? report, ? ReadTIFFg, ? ReadTIFFpackage.";

declare options to exported functions

Options[ReadTIFFg] = {report -> False, ignoreNonBaseline -> True};

begin the private context

Begin["`Private`"]

unprotect any system functions for which rules will be defined

(* protected = Unprotect[ Sin, Cos ] *)
(* use blue colour to show commented-out parts, and magenta to show comments *)

error messages for the exported objects

ReadTIFFg::formatErr = "Format error type `1` occurred.";

ReadTIFFg::filenameErr = "Sorry, the file \"`1`\" does not exist.";

ReadTIFFg::typeErr = "Cannot load the file because the Type of a required baseline field is invalid.\n\nTerminating import process. Bye Bye.";

ReadTIFFg::valueErr = "Because the value of the required baseline parameter is invalid, this file cannot be read-in. \n\nTerminating import process. Bye Bye.";

ReadTIFFg::unknownTag = "This TIFF file's IFD contains a field for an unknown tag (`1`) of type `2`  There are `3` values with Value/Offset `4`. This field will be ignored, but the image will be read-in.";

ReadTIFFg::compressedErr = "A compression scheme was used on the image data in this TIFF file. Sadly, this version of the Mathematica ReadTIFFg package (v1.x) cannot handle compressed images.  This is a serious shortfall and violates the minimum requirements for a true baseline TIFF reader; however, I'm hoping that most users of the ReadTIFFg package will be able to avoid using compressed images. If you do find lack of a decompression facility a big problem then contact Simon Chandler (simonc@bri.hp.com) for advice. \n\nTerminating import process. Bye Bye.";

ReadTIFFg::orientationErr = "The standard/default orientation of a TIFF image is with 0th row representing the visual top of the image, and the 0th column representing the visual left-hand side of the image.  This file's TIFF image has a different orientation (type `1`, see p.36 of the full TIFF specification document).  This version of the Mathematica ReadTIFFpackage (v1.x) does not support non-default orientations, however, it should be easy enough to get Mathematica to rotate (etc) the List returned by this function, so convert it to the correct orientation. See ? tiffOrientation for explanation of the orientation types.";

ReadTIFFg::stripErr = "This file violates the TIFF format. The bitmap image should be broken into one or more 'strips' with a stated numer of bytes in each strip. The IFD shows there should be `3` strip(s) (see ? tiffStripsPerImage), however, the IFD also gives `1` strip file offset(s) (see ? tiffStripOffsets) and `2` byte count(s) (see ? tiffStripByteCounts), which is inconsistent. \n\nTerminating import process. Bye Bye.";

ReadTIFFg::colourErr = "This TIFF file contains `1`However, this version of the Mathematica ReadTIFFg package can only handle grayscale images (TIFF Class G reader). The image will not be read-in.\n\nNote: I originally intended the pixel values of colour images to be read-in, even though they would be incorrectly interpreted. This caused a few programming problems and also seemed silly so, in the end, I decided to reject colour images completely. If you do need to load a colour image as grayscale there are several powerful image manipulation programs that can do this properly (e.g., xv on Unix).";

ReadTIFFg::badBitErr = "This version of the Mathematica ReadTIFFg function (v1.x) can handle bilevel images (Class B), which have 1 bit per pixel, and TIFF grayscale images (Class G), with 4 or 8 bits per pixel. However, this file has `1` bit(s) per pixel so the file cannot be read-in. \n\nTerminating import process. Bye Bye.";

ReadTIFFg::unknownResolutionUnit = "This TIFF file has a value for tiffResolutionUnit whose meaning is unknown. Known (valid) values are: \n\n1, for no absolute unit of measurement \n\n2, for resolution unit of inch \n\n3, for resolution unit of centimeter \n\nWill set tiffResolutionUnit to 1.  See also ? tiffXResolution, ? tiffYResolution.";

ReadTIFFg::StripByteCountsErr = "This TIFF file's IFD did not contain a StripByteCounts field. This is strictly a format violation for TIFF Class X files; however, in simple cases the StripByteCounts parameter may be calculated from other field values. I will try to use the calculated value but may crash.\n\nWish me luck !";

definition of local (static) variables

typeCodes = {1->BYTE, 2->ASCII, 3->SHORT, 4->LONG, 5->RATIONAL};

The following 'table' lists the IFD tags that MUST all be present in a baseline TIFF grayscale files (TIFF class G).  The format is:
   1.  The standard parameter name,
   2.  The tag number,
   3.  List of valid types,
   4.  List of valid values, (* note: these are no longer used in the code *)
   5.  Default value which is used if entry not present in IFD
       - set to Null if no default

baselineTIFFparameters = 
{
   { tiffImageWidth,                256, {SHORT,LONG}, Null, Null },
   { tiffImageLength,               257, {SHORT,LONG}, Null, Null },
   { tiffBitsPerSample,             258, {SHORT}, {1,4,8}, 1 },
   { tiffCompression,               259, {SHORT}, {1,32773}, 1 },
   { tiffPhotometricInterpretation, 262, {SHORT}, {0,1,2,3,4}, Null },
   { tiffStripOffsets,              273, {SHORT,LONG}, Null, Null },
   { tiffRowsPerStrip,              278, {SHORT,LONG}, Null, (2^32)-1 },
   { tiffStripByteCounts,           279, {SHORT,LONG}, Null, -1 },
   { tiffXResolution,               282, {RATIONAL}, Null, 0 },
   { tiffYResolution,               283, {RATIONAL}, Null, 0 },
   { tiffResolutionUnit,            296, {SHORT, LONG}, {1,2,3}, 2 } 
};

The following 'table' lists non-baseline but known IFD tags that may be present in a TIFF G file.  The format is:
   1.  The standard parameter name,
   2.  The tag number,
   3.  List of valid types,
   4.  List of valid values, (* note: these are no longer used in the code *)
   5.  Default value which is used if entry not present in IFD
       - set to Null if no default

Don't forget to include a usage message if you add to the following table

nonbaselineTIFFparameters = 
{
   { tiffArtist,                    315, {ASCII}, Null, Null },
   { tiffCopyright,               33432, {ASCII}, Null, Null },
   { tiffHostComputer,              316, {ASCII}, Null, Null },
   { tiffImageDescription,          270, {ASCII}, Null, Null },
   { tiffMake,                      271, {ASCII}, Null, Null },
   { tiffModel,                     272, {ASCII}, Null, Null },
   { tiffMaxSampleValue,            281, {SHORT}, Null, Null },
   { tiffMinSampleValue,            280, {SHORT}, Null, 0 },
   { tiffNewSubfileType,            254, {LONG},  Null, 0 },
   { tiffOrientation,               274, {SHORT}, Range[8], 1 },
   { tiffSoftware,                  305, {ASCII}, Null, Null },
   { tiffDateTime,                  306, {ASCII}, Null, Null },
   { tiffDocumentName,              269, {ASCII}, Null, Null },
   { tiffPageName,                  285, {ASCII}, Null, Null },
   { tiffSamplesPerPixel,           277, {SHORT}, Null, 1 } 
};

definition of auxiliary functions

Define Function : stringDropBeforeLast and stripContext

stringDropBeforeLast[theString_String,chars_String]:=
Module[
   {positionsOfChars = Flatten[StringPosition[theString,chars]]},
   (*Print["theString = ",theString];
   Print["chars = ",chars];*)
   If[
      positionsOfChars == {},
   (* then doesn't occur, so return all of theString *)
      theString,
   (* else it does occur so drop before *)
					 StringDrop[
					    theString,
					    Last[positionsOfChars]
					 ]
			]
];
 
stringDropBeforeLast[theObject_,chars_String]:=
 stringDropBeforeLast[ToString[theObject],chars];

stripContext[listOfObjects_List]:= 
 stripContext[#]& /@ listOfObjects;

stripContext[theObject_]:= stringDropBeforeLast[theObject,"`"];

Define Function : readInTIFFheader

Clear[readInTIFFheader];

readInTIFFheader::byteOrderErr = "This file may not be a TIFF format image file.\n\nThe first two bytes of file should be II or MM, indicating the file's byte order. This file has bytes `1` so is invalid. \n\nTerminating import process. Bye Bye.";

readInTIFFheader::notTIFFerr = "This file may not be a TIFF format image file.\n\nBytes 2 and 3 of all TIFF files should be the number 42 (chosen because it's the answer to the ultimate question !). This file fails with the byte value `1` \n\nTerminating import process. Bye Bye.";

readInTIFFheader[stream_]:=
Module[
   {byteOrder, checkItsTIFF, offsetOfFirstIFD},
			byteOrder = ReadListBinary[stream,Byte,2];
			(* Print["byteOrder = ", byteOrder,"\n\n"]; *)
   Which[
      byteOrder == {73,73},
   (* then *)
      theByteOrder = LeastSignificantByteFirst,
   (* else if *)
      byteOrder == {77,77},
   (* then *)
      theByteOrder = MostSignificantByteFirst,
   (* else default to *)
      True,
      Message[
         readInTIFFheader::byteOrderErr,
         FromCharacterCode[byteOrder]
      ];
      Throw[1]
   ];
   (* note that theByteOrder is not local to ReadTIFFg, but is local to 
      the package variable *)
   
   checkItsTIFF = 
	   ReadBinary[
	      stream,
	      Int16,
	      ByteOrder->theByteOrder
	   ];
			(* Print["checkItsTIFF = ", checkItsTIFF,"\n\n"]; *)
   If[
      checkItsTIFF != 42,
   (* then this fails the validation test so exit*)
      Message[
         readInTIFFheader::notTIFFerr,
         checkItsTIFF
      ];
      Throw[2]
   ];
			   
			offsetOfFirstIFD = 
			 ReadBinary[
			    stream,
			    Int32,
			    ByteOrder->theByteOrder
				];
			(* Print["offsetOfFirstIFD = ", offsetOfFirstIFD,"\n\n"];*)
			{theByteOrder, offsetOfFirstIFD}
]

Define Function : readInImageFileDirectory

Clear[readInImageFileDirectory];

readInImageFileDirectory::canOnlyReadOneSubfile = "This TIFF file contains more than one image. However, only the first of these can be read-in by this version (v1.x) of the Mathematica ReadTIFFg function. \n\nReading first image but skipping rest";

readInImageFileDirectory[stream_]:=
Module[
   {noOfFields, theTag, theType, valueCount, valueOffset, theIFD={}, 
    nextIFD},
   (* Print["theByteOrder = ",theByteOrder]; *)
   noOfFields = 
	   ReadBinary[
	      stream,
	      Int16,
	      ByteOrder->theByteOrder
	   ];
   (* Print["noOfFields = ", noOfFields,"\n\n"];*)
	   
   Do[

			   theTag = 
				   ReadBinary[
				      stream,
				      Int16,
				      ByteOrder->theByteOrder
				   ];
				  (* SetStreamPosition[stream,StreamPosition[stream]-2];
				  rawBytes = 
				   ReadListBinary[
				      stream,
				      Byte,
				      2
				   ];
			   Print["theTag = ", theTag," : raw bytes were ",rawBytes];*)
			   
			   theType = 
				   ReadBinary[
				      stream,
				      Int16,
				      ByteOrder->theByteOrder
				   ];
				  (* SetStreamPosition[stream,StreamPosition[stream]-2];
				  rawBytes = 
				   ReadListBinary[
				      stream,
				      Byte,
				      2
				   ];
			   Print["theType = ", theType," : raw bytes were ",rawBytes];*)
			   
			   valueCount = 
				   ReadBinary[
				      stream,
				      Int32,
				      ByteOrder->theByteOrder
				   ];
				  (* SetStreamPosition[stream,StreamPosition[stream]-4];
				  rawBytes = 
				   ReadListBinary[
				      stream,
				      Byte,
				      4
				   ];
			   Print["valueCount = ", valueCount," : raw bytes were ",rawBytes];*)
			   
			   valueOffset = 
				   ReadBinary[
				      stream,
				      Int32,
				      ByteOrder->theByteOrder
				   ];
				  (* SetStreamPosition[stream,StreamPosition[stream]-4];
				  rawBytes = 
				   ReadListBinary[
				      stream,
				      Byte,
				      4
				   ];
			   Print["valueOffset = ", valueOffset," : raw bytes were ",rawBytes,"\n\n"];*)
			   
			   AppendTo[theIFD,{theTag,theType,valueCount,valueOffset}],
			   {noOfFields}
						];

   nextIFD = 
	   ReadBinary[
	      stream,
	      Int32,
	      ByteOrder->theByteOrder
	   ];
   (* Print["nextIFD = ", nextIFD];*)
   If[
      nextIFD != 0,
      Message[readInImageFileDirectory::canOnlyReadOneSubfile]
   ];
			   
			(* Print["IFD = ", theIFD];
			Print[""];*)
			theIFD
]

Define Function : selectTags

selectTags[theItems_List,fromParameterList_List]:=
Module[
   {targetTags = Transpose[fromParameterList][[2]] },
   Select[
      theItems,
      MemberQ[ targetTags, #[[1]]  ]&
   ]
]

Define Function : setMissingParamsToDefault

setMissingParamsToDefault::missingAndNoDefault = "
This TIFF file has an invalid format; it does not contain an IFD entry for the field(s) `1` \n\nThis field does not have a default value so MUST be present. \n\nTerminating import process. Bye Bye.";

setMissingParamsToDefault[itemsInIFD_List,requiredParameterList_List]:=
Module[
   {tagsPresent, missingParams, missingParamsSansDefault={}},
   tagsPresent = Transpose[itemsInIFD][[1]];
   missingParams =
	   Select[
	      requiredParameterList,
	      Not[ MemberQ[ tagsPresent, #[[2]]  ]] &
	   ];
	  (* Print["missingParams = ", missingParams];*)
	  missingParamsSansDefault = 
	   Select[
	      missingParams,
	      #[[5]] == Null &
	   ];
	  (* Print["missingParamsSansDefault = ", missingParamsSansDefault];*)
	  If[
	     missingParamsSansDefault != {},
	     Message[
	        setMissingParamsToDefault::missingAndNoDefault,
	        Transpose[missingParamsSansDefault][[1]]
	     ];
	     Throw[3]
	  ];
	  
	  missingParams = Transpose[missingParams];
	  (* Print["missingParams = ", missingParams]; *)
	  
	  MapThread[
	     Set,
	     {First[missingParams],Last[missingParams]}
	  ];
];

Define Function : passOnlyValidTypes

Clear[passOnlyValidTypes];

passOnlyValidTypes::wrongType = "The file's IDF field `1` (tag = `2`) has an entry of type `3`, which is invalid. Valid types are `4`\n\nWill attempt to continue loading...\n\n";

passOnlyValidTypes[itemsInIFD_List,parameterList_List]:=
Module[
   {itemsWithValidType, itemsWithInvalidType},
   (* Print["itemsInIFD = ",itemsInIFD,"\n\n"]; *)
   (* Print["parameterList = ",parameterList,"\n\n"]; *)
   itemsWithValidType = 
	   Select[
	      itemsInIFD,
	      MemberQ[
				      parameterList[[
				         (First[First[Position[parameterList,#[[1]]]]]),
				         3
				      ]],
				      #[[2]] /. typeCodes
	      ] &
	   ];
	  itemsWithInvalidType = Complement[itemsInIFD,itemsWithValidType];
   (* Print["itemsWithValidType = ",itemsWithValidType,"\n\n"];
   Print["itemsWithInvalidType = ",itemsWithInvalidType,"\n\n"]; *)
   If[
      Length[itemsWithInvalidType] != 0,
   (* then report which were in error*)
      parametersInError = 
	      getParameterInfoForIFDentry[
	         #,
	         parameterList
	      ]& /@ itemsWithInvalidType;
      (* Print["parametersInError  = ",parametersInError ,"\n\n"];*)
	     MapThread[
				     Message[
				        passOnlyValidTypes::wrongType,
				        stripContext[ #2[[1]] ],
				        #2[[2]],
				        stripContext[ #1[[2]] /. typeCodes ],
					       stripContext[ #2[[3]] ]
					     ]&,
				     {
				      itemsWithInvalidType,
				      parametersInError
				     }
	     ];
		 ];
		 (* Now return the valid items *)
		 itemsWithValidType
]

Define Function : getParameterInfoForIFDentry

getParameterInfoForIFDentry[theIFDEntry_List,parameterList_List]:=
	parameterList[[
	      Position[
		     Transpose[parameterList][[2]],
		     theIFDEntry[[1]]
	   ][[1,1]]
	]]

Define Function: valueFitsInFourBytesQ

valueFitsInFourBytesQ is a function that returns true iff the directory entry value fits into four bytes

valueFitsInFourBytesQ[theType_,valueCount_]:=
Module[
   {unitLength},
   unitLength =
   Which[
      theType == 1, (* BYTE, 8-bit byte unsigned integer *)
      1,
      theType == 2, (* ASCII, 8-bit byte containing 7-bit ascii code &
                       zero in last bit *)
      1,
      theType == 3, (* SHORT, 16-bit 2-byte unsigned integer *)
      2,
      theType == 4, (* LONG , 36-bit 4-byte unsigned integer *)
      4,
      theType == 5, (* RATIONAL , Two LONGs: first is numerator and
                       second is denominator *)
      8
   ];
   (valueCount * unitLength) < 5
];

Define Function : getItemFromFile

getItemFromFile[theType_,valueCount_,valueOffset_,stream_]:=
Module[
   {theValuesList},
   SetStreamPosition[stream,valueOffset];
   theValuesList=
    Which[
	      theType == 1, (* BYTE *)
	      ReadListBinary[
	         stream,
	         Byte,
	         valueCount,
	         ByteOrder->theByteOrder
	      ],
	
	      theType == 2, (* ASCII *)
			    {StringJoin[ 
			       Map[
			          FromCharacterCode[#] &,
			          Drop[
			             ReadListBinary[
			                stream,
			                Byte,
			                valueCount,
			                ByteOrder->theByteOrder
			             ],
			             -1
			          ]
			       ]
			    ]},
	
	      theType == 3, (* SHORT *)
	      ReadListBinary[
	         stream,
	         Int16,
	         valueCount,
	         ByteOrder->theByteOrder
	      ],
	
	      theType == 4, (* LONG *)
	      ReadListBinary[
	         stream,
	         Int32,
	         valueCount,
	         ByteOrder->theByteOrder
	      ],
	      
	      theType == 5, (* RATIONAL *)
	      Module[
	         {numbers},
	         numbers = 
		         ReadBinary[
									     stream,
									     Int32,
									     ByteOrder->theByteOrder
									  ]& /@ Range[2*valueCount];
									  
				      Divide @@ # & /@ Partition[numbers,2]
					   ]
	    ];
	   (* Print["theValuesList = ",theValuesList];*)
(* the above items are all lists. Strip off the enclosing brackets if there is only one element in the list *)
	   If[
	      Length[theValuesList] == 1,
	   (* then *)
	      theValuesList[[1]],
	   (* else *)
	      theValuesList
	   ]
	     
];

Define Function : getItemFromValueOffset

getItemFromValueOffset[theTag_,theType_,valueCount_,valueOffset_]:=
Module[
   {tempFilename, tempStream, theValue},
			(* Print["Getting value from valueOffset"];
			Print["theTag = ",theTag];
			Print["theType = ",theType];
			Print["valueCount = ",valueCount];
			Print["valueOffset = ",valueOffset];*)
			tempFilename = Close[OpenTemporary[]];
			(* Print["file = ",tempFilename];*)
			tempStream = OpenWriteBinary[tempFilename];
			WriteBinary[
			   tempStream,
			   valueOffset,
      IntegerConvert -> Int32,
			   ByteOrder -> theByteOrder
			];
			Close[tempStream];

			tempStream = OpenReadBinary[tempFilename];

   theValue = getItemFromFile[theType,valueCount,0,tempStream];
			Close[tempStream];
			DeleteFile[tempFilename];
			
			(* Print["value = ",theValue,"\n\n"];*)
			
   theValue
];

Define Function: getValue

getValue[{theTag_,theType_,valueCount_,valueOffset_},stream_]:=
	If[ 
	   valueFitsInFourBytesQ[theType,valueCount],
	 (* then *)
	   getItemFromValueOffset[theTag,theType,valueCount,valueOffset],
	 (* else *)
	   getItemFromFile[theType,valueCount,valueOffset,stream],
	]

Define Function : valuesOfIFDsValidQ

valuesOfIFDsValidQ::badValue = "The value of this file's IDF parameter `1` (tag = `2`) is `3`.  Only the following are valid for TIFF Class G files : `4`. \n\n";

valuesOfIFDsValidQ[theIFDsWithValues_List]:=
Module[
   {restrictedValueItems, invalidRestrictedValueItems},
   restrictedValueItems = 
	   Select[
	      theIFDsWithValues,
	      Not[#[[4]]===Null] &
	   ];
   invalidRestrictedValueItems = 
	   Select[
	      restrictedValueItems,
	      Not[MemberQ[
	         #[[4]],
	         #[[6]]
	      ]] &
	   ];
   (* Print["invalidRestrictedValueItems = ",invalidRestrictedValueItems];*)
  
   If[
      Length[invalidRestrictedValueItems]==0,
   (* then no items have obviously invalid values so return True *)
      True,
   (* else some values are invalid, so report them and return False *)
      Message[
         valuesOfIFDsValidQ::badValue,
         stripContext[ #[[1]] ],
         #[[2]],
         #[[6]],
         #[[4]]
      ] & /@ invalidRestrictedValueItems;
      False
   ]
]

Define Function : setTIFFparameterValues

setTIFFparameterValues[theIFDsWithValues_List]:=
Module[
   {itemInfo},
   itemInfo = Transpose[theIFDsWithValues];
   (* Print["itemInfo = ",itemInfo];*)
	  MapThread[
	     Set,
	     {First[itemInfo],Last[itemInfo]}
	  ]
]

Define Function : reportTIFFparameterValues

reportTIFFparameterValues[theIFDsWithValues_List]:=
 Print[
    "Tiff parameter ",
    stripContext[ #[[1]] ],
    " (tag = ",
    #[[2]],
    ") is set to ",
    #[[6]]
 ] & /@ theIFDsWithValues

Define Function : getStrip

getStrip[
   theStream_, 
   offset_Integer, 
   bytes_Integer, 
   imageWidth_Integer, 
   bitsPerSample_Integer
]:=
Module[
   {rawStrip},
   (* Print["offset = ",offset];
   Print["bytes to read = ",bytes]; *)
   SetStreamPosition[theStream,offset];
   rawStrip = ReadListBinary[theStream,Byte,bytes];
   
   Which[
      bitsPerSample == 1,
   (* then each byte is 8 bilevel pixels, so break up each byte into bits *)
						Map[
						   Take[#,imageWidth] &,
						   Partition[
						      Flatten[ IntegerDigits[#,2,8]& /@ rawStrip ],
						      8*(1+Quotient[imageWidth - 0.5, 8])
						   ]
						],
			(* else if *)
      bitsPerSample == 4,
   (* then each byte is 2 16-level grayscale pixels, so break up each byte 
      into two nibbles *)
						Map[
						   Take[#,imageWidth] &,
						   Partition[
						      Flatten[ IntegerDigits[#,16,2]& /@ rawStrip ],
						      2*(1+Quotient[imageWidth - 0.5, 2])
						   ]
						],
			(* else if *)
      bitsPerSample == 8,
   (* then each byte is a single grayscale pixel *) 
				   Partition[
				      rawStrip,
				      imageWidth
				   ]
   ]
]

definition of exported functions : ReadTIFFg

ReadTIFFg[filename_String,opts___Rule]:=
Module[
   {
      verboseReport,
      ignoreOther,
      stream,
      failure,
      theByteOrder,
      offsetOfFirstIFD,
      theIFD, baselineIFDs, otherIFDs, knownIFDs, unknownIFDs, validKnownIFDs,
      theIFDsWithValues, rawData
   },
   
   verboseReport = report /. {opts} /. Options[ReadTIFFg];
   ignoreOther = ignoreNonBaseline /. {opts} /. Options[ReadTIFFg];
   (*Print["verboseReport = ",verboseReport];
   Print["ignoreOther = ",ignoreOther];*)

   Clear["ReadTIFFpackage`tiff*"];
   Clear["ReadTIFFpackage`private`tiff*"];

	  If[
	     Not[FileType[filename] === File],
	  (* then file does not exist so Abort *)
	     Message[ReadTIFFg::filenameErr, filename];
	     Abort[]
	  ];

   stream = OpenReadBinary[filename];
   failure = Catch[
   
      {theByteOrder, offsetOfFirstIFD} = readInTIFFheader[stream];
			   (* Print["theByteOrder = ", theByteOrder, "\n\n"];*)

      SetStreamPosition[stream,offsetOfFirstIFD]; (* jump to IFD *)
      theIFD = readInImageFileDirectory[stream];
			   (* Print["IFD = ", theIFD, "\n\n"]; *)

			   baselineIFDs = selectTags[theIFD,baselineTIFFparameters];
		    (* Print["baselineIFDs = ", baselineIFDs, "\n\n"];*)
						otherIFDs = Complement[theIFD, baselineIFDs];
		    (* Print["otherIFDs = ", otherIFDs, "\n\n"]; *)
		    knownIFDs = selectTags[otherIFDs,nonbaselineTIFFparameters];
		    (* Print["knownIFDs = ", knownIFDs, "\n\n"]; *)
      unknownIFDs = Complement[otherIFDs,knownIFDs];
		    (* Print["unknownIFDs = ", unknownIFDs, "\n\n"]; *)
      validKnownIFDs = 
	      passOnlyValidTypes[
							   knownIFDs,
										nonbaselineTIFFparameters
							];
		    (* Print["validKnownIFDs = ", validKnownIFDs, "\n\n"]; *)
						
					(* Work on the baseline TIFF parameters. i.e., 
         Handle defaults, 
         check types are valid, 
         get values, 
         check values are valid,
         set parameters 
      *)
      
		    setMissingParamsToDefault[
		       baselineIFDs,
		       baselineTIFFparameters
		    ];
		    
		    If[
					    passOnlyValidTypes[baselineIFDs, baselineTIFFparameters] != 
					      baselineIFDs,
								 Message[ReadTIFFg::typeErr];
								 Throw[5]
					 ];
					 
					 theIFDsWithValues = 
						 MapThread[
						    Append,
						    {
							      getParameterInfoForIFDentry[
						          #,
						          baselineTIFFparameters
						       ] & /@ baselineIFDs,
						       getValue[#,stream] & /@ baselineIFDs
							    }
						 ];
					 
					(* Print["theIFDsWithValues = ", theIFDsWithValues, "\n\n"];*)

					 If[
					    verboseReport,
					    Print["Essential IFD fields\n--------------------"];
					    reportTIFFparameterValues[theIFDsWithValues];
					 ];
					 
					 setTIFFparameterValues[theIFDsWithValues];

					 tiffStripsPerImage = 
					  Floor[(tiffImageLength+tiffRowsPerStrip-1)/tiffRowsPerStrip];

					 If[
					    verboseReport,
					    Print["tiffStripsPerImage = ",tiffStripsPerImage];
					 ];
					 
					 If[
					    tiffStripByteCounts == -1,
					 (* then the IFD did not contain a StripByteCounts field. This is strictly 
					    invalid but the parameter can actually be calculated in simple cases. 
					    Report then try to calculate *)
					    Message[ReadTIFFg::StripByteCountsErr];
					    tiffStripByteCounts = 
					     Table[
					        Ceiling[tiffImageLength/tiffStripsPerImage]         (* no rows/strip *)
					        * Ceiling[tiffImageWidth * tiffBitsPerSample / 8],  (* no bytes / row *)
					        {tiffStripsPerImage}
					     ];
					 ];
					 
					 (* Print["tiffStripByteCounts = ",tiffStripByteCounts];
					 Print["tiffStripOffsets = ",tiffStripOffsets]; *)

					 If[
					    tiffCompression != 1,
					 (* then image is compressed, so report and Throw *)
					    Message[ReadTIFFg::compressedErr];
					    Throw[7]
					 ];
					 
					 If[
					    (Length[Flatten[{tiffStripOffsets}]] != tiffStripsPerImage) || 
					     (Length[Flatten[{tiffStripByteCounts}]] != tiffStripsPerImage),
					 (* then a format error, report and throw *)
					    Message[
					       ReadTIFFg::stripErr,
					       Length[Flatten[{tiffStripOffsets}]],
					       Length[Flatten[{tiffStripByteCounts}]],
					       tiffStripsPerImage
					    ];
					    Throw[8]
					 ];
					 
					 If[
					    tiffPhotometricInterpretation > 1,
					 (* this is a colour image, report then Throw *)
					     Message[
					        ReadTIFFg::colourErr,
					        Which[
					           tiffPhotometricInterpretation == 0,
					        (* then a *)
					           "a grayscale image in WhiteIsZero format.",
					        (* if *)
					           tiffPhotometricInterpretation == 1,
					        (* then a *)
					           "a grayscale image in BlackIsZero format.",
					        (* if *)
					           tiffPhotometricInterpretation == 2,
					        (* then a *)
					           "an RGB full colour image.",
					        (* if *)
					           tiffPhotometricInterpretation == 3,
					        (* then a *)
					           "a pallete-colour image.",
					        (* default *)
					           True,
					           "an unknown format image."
					        ]
					     ];
					     Throw[9]
					  ];

					 If[
					    Not[ MemberQ[{1,4,8},tiffBitsPerSample] ],
					 (* then image is not 1,4 or 8-bit, so report and Throw *)
					    Message[ReadTIFFg::badBitErr,tiffBitsPerSample];
					    Throw[10]
					 ];
					 
					 If[
					    Not[ MemberQ[{1,2,3},tiffResolutionUnit] ],
					 (* then tiffResolutionUnit is not 1,2 or 3, so report and reset *)
					    Message[ReadTIFFg::unknownResolutionUnit,tiffResolutionUnit];
					    tiffResolutionUnit = 1
					 ];
					 
     (* Note: checking values by valuesOfIFDsValidQ[] was here *)
     (* This checking has now been commented out. All checking of 
     important parameters must now be done manually, in individual
     lines of code *)
	    (*If[
					    Not[valuesOfIFDsValidQ[theIFDsWithValues]],
								 Message[ReadTIFFg::valueErr];
								 Throw[6]
					 ];*)
				  
      (* If user wants non-required fields processed then 
      work on other, non-essential TIFF parameters. i.e.,
         report unknown IFD items, 
         filter out known items with invalid types,
         get values of known items, 
         check values are valid,
         report values 
     *)
      If[
         Not[ignoreOther],
         (* Print["Processed non-essential fields"];*)
			      Message[
			         ReadTIFFg::unknownTag,
			         #[[1]],
			         stripContext[ #[[2]] /. typeCodes ],
			         #[[3]],
			         #[[4]]
			      ]& /@ unknownIFDs;
			
								 theIFDsWithValues = 
									 MapThread[
									    Append,
									    {
										      getParameterInfoForIFDentry[
									          #,
									          nonbaselineTIFFparameters
									       ] & /@ validKnownIFDs,
									       getValue[#,stream] & /@ validKnownIFDs
										    }
									 ];
									(* Print["theIFDsWithValues = ", theIFDsWithValues, "\n\n"];*)
								 If[
								    verboseReport,
					       Print["\n\nOptional IFD fields\n-------------------"]
												reportTIFFparameterValues[theIFDsWithValues];
								 ];
					    setTIFFparameterValues[theIFDsWithValues];
								 If[
								    tiffOrientation != 1,
								  (* then report that not handled by this version of ReadTIFFg *)
								     Message[ReadTIFFg::orientationErr,tiffOrientation]
								  ];
						];
				  
				 (* Now you have all the parameters - get the image *)
				  
						rawData = 
							Reverse[Flatten[
					  			MapThread[
											  getStrip[
											     stream,
											     #1,
											     #2,
											     tiffImageWidth,
											     tiffBitsPerSample
											  ]&,
											  {
											    Flatten[{tiffStripOffsets}],
											    Flatten[{tiffStripByteCounts}]
											  }
										],
										1
							]];
						
						If[
						   tiffPhotometricInterpretation == 0,
						(* WhiteIsZero - which is reverse of MMA default so *)
						   rawData = (2^tiffBitsPerSample - 1) - rawData
						];
						
			];
   Close[stream];

   If[
      NumberQ[failure],
   (* then *)
      (* Print["Error type ", failure," occured."];*)
      Null,
   (* else *)
      rawData
   ]
];

rules for system functions

(* Sin/: Sin[x_]^2 := 1 - Cos[x]^2 *)

restore protection of system symbols

(*Protect[ Evaluate[protected] ]  *)

end the private context

End[]

protect exported symbols

Protect[ ReadTIFFg ]

end the package context

EndPackage[]

report that package loaded

Print["ReadTIFFpackage.m version 1.1 loaded. Enjoy !"]