#! /usr/local/bin/perl
#
# Script to read files from a GE signa 5.x DAT tape and convert to minc
# format
#

sub numeric_order { $a - $b;}

# Subroutine to clean up files and exit
sub cleanup_and_die {

    # Get message to print and exit status
    local($message,$status) = @_;
    if (!defined($status)) {$status = 0;}
    if (defined($message)) {
        print STDERR $message;
        if ($message !~ /\n$/) {print STDERR "\n";}
    }

    $SIG{'INT'}  = 'IGNORE';
    $SIG{'TERM'} = 'IGNORE';
    $SIG{'QUIT'} = 'IGNORE';
    # Check for temp files
    if (defined($tmpdir) && -e $tmpdir) {
        print STDERR "Cleaning up temporary files.\n";
        system "rm -rf $tmpdir";
    }

    exit($status);
}

# Subroutine to execute commands and check return status
sub execute {
    local($status);
    if ($print_execution_statements) {
        print join(' ',@_),"\n";
    }
    $status = system @_;
    if ($status != 0) {
        &cleanup_and_die("Error executing command \"".join(" ",@_)."\".\n",
                         $status);
    }
}

# Subroutine to remove a list of files
sub remove_file {
    unlink @_;
}

# Subroutine to create a temporary directory (global variable $tmpdir
# is created)
sub create_tmpdir {
    local($subdir);
    if (scalar(@_) >= 1) {
        $subdir = $_[0];
    }
    else {
        $subdir = $$;
    }
    if (defined($ENV{TMPDIR})) {
        $tmpdir = $ENV{TMPDIR};
    }
    else {
        $tmpdir = "/usr/tmp";
    }
    $tmpdir .= "/$subdir";
    mkdir($tmpdir,0777) || die "Unable to create directory $tmpdir: $!";
    $SIG{'INT'}  = 'cleanup_and_die';
    $SIG{'TERM'} = 'cleanup_and_die';
    $SIG{'QUIT'} = 'cleanup_and_die';
}

# Subroutine to read a file from tape
sub read_next_file {
    local($tapedrive, *input_list) = @_;
    if (!defined($tapedrive) && !defined(@input_list)) {
        $tapedrive = "/dev/tape";
    }

    # Constants
    $tape_block_size = 8192;
    $tape_sleep = 1;

    # Get next value from list if no tape drive
    if (length($tapedrive) == 0) {
        return shift(@input_list);
    }

    # Create file counting variable if it does not exist
    if (!defined($counter_for_read_next_file)) {
        $counter_for_read_next_file = 0;
    }

    # Create the filename
    local($filename) = "$tmpdir/datafile_".$counter_for_read_next_file;
    $counter_for_read_next_file++;

    # Sleep for a moment, then read from tape drive (don't ask me why,
    # it just works!)
    print STDERR "Retrieving file $filename from drive $tapedrive\n";
    select(undef, undef, undef, $tape_sleep);
    local($status) = system("dd if=$tapedrive of=$filename ".
                            "ibs=$tape_block_size >/dev/null 2>/dev/null");
    if (($status!=0) || -z $filename) {
        print STDERR "End of tape.\n";
        &remove_file($filename);
        return "";
    }
    else {
        return $filename;
    }

}

# Subroutine to read the ge DAT headers
sub read_gedat_headers {

    # Set constants for reading file
    local($suite_hdr_len) = 114;
    local($exam_hdr_len) = 1024;
    local($series_hdr_len) = 1020;
    local($image_hdr_len) = 1022;
    local($pixel_hdr_prefix_len) = 124;
    local($pixdatsize_len) = 4;

    # Check arguements
    if (scalar(@_) != 8) {
        &cleanup_and_die("Argument error in read_gedat_headers",1);
    }
    local($filename, *suite_hdr, *exam_hdr, *series_hdr, 
          *image_hdr, *pixel_hdr, *pixel_data_offset, *pixel_data_len)
        = @_;

    # Read in file headers
    if (!open(GEF, "<".$filename)) {
        warn "Can't open file $filename: $!";
        return 1;
    }
    return 1
        if (read(GEF, $suite_hdr, $suite_hdr_len) != $suite_hdr_len);
    return 1
        if (read(GEF, $exam_hdr, $exam_hdr_len) != $exam_hdr_len);
    return 1
        if (read(GEF, $series_hdr, $series_hdr_len) != $series_hdr_len);
    return 1
        if (read(GEF, $image_hdr, $image_hdr_len) != $image_hdr_len);
    return 1
        if (read(GEF, $pixel_hdr, $pixel_hdr_prefix_len) !=
            $pixel_hdr_prefix_len);

    # Check the image file magic number
    if (unpack("a4",$pixel_hdr) ne "IMGF") {
        warn "Bad image file magic number in \"$filename\"";
        return 1;
    }

    # Get the length of the pixel header
    local($pixel_hdr_len) = unpack("L", substr($pixel_hdr, 4, 4));

    # Calculate the offset to pixel data
    local($pixdatsize_offset) = $suite_hdr_len + $exam_hdr_len + 
        $series_hdr_len + $image_hdr_len + $pixel_hdr_len;
    $pixel_data_offset = $pixdatsize_offset + $pixdatsize_len;

    # Read in the length of the pixel data
    local($pixdatsize);
    seek(GEF, $pixdatsize_offset, 0);
    return 1
        if (read(GEF, $pixdatsize, $pixdatsize_len) != $pixdatsize_len);
    $pixel_data_len = unpack("L",$pixdatsize);

    # Close input file
    close(GEF);

    return 0;
}

# Subroutine to unpack a value from a string
sub unpack_value {
    local(*string, $offset, $type) = @_;
    if ($dump_unpack_value) {
        print "unpack_value: length, offset, type = ",length($string), 
        " $offset $type\n";
    }
    return unpack("x$offset $type", $string);
}

# Subroutine to create a minc file
sub create_mincfile {
    if (scalar(@_) != 6) {
        die "Argument error in create_mincfile";
    }
    local($mincfile, *file_list, *fileinfo, $firstslice, $lastslice, $echo) 
        = @_;

    # Get number of slices
    local($nslices) = $lastslice - $firstslice + 1;
    if ($nslices <= 0) {
        warn "No images to copy for minc file \"$mincfile\"";
        return;
    }

    # Check for existence of file - create a new name if it exists
    local($filebase);
    if ($mincfile =~ /^(.*)\.mnc$/) {
        $filebase = $1;
    }
    else {
        $filebase = $mincfile;
    }
    local($version) = 1;
    while (-e $mincfile) {
        $version++;
        $mincfile = $filebase."_version$version.mnc";
        print STDERR "Minc file already exists. Trying name \"$mincfile\".\n";
    }

    # Set up rawtominc command
    local($nrows, $ncols, $orientation, $pixel_size, $slicestep);
    local($xstep, $ystep, $zstep, $xstart, $ystart, $zstart);
    local($orient_flag);
    $pixel_size = $fileinfo{'depth'} / 8;
    if ($nslices > 1) {
        $slicestep = ($fileinfo{'sliceend'} - $fileinfo{'slicestart'}) /
            ($nslices - 1);
    }
    else {
        $slicestep = 1;
    }
    $nrows = $fileinfo{'height'};
    $ncols = $fileinfo{'width'};
    $xstep = $fileinfo{'colstep'};
    $ystep = $fileinfo{'rowstep'};
    $zstep = $slicestep;
    $orient_flag = $fileinfo{'orientation'};
    if ($orient_flag == 4) {    # Sagittal
        ($ystep, $zstep, $xstep) = 
            ($fileinfo{'colstep'}, $fileinfo{'rowstep'}, $slicestep);
        ($ystart, $zstart, $xstart) = (0, 0, $fileinfo{'slicestart'});
        $orientation = "-sagittal";
    }
    elsif ($orient_flag == 8) { # Coronal
        ($xstep, $zstep, $ystep) = 
            ($fileinfo{'colstep'}, $fileinfo{'rowstep'}, $slicestep);
        ($xstart, $zstart, $ystart) = (0, 0, $fileinfo{'slicestart'});
        $orientation = "-coronal";
    }
    else {                      # Transverse
        ($xstep, $ystep, $zstep) = 
            ($fileinfo{'colstep'}, $fileinfo{'rowstep'}, $slicestep);
        ($xstart, $ystart, $zstart) = (0, 0, $fileinfo{'slicestart'});
        $orientation = "-transverse";
    }
    open(MINC, "|itof |rawtominc $mincfile $nslices $nrows $ncols -noclobber ".
         "-float -obyte $orientation ".
         "-xstep $xstep -ystep $ystep -zstep $zstep ".
         "-xstart $xstart -ystart $ystart -zstart $zstart ".
         "-mri -attribute patient:full_name='".$fileinfo{'patient_name'}."' ".
         "-attribute acquisition:repetition_time='".$fileinfo{'tr'}."' ".
         "-attribute acquisition:echo_time='".$fileinfo{'te',$echo}."' ".
         "-attribute acquisition:inversion_time='".$fileinfo{'ti'}."' ".
         "-attribute acquisition:flip_angle='".$fileinfo{'mr_flip'}."' ".
         "-attribute acquisition:scanning_sequence='".$fileinfo{'psdname'}.
         "' ".
         "-attribute ge_mrimage:pseq='".$fileinfo{'pseq'}."' ".
         "-attribute ge_mrimage:pseqmode='".$fileinfo{'pseqmode'}."' ".
         "");

    # Loop through slices
    local($cur_file);
    foreach $slice ($firstslice..$lastslice) {

        # Get file name
        $cur_file = $file_list{$slice, $echo};

        # Print log message
        if (length($cur_file) > 0) {
            print STDERR 
                "Getting data from file \"$cur_file\" for slice $slice.\n";
        }
        else {
            print STDERR "Using blank image for slice $slice.\n";
        }

        # Set up variables for getting the image
        $image_data_len = $nrows * $ncols * $pixel_size;
        if ($fileinfo{'compress'} == 1) { # No compression
            $decompress_cmd = "";
        }
        elsif ($fileinfo{'compress'} == 3) { # Compression
            $decompress_cmd = " | ge_uncompress";
        }
        else {
            close(MINC);
            warn "Bad compression code while creating minc file \"$mincfile\"";
            return;
        }

        # Read the image (if not defined, create a blank image)
        if (length($cur_file) <= 0) {
            $image_data = pack("x$image_data_len",());
        }
        else {
            open(GEDAT, "extract ".
                 $fileinfo{$slice, $echo, 'pixel_data_offset'} . " " .
                 $fileinfo{$slice, $echo, 'pixel_data_len'} . " " . 
                 $cur_file . 
                 $decompress_cmd . " |");
            read(GEDAT, $image_data, $image_data_len);
            close(GEDAT);
            if (length($image_data) != $image_data_len) {
                close(MINC);
                warn "Error decompresssing from $cur_file ".
                    "while creating minc file \"$mincfile\"";
                return;
            }
        }

        # Write out the image
        print MINC $image_data;
    }

    # Close the minc file
    close(MINC);

}

# MAIN PROGRAM

$| = 1;
$outputdir = shift;
if (!defined($outputdir)) {
    die "Usage: $0 <outputdir> [/dev/<tape device> | <file1> <file2> ...]";
}

# Get arguments
if (!defined($ARGV[0])) {
    $tapedrive = "/dev/nrtape";
    @input_list = ();
}
elsif ($ARGV[0] =~ m+^/dev/+) {
    $tapedrive = shift;
    @input_list = ();
    if (scalar(@ARGV) > 0) {
        die "Do not specify tapedrive and input file list, stopped";
    }
}
else {
    $tapedrive = '';
    @input_list = @ARGV;
}

# Should we delete the files?
$delete_files = (length($tapedrive) > 0);

# Create a temporary directory
&create_tmpdir("read_ge_dat_$$");

# Rewind the tape
if (length($tapedrive) > 0) {
    &execute("mt -t $tapedrive rewind");
}

# Skip the first file on the tape
$nextfile = &read_next_file($tapedrive);
if ($delete_files) {
    &remove_file($nextfile);
}

# Loop through files on tape
$keep_looping = 1;
while ($keep_looping) {

    # Get next file
    $nextfile = &read_next_file($tapedrive, *input_list);
    if ($nextfile eq "") {
        $keep_looping = 0;
    }

    # Read in headers
    if ($keep_looping) {
        $image_hdr = 1;
        if (&read_gedat_headers($nextfile, *suite_hdr, *exam_hdr, *series_hdr,
                                *image_hdr, *pixel_hdr, 
                                *pixel_data_offset, *pixel_data_len)) {
            warn "Error reading file \"$nextfile\". Skipping to next.";
            next;
        }

        # Get interesting values
        $cur_numechos = &unpack_value(*image_hdr, 210, 's');
        if ($cur_numechos <= 0) {$cur_numechos = 1;}
        $cur_exam = &unpack_value(*image_hdr, 8, 'S');
        $cur_series = &unpack_value(*image_hdr, 10, 's');
        $cur_image = int((&unpack_value(*image_hdr, 12, 's')-1)/$cur_numechos);
        $cur_echo = &unpack_value(*image_hdr, 212, 's');
        $cur_width = &unpack_value(*pixel_hdr, 8, 'i');
        $cur_height = &unpack_value(*pixel_hdr, 12, 'i');
        $cur_depth = &unpack_value(*pixel_hdr, 16, 'i');
        $cur_compress = &unpack_value(*pixel_hdr, 20, 'i');
        $cur_slicepos = &unpack_value(*image_hdr, 126, 'f');
    }

    # Check if number exam or series has changed or if this is the last file
    if ((scalar(keys(%file_list)) > 0) &&
        (!$keep_looping || 
         ($fileinfo{'exam'} != $cur_exam) || 
         ($fileinfo{'series'} != $cur_series))) {

        # Loop through echos
        @echos = sort(numeric_order keys(%echo_list));
        foreach $echo (@echos) {

            # Create minc file
            ($patient_name = $fileinfo{'patient_name'}) =~
                tr/a-zA-Z0-9_\-/_/cs;
            $patient_name =~ tr/A-Z/a-z/;
            $mincfile = "$outputdir/".$patient_name."_".
                $fileinfo{'exam'}."_".$fileinfo{'series'};
            if (scalar(@echos) > 1) {
                $mincfile .= "_e$echo";
            }
            $mincfile .= "_mri.mnc";
            print STDERR "Creating minc file \"$mincfile\"\n";
            &create_mincfile($mincfile, *file_list, *fileinfo, 
                             $fileinfo{'firstslice'},
                             $fileinfo{'lastslice'}, $echo);
        }

        # Delete files (if needed) and reset variables
        if ($delete_files) {
            &remove_file(values(%file_list));
        }
        undef(%file_list, %echo_list, %fileinfo);
    }

    # Break out here if stopping loop
    if (!$keep_looping) {next;}

    # Check for unusable compression code and depth
    if (($cur_compress != 1) && ($cur_compress != 3)) {
        warn "Unusable compression scheme ($cur_compress) in \"$nextfile\". ".
            "Skipping.";
        next;
    }
    if ($cur_depth != 16) {
        warn "Unusable pixel depth ($cur_depth) in \"$nextfile\". Skipping.";
        next;
    }

    # Save info for all files
    $fileinfo{$cur_image, $cur_echo, 'pixel_data_offset'} = 
        $pixel_data_offset;
    $fileinfo{$cur_image, $cur_echo, 'pixel_data_len'} = 
        $pixel_data_len;

    # If first file, then save appropriate info
    if (scalar(keys(%file_list)) <= 0) {
        $fileinfo{'exam'} = $cur_exam;
        $fileinfo{'series'} = $cur_series;
        $fileinfo{'width'} = $cur_width;
        $fileinfo{'height'} = $cur_height;
        $fileinfo{'depth'} = $cur_depth;
        $fileinfo{'compress'} = $cur_compress;
        $fileinfo{'patient_name'} = &unpack_value(*exam_hdr, 97, "A25");
        $fileinfo{'orientation'} = &unpack_value(*image_hdr, 114, 's');
        $fovx = &unpack_value(*image_hdr, 34, 'f');
        $fovy = &unpack_value(*image_hdr, 38, 'f');
        $fileinfo{'colstep'} = -$fovx / $fileinfo{'width'};
        $fileinfo{'rowstep'} = -(($fovy != 0) ? $fovy : $fovx)
            / $fileinfo{'height'};
        $fileinfo{'firstslice'} = $cur_image;
        $fileinfo{'slicestart'} = $cur_slicepos;
        $fileinfo{'lastslice'} = $cur_image;
        $fileinfo{'sliceend'} = $cur_slicepos;
        $fileinfo{'tr'} = &unpack_value(*image_hdr, 194, 'i')/1000000;
        $fileinfo{'ti'} = &unpack_value(*image_hdr, 198, 'i')/1000000;
        $fileinfo{'mr_flip'} = &unpack_value(*image_hdr, 254, 's');
        $fileinfo{'pseq'} = &unpack_value(*image_hdr, 304, 's');
        $fileinfo{'pseqmode'} = &unpack_value(*image_hdr, 306, 's');
        $fileinfo{'psdname'} = &unpack_value(*image_hdr, 308, 'a33');
        $fileinfo{'psdname'} =~ s/\0.*$//;
        $fileinfo{'psdname'} =~ s/\n//;
    }
    else {
        if (($cur_width != $fileinfo{'width'}) || 
            ($cur_height != $fileinfo{'height'}) || 
            ($cur_compress != $fileinfo{'compress'})) {
            warn "Width, height or compression do not match first file ".
                "for file $nextfile. Skipping to next.";
            next;
        }
        if ($cur_image < $fileinfo{'firstslice'}) {
            $fileinfo{'firstslice'} = $cur_image;
            $fileinfo{'slicestart'} = $cur_slicepos;
        }
        if ($cur_image > $fileinfo{'lastslice'}) {
            $fileinfo{'lastslice'} = $cur_image;
            $fileinfo{'sliceend'} = $cur_slicepos;
        }
    }
    if (!defined($fileinfo{'te', $cur_echo})) {
        $fileinfo{'te', $cur_echo} =
            &unpack_value(*image_hdr, 202, 'i')/1000000;
    }

    # Keep track of files, image numbers and echo numbers
    $file_list{$cur_image, $cur_echo} = $nextfile;
    $echo_list{$cur_echo} = '';

}

# Rewind the tape
if (length($tapedrive) > 0) {
    &execute("mt -t $tapedrive rewind");
}

&cleanup_and_die;
