#! /usr/bin/env perl
#
# Andrew Janke - rotor@bic.mni.mcgill.ca
# McConnell Brain Imaging Center
# Montreal Neurological Institute
#
# MINC is a free file format from the MNI: www.bic.mni.mcgill.ca
# MIF4 is a file format of a lab from UBC - www.medicine.ubc.ca
#  The specs for MIF4 were kindly provided by Andrew Riddehough
#
# NB: this is not a complete translation only fields that seemed
#     pertinent at the time of writing this are used...
#
# Mon Jan  7 14:18:37 EST 2002 - initial version
# Wed Feb 13 16:22:05 EST 2002 - Added byte_swapping aka ana2mnc 

require 5.0;

use strict;
use warnings "all";

use Getopt::Tabular;
$SIG{__DIE__} =  sub { &cleanup; die $_[0]; };

my(%Unpack_codes) = ('a', 1, 'A', 1, 
                     's', 2, 'S', 2,
                     'i', 4, 'I', 4,
                     'f', 4, 'd', 8,
                     'l', 4, 'L', 4,
                     );

my(%dtypes) = (
              2 => ['-short', '-unsigned'],
              4 => ['-int',   '-unsigned'],
              8 => ['-float'],
             16 => ['-double'],
              );

chomp(my($me) = `basename $0`);
my($tmpfile) = "/tmp/$me-$$.raw";
my($clobber) = 0;
my($verbose) = 0;
my(@opt_table) = (
              ["-verbose", "boolean", 0, \$verbose, "Be Verbose"],
              ["-clobber", "boolean", 0, \$clobber, "clobber existing files"],
              );

my($Help) = <<HELP;
This script can be invoked as:
   mif2mnc     Convert a MIF4 file to a MINC
HELP

my($Usage) = "Usage: $me [options] <in.MIF> <out.mnc>\n".
             "       $me -help to list options\n\n";
             
my($miffile, $mncfile, $files, $nfiles, @args, $history);

# create the history string
chomp($history = `date`);
$history .= '>>>> ' . join(' ', $me, @ARGV) . "\n";

# Check arguments
&Getopt::Tabular::SetHelp ($Help, $Usage);
&GetOptions (\@opt_table, \@ARGV) || exit 1;
if ($#ARGV < 1){ die $Usage; }
($miffile, $mncfile) = @ARGV[0..1];
   
if(!-e "$miffile"){ 
   die "$me: Couldn't find $miffile\n"; 
   }
if(-e $mncfile && !$clobber){ 
   die "$me: $mncfile exists! use -clobber to overwrite\n"; 
   }

# Read in the file header
open(HDR, $miffile) or die "$me: Error opening file: $miffile\n";
read(HDR, my($hdr), 512);

my %h;

# check for byte-swapping
my($bs) = 0;
if(destruct(\$hdr,  44,'s', 0) != 4){
   if($verbose){ print "Hrm, let's try byte swapping\n"; }
   $bs = 1;
   }

$h{MIF_ID}        = destruct(\$hdr,   0,'a20', $bs);
$h{MIF_Version}   = destruct(\$hdr,  44,'s',   $bs);
$h{MIF_SubVersion}= destruct(\$hdr,  46,'s',   $bs);
$h{MIF_Name}      = destruct(\$hdr,  48,'a80', $bs);

$h{FileDateTime}  = destruct(\$hdr, 132,'l',   $bs);
$h{ConvExeTime}   = destruct(\$hdr, 140,'l',   $bs);
$h{SiteDataSet}   = destruct(\$hdr, 146,'a32', $bs);

$h{Pat_Number}    = destruct(\$hdr, 192,'a25', $bs);
$h{Pat_Name}      = destruct(\$hdr, 217,'a25', $bs);
$h{Pat_Initials}  = destruct(\$hdr, 242,'a8',  $bs);
$h{Pat_DOB}       = destruct(\$hdr, 250,'a12', $bs);
$h{Pat_Gender}    = destruct(\$hdr, 262,'a1',  $bs);
$h{Hosp_Name}     = destruct(\$hdr, 263,'a33', $bs);

$h{SeriesDateTime}= destruct(\$hdr, 300,'l',   $bs);
$h{Scan_ID}       = destruct(\$hdr, 304,'l',   $bs);


$h{Num_Images}    = destruct(\$hdr, 332,'s',   $bs);
$h{Num_Slices}    = destruct(\$hdr, 334,'s',   $bs);
$h{Num_Echoes}    = destruct(\$hdr, 336,'s',   $bs);
$h{Image_Dim_x}   = destruct(\$hdr, 338,'s',   $bs);
$h{Image_Dim_y}   = destruct(\$hdr, 340,'s',   $bs);
$h{Bytes_per_Pix} = destruct(\$hdr, 342,'s',   $bs);
$h{Def_Slice_Gap} = destruct(\$hdr, 344,'l',   $bs);

# orientation info
$h{Orientation}   = destruct(\$hdr, 356,'a1',  $bs);
$h{Contrast_Flag} = destruct(\$hdr, 369,'s',   $bs);
$h{Mag_Strength}  = destruct(\$hdr, 438,'s',   $bs);

$h{Translation_x} = destruct(\$hdr, 484,'l',   $bs);
$h{Translation_y} = destruct(\$hdr, 488,'l',   $bs);
$h{Translation_z} = destruct(\$hdr, 492,'l',   $bs);
$h{Rotation_x}    = destruct(\$hdr, 496,'f',   $bs);
$h{Rotation_y}    = destruct(\$hdr, 500,'f',   $bs);
$h{Rotation_z}    = destruct(\$hdr, 504,'f',   $bs);

# munge a few values
$h{FileDateTime}    = localtime($h{FileDateTime});
$h{ConvExeTime}     = localtime($h{ConvExeTime});
$h{SeriesDateTime}  = localtime($h{SeriesDateTime});
$h{Def_Slice_Gap}  /= 1000;
$h{Mag_Strength}   /= 10000;

# print a few things out
if($verbose){
   foreach (sort(keys(%h))){
      print STDOUT "$_:\t$h{$_}\n";
      }
   }


# create the temporary file for rawtominc to read in
open(TMP, ">$tmpfile");

my($c, $position, $pix_size_x, $pix_size_y, $size_x, $size_y, $type, $thickness, $data); 

for($c=0; $c<$h{Num_Slices}; $c++){
   read(HDR, my($hdr), 256);
   
   $position      = destruct(\$hdr,  109,'f',  $bs);
   $pix_size_x    = destruct(\$hdr,  113,'l',  $bs) / 1000;
   $pix_size_y    = destruct(\$hdr,  113,'l',  $bs) / 1000;
   
   $size_x        = destruct(\$hdr,  121,'s',  $bs);
   $size_y        = destruct(\$hdr,  123,'s',  $bs);
   $type          = destruct(\$hdr,  125,'s',  $bs);
   
   $thickness     = destruct(\$hdr,  127,'l',  $bs) / 1000;
   
   if($verbose){
      print STDOUT "Slice $c pos: $position $thickness $size_x:$size_y $pix_size_x:$pix_size_y\n";
      }
   
   read(HDR, $data, $size_x*$size_y*$type);
   syswrite(TMP, $data, $size_x*$size_y*$type);
   }
close(HDR);
close(TMP);


# byte swapping time
@args = ('dd', "if=$tmpfile", "of=$tmpfile.swp", 'conv=swab');
if ($verbose){ print STDOUT "@args\n"; }
system(@args) == 0 or die "Died during byte swapping\n";

# Check the data type
if (!defined($dtypes{$h{Bytes_per_Pix}})){ 
   die "Unknown data type: $h{Bytes_per_Pix}\n"; 
   }


# Set up rawtominc command
@args = ('rawtominc');
if($clobber){
   push(@args, "-clobber");
   }
push(@args, @{$dtypes{$h{Bytes_per_Pix}}});

# Get step info
if (($h{Orientation} eq "O") || 
    ($h{Orientation} eq "T")){   # Transverse
   push(@args, '-transverse');
   }
elsif ($h{Orientation} eq "C"){  # Coronal
   push(@args, '-coronal');
   }
elsif ($h{Orientation} eq "S"){  # Sagittal
   push(@args, '-sagittal');
   }
else{                            # Unknown
   warn "Unknown data orientation: assuming transverse\n";
   push(@args, '-transverse');
   }

# range
push(@args, '-scan_range', '-range', 0, 4095);

# figger out starts and origins
$pix_size_y *= -1;

push(@args, '-xstep', $pix_size_x,
            '-ystep', $pix_size_y,
            '-zstep', $thickness+$h{Def_Slice_Gap},
            '-xstart', -$pix_size_x*$h{Image_Dim_x}/2,
            '-ystart', -$pix_size_y*$h{Image_Dim_y}/2,
            '-zstart', -$thickness*$h{Num_Slices}/2,
            
            '-sattribute', ":history=$history",
            '-input', "$tmpfile.swp", $mncfile,
            "$h{Num_Slices}", "$h{Image_Dim_x}", "$h{Image_Dim_y}");

if ($verbose){ print STDOUT join(" ", @args) . "\n"; }
system(@args) == 0 or die "Died during rawtominc system command\n";

# insert all the MIF stuff in the header
undef(@args);
push(@args, "minc_modify_header");
foreach (sort(keys(%h))){
   push(@args, ("-sinsert", "MIF4:$_=$h{$_}"));
   }
push(@args, $mncfile);

if ($verbose){ print STDOUT join(" ", @args) . "\n"; }
system(@args) == 0 or die "Died during minc_modify_header command\n";
&cleanup;


# Unpack a value from a string (passed by reference)
# A legendary bit of code nicked from dicom_to_minc 
sub destruct{
    my($stringref, $offset, $type, $bs) = @_;
    
    my($code, $number, $size, $tempstring, $iloop, $max);
    
    # Check for byte swapping
    if($bs){
       if($type !~ /^\s*([a-zA-Z])(\d+)?\s*$/){
          die "Unrecognized data type \"$type\" on little-endian machine.\n";
          }
       $code = $1;
       $number =(defined($2) ? $2 : 1);
       if(!defined($Unpack_codes{$code})){
          die "Unrecognized unpack code \"$code\" on little-endian machine.\n";
          }
       $size = $Unpack_codes{$code};
       $tempstring = substr($$stringref, $offset, $number * $size);
       if($size > 1){
          $max = $number * $size;
          for($iloop=0; $iloop < $max; $iloop+=$size){
             substr($tempstring, $iloop, $size) = 
                reverse(substr($tempstring, $iloop, $size));
             }
          }
       return unpack("$type", $tempstring);
       }
       
    # No byte swapping required
    else{
       return unpack("x$offset $type", $$stringref);
       }
   }

# cleanup before dying
sub cleanup {
   if ($verbose){ print STDOUT "Cleaning up ......\n"; }
   my @args = ('rm', '-r', '-f', "$tmpfile", "$tmpfile.swp");
   system @args;
   }



