/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * filename: m-stack.c                                                     *
 *                                                                         *
 * UTIL C-source: Medical Image Conversion Utility                         *
 *                                                                         *
 * purpose      : stack files as specified                                 *
 *                                                                         *
 * project      : (X)MedCon by Erik Nolf                                   *
 *                                                                         *
 * Functions    : MdcGetNormSliceSpacing() - Get spacing between slices    *
 *                MdcStackSlices()         - Stack single slice image files*
 *                MdcStackFrames()         - Stack multi slice volume files*
 *                MdcStackFiles()          - Main stack routine            *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 */

/*
   Copyright (C) 1997-2026 by Erik Nolf

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any later
   version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
   Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */

/****************************************************************************
                              H E A D E R S
****************************************************************************/

#include <stdio.h>
#include <math.h>

#include "medcon.h"

/****************************************************************************
                              D E F I N E S
****************************************************************************/

static FILEINFO infi, outfi;

static int mdc_nrstack=0;

/****************************************************************************
                            F U N C T I O N S
****************************************************************************/

float MdcGetNormSliceSpacing(IMG_DATA *id1, IMG_DATA *id2)
{
  /* slice_spacing = sqrt( (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2 ) */
  float value, slice_spacing;
  float dx, dy, dz;

  slice_spacing = id1->slice_spacing;

  dx = id1->image_pos_pat[0] - id2->image_pos_pat[0];
  dy = id1->image_pos_pat[1] - id2->image_pos_pat[1];
  dz = id1->image_pos_pat[2] - id2->image_pos_pat[2];

  value = (float)sqrt((double)(dx*dx + dy*dy + dz*dz));

  if (fabs(slice_spacing - value) <= MDC_FLT_EPSILON)  {
    /* insignificant difference, use original header value */
    slice_spacing = id1->slice_spacing;
  }else{
    /* significant, use calculated value from image_pos_pat[] info */
    slice_spacing = (float)value;
  }

  return(slice_spacing);

}

/* tomo  : stack single slice image files into one 3D volume file  (2D+ -> 3D)*/
/* planar: stack planar slice image files into one single    file             */
char *MdcStackSlices(void)
{
  FILEINFO *ifi, *ofi;
  IMG_DATA *idprev, *idcur;
  DYNAMIC_DATA *ddprev, *ddcur;
  Uint32 d, nr_of_images;
  int HAS_DYNAMIC_DATA = MDC_NO;
  char *msg=NULL;
  int *total   = mdc_arg_total; /* total arguments of files & conversions */
  int *convs   = mdc_arg_convs; /* counter for each conversion format     */
  char **files = mdc_arg_files; /* array of pointers to input filenames   */
  int i, convert, c;
  float prev_time_frame_duration=0.;
  int prev_pat_slice_orient=0, iprev=0, icur=0;
  char strvalprev[MDC_MAXSTR], strvalcur[MDC_MAXSTR];

  ifi = &infi; ofi= &outfi;

  /* initialize output FILEINFO */
  MdcInitFI(ofi,"stack3d");
  nr_of_images = total[MDC_FILES];


  /* read and stack the several single slice files */
  for (i=0; i<total[MDC_FILES]; i++) {

     /* open file */
     if (MdcOpenFile(ifi,files[i]) != MDC_OK) {
       MdcCleanUpFI(ofi); return("stack slices : Failure to open file");
     }

     /* read the file */
     if (MdcReadFile(ifi,i,NULL) != MDC_OK) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       return("stack slices : Failure to read file");
     }

     if ((ifi->dynnr > 0) && (ifi->dyndata != NULL)) {
       HAS_DYNAMIC_DATA = MDC_YES;
     }else{
       HAS_DYNAMIC_DATA = MDC_NO;
     }

     if (i == 0) {
       /* copy FILEINFO stuff from 1st image */
       MdcCopyFI(ofi,ifi,MDC_NO,MDC_NO);

       /* set some specific parameters */
       ofi->dim[0] = 3;
       ofi->dim[1] = ifi->dim[1];
       ofi->dim[2] = ifi->dim[2];
       ofi->dim[3] = nr_of_images;
       ofi->pixdim[0] = 3.;
       ofi->pixdim[1] = ifi->pixdim[1];
       ofi->pixdim[2] = ifi->pixdim[2];

       if (ofi->planar == MDC_NO) ofi->acquisition_type = MDC_ACQUISITION_TOMO;

       if (!MdcGetStructDD(ofi,1)) {
         MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
         return("stack slices : Couldn't alloc output DYNAMIC_DATA structs");
       }else{
         ofi->dyndata[0].nr_of_slices = nr_of_images;
       }

       if (!MdcGetStructID(ofi,nr_of_images)) {
         MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
         return("stack slices : Couldn't alloc output IMG_DATA structs");
       }

       /* remember time_frame_duration */
       if (HAS_DYNAMIC_DATA == MDC_YES) {
         prev_time_frame_duration = ifi->dyndata[0].time_frame_duration;
       }

       /* remember pat_slice_orient */
       prev_pat_slice_orient = ifi->pat_slice_orient;

     }else{

       if (HAS_DYNAMIC_DATA == MDC_YES) {

         iprev = i; icur = i+1;  /* one based index */

         ddprev = &ofi->dyndata[0];
         ddcur  = &ifi->dyndata[0];

         /* check time_frame_duration differences */
         if (prev_time_frame_duration != ddcur->time_frame_duration) {
           MdcPrntWarn("stack slices : Different 'image durations' between image [%d]:(%f) <> [%d]:(%f)",
                                        iprev, prev_time_frame_duration, icur, ddcur->time_frame_duration);
         }

         /* planar = increment total time_frame_duration */
         if (ofi->planar == MDC_YES) {
           ddprev->time_frame_duration += ddcur->time_frame_duration;
         }

         /* preserve as previous time_frame_duration */
         prev_time_frame_duration = ddcur->time_frame_duration;

       }

     }

     /* sanity checks */
     for (d=3; d < MDC_MAX_DIMS; d++) if (ifi->dim[d] > 1 ) {
        MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
        return("stack slices : Only single slice (one image) files supported");
     }

     if (ifi->dim[3] == 0) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       return("stack slices : File without image found");
     }

     /* copy IMG_DATA info */
     msg = MdcCopyID(&ofi->image[i],&ifi->image[0],MDC_YES);
     if (msg != NULL) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       sprintf(mdcbufr,"stack slices : %s",msg); return(mdcbufr);
     }

     /* small checks for file integrity */
     if (i > 0) {

       iprev = i; icur = i+1;  /* one based index */

       idprev = &ofi->image[i-1]; /* previous slice */
       idcur  = &ifi->image[0];   /* current  slice */

       if (prev_pat_slice_orient != ifi->pat_slice_orient) {
         MdcStringCopy(strvalprev, MdcGetStrPatSlOrient(prev_pat_slice_orient), MDC_MAXSTR);
         MdcStringCopy(strvalcur , MdcGetStrPatSlOrient(ifi->pat_slice_orient), MDC_MAXSTR);
         MdcPrntWarn("stack slices : Different 'patient slice orient' between image [%d]:(%s) <> [%d]:(%s)", 
                                        iprev, strvalprev, icur, strvalcur);
       }

       if ((idprev->width != idcur->width) || (idprev->height != idcur->height)) {
         MdcPrntWarn("stack slices : Different 'image dimensions' between image [%d]:(%u x %u) <> [%d]:(%u x %u)",
                                        iprev, idprev->width, idprev->height, icur, idcur->width, idcur->height);
       }
       if (idprev->slice_width != idcur->slice_width) {
         MdcPrntWarn("stack slices : Different 'slice thickness' between image [%d]:(%f) <> [%d]:(%f)",
                                        iprev, idprev->slice_width, icur, idcur->slice_width);
       }
       if (idprev->slice_spacing != idcur->slice_spacing) {
         MdcPrntWarn("stack slices : Different 'slice spacing' between image [%d]:(%f) <> [%d]:(%f)",
                                        iprev, idprev->slice_spacing, icur, idcur->slice_spacing);
       }
       if (idprev->type != idcur->type) {
         MdcStringCopy(strvalprev, MdcGetStrPixelType(idprev->type), MDC_MAXSTR); 
         MdcStringCopy(strvalcur , MdcGetStrPixelType(idcur->type),  MDC_MAXSTR);
         MdcPrntWarn("stack slices : Different 'pixel type' between image: [%d]:(%s) <> [%d]:(%s)",
                                        iprev, strvalprev, icur, strvalcur);
       }

       /* preserve previous pat_slice_orient */
       prev_pat_slice_orient = ifi->pat_slice_orient;

     }

     MdcCleanUpFI(ifi);

  }

  /* check all the images */
  msg = MdcImagesPixelFiddle(ofi);
  if (msg != NULL) {
    MdcCleanUpFI(ofi);
    sprintf(mdcbufr,"stack slices : %s",msg); return(mdcbufr);
  }

  if (ofi->planar == MDC_NO) {
    /* check for orthogonal slices */
    switch (ofi->pat_slice_orient) {
      case MDC_SUPINE_HEADFIRST_SAGITTAL            :
      case MDC_SUPINE_FEETFIRST_SAGITTAL            :
      case MDC_PRONE_HEADFIRST_SAGITTAL             :
      case MDC_PRONE_FEETFIRST_SAGITTAL             :
      case MDC_SUPINE_HEADFIRST_CORONAL             :
      case MDC_SUPINE_FEETFIRST_CORONAL             :
      case MDC_PRONE_HEADFIRST_CORONAL              :
      case MDC_PRONE_FEETFIRST_CORONAL              :
      case MDC_SUPINE_HEADFIRST_TRANSAXIAL          :
      case MDC_SUPINE_FEETFIRST_TRANSAXIAL          :
      case MDC_PRONE_HEADFIRST_TRANSAXIAL           :
      case MDC_PRONE_FEETFIRST_TRANSAXIAL           :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_SAGITTAL   :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_SAGITTAL   :
      case MDC_DECUBITUS_LEFT_HEADFIRST_SAGITTAL    :
      case MDC_DECUBITUS_LEFT_FEETFIRST_SAGITTAL    :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_CORONAL    :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_CORONAL    :
      case MDC_DECUBITUS_LEFT_HEADFIRST_CORONAL     :
      case MDC_DECUBITUS_LEFT_FEETFIRST_CORONAL     :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_TRANSAXIAL :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_TRANSAXIAL :
      case MDC_DECUBITUS_LEFT_HEADFIRST_TRANSAXIAL  :
      case MDC_DECUBITUS_LEFT_FEETFIRST_TRANSAXIAL  : break;
      default:
        MdcPrntWarn("stack slices : Probably file with Non-Orthogonal slices");
    }
  }

  /* correct slice_spacing */
  for (i=1; i<nr_of_images; i++) {
     idcur  = &ofi->image[i];
     idprev = &ofi->image[i-1];
     idcur->slice_spacing=MdcGetNormSliceSpacing(idcur,idprev);
     if (idcur->slice_spacing == 0.0) idcur->slice_spacing = idcur->slice_width;
  }
  /* and also for the first image */
  if (nr_of_images > 1) {
    ofi->image[0].slice_spacing = ofi->image[1].slice_spacing;
  }

  /* apply read options */
  msg = MdcApplyReadOptions(ofi);
  if (msg != NULL) {
    MdcCleanUpFI(ofi);
    sprintf(mdcbufr,"stack slices : %s",msg); return(mdcbufr);
  }

  /* if requested, reverse slices */
  if (MDC_SORT_REVERSE == MDC_YES) {
    msg = MdcSortReverse(ofi);
    if (msg != NULL) {
      MdcCleanUpFI(ofi);
      sprintf(mdcbufr,"stack slices : %s",msg); return(mdcbufr);
    }
  }

  /* write the file */
  if (total[MDC_CONVS] > 0) {
    /* go through conversion formats */
    for (c=1; c<MDC_MAX_FRMTS; c++) {
      convert = convs[c];
      /* write output format when selected */
      while (convert -- > 0) {
        if (MdcWriteFile(ofi, c, mdc_nrstack++, NULL) != MDC_OK) {
          MdcCleanUpFI(ofi);
          return("stack slices : Failure to write file");
        }
      }
    }
  }

  MdcCleanUpFI(ofi);

  return(NULL);

}

/* tomo  : stack volumes at different time frames into one 4D file (3D+ -> 4D)*/
/* planar: stack planar dynamic files into one planar dynamic file            */
char *MdcStackFrames(void)
{

  FILEINFO *ifi, *ofi;
  Uint32 d, nr_of_frames, nr_of_images=0;
  char *msg = NULL;
  int  *total  = mdc_arg_total; /* total arguments of files & conversions */
  int  *convs  = mdc_arg_convs; /* counter for each conversion format     */
  char **files = mdc_arg_files; /* array of pointers to input filenames   */
  int  i, j, f, convert, c;

  ifi = &infi; ofi = &outfi;

  /* initialize output FILEINFO */
  MdcInitFI(ofi,"stack4d");
  nr_of_frames = total[MDC_FILES];

  for (i=0, j=0, f=0; f < total[MDC_FILES]; f++) {

     /* open file */
     if (MdcOpenFile(ifi,files[f]) != MDC_OK) {
       MdcCleanUpFI(ofi);
       return("stack frames : Failure to open file");
     }

     /* read the file */
     if (MdcReadFile(ifi,f,NULL) != MDC_OK) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       return("stack frames : Failure to read file");
     }
     MdcCloseFile(ifi->ifp); /* no further need */

     /* sanity checks */
     for (d=4; d<MDC_MAX_DIMS; d++) if (ifi->dim[d] > 1) {
        MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
        return("stack frames : Only tomo volumes or planar dynamic supported");
     }
     if ((ifi->dim[3] == 1) && (ifi->planar == MDC_NO)) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       return("stack frames : Use option '-stacks' for single slice files");
     }
     if (ifi->dim[3] == 0) {
       MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
       return("stack frames : File without images found");
     }

     if (f == 0) {
       /* copy FILEINFO stuff from 1st file */
       MdcCopyFI(ofi,ifi,MDC_NO,MDC_NO);

       /* 4D -> dynamic */
       ofi->acquisition_type = MDC_ACQUISITION_DYNAMIC;

       /* get appropriate structs */
       if (!MdcGetStructDD(ofi,nr_of_frames)) {
         MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
         return("stack frames : Couldn't alloc output DYNAMIC_DATA structs");
       }

       /* set some specific parameters */
       if (ofi->planar == MDC_YES) {
         /* planar: asymmetric */
         nr_of_images= ifi->number; /* increment */
         ofi->dim[0] = 3;
         ofi->dim[1] = ifi->dim[1];
         ofi->dim[2] = ifi->dim[2];
         ofi->dim[3] = nr_of_images;
         ofi->pixdim[0] = 3.;
         ofi->pixdim[1] = ifi->pixdim[1];
         ofi->pixdim[2] = ifi->pixdim[2];
         ofi->pixdim[3] = ifi->pixdim[3];
       }else{
         /* tomo  : symmectric */
         nr_of_images= ifi->number * nr_of_frames;
         ofi->dim[0] = 4;
         ofi->dim[1] = ifi->dim[1];
         ofi->dim[2] = ifi->dim[2];
         ofi->dim[3] = ifi->dim[3];
         ofi->dim[4] = nr_of_frames;
         ofi->pixdim[0] = 4.;
         ofi->pixdim[1] = ifi->pixdim[1];
         ofi->pixdim[2] = ifi->pixdim[2];
         ofi->pixdim[3] = ifi->pixdim[3];
         ofi->pixdim[4] = ofi->dyndata[0].time_frame_duration; /* tomo */
       }

       /* malloc all IMG_DATA structs */
       if (!MdcGetStructID(ofi,nr_of_images)) {
         MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
         return("stack frames : Couldn't alloc output IMG_DATA structs");
       }

     }else{

       if (ofi->planar == MDC_YES) {
         nr_of_images += ifi->number;
         ofi->dim[3] = nr_of_images;
         /* malloc IMG_DATA structs current frame */
         if (!MdcGetStructID(ofi,nr_of_images)) {
           MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
           return("stack frames : Couldn't alloc planar IMG_DATA structs");
         }
       }

       /* copy DYNAMIC_DATA struct when available   */
       /* f=0 already copied at initial MdcCopyFI() */
       if ((ifi->dynnr > 0) && (ifi->dyndata != NULL)) {
         MdcCopyDD(&ofi->dyndata[f],&ifi->dyndata[0]);
       }

       /* suspectable differences */
       if (ifi->pat_slice_orient != ofi->pat_slice_orient) {
         MdcPrntWarn("stack frames : Different 'patient slice orient' found");
       }
       if (ifi->planar != ofi->planar) {
         MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
         return("stack frames : wrongful mixture of tomo and planar frames");
       }
     }

     for (i=0; i<ifi->dim[3]; i++, j++) {
        /* copy IMG_DATA info */
        msg = MdcCopyID(&ofi->image[j],&ifi->image[i],MDC_YES);
        if (msg != NULL) {
          MdcCleanUpFI(ofi); MdcCleanUpFI(ifi);
          sprintf(mdcbufr,"stack frames : %s",msg); return(mdcbufr);
        }
     }

     MdcCleanUpFI(ifi);

  }

  /* check all the images */
  msg = MdcImagesPixelFiddle(ofi);
  if (msg != NULL) {
    MdcCleanUpFI(ofi);
    sprintf(mdcbufr,"stack frames : %s",msg); return(mdcbufr);
  }

  if (ofi->planar == MDC_NO) {
    /* check for orthogonal slices */
    switch (ofi->pat_slice_orient) {
      case MDC_SUPINE_HEADFIRST_SAGITTAL            :
      case MDC_SUPINE_FEETFIRST_SAGITTAL            :
      case MDC_PRONE_HEADFIRST_SAGITTAL             :
      case MDC_PRONE_FEETFIRST_SAGITTAL             :
      case MDC_SUPINE_HEADFIRST_CORONAL             :
      case MDC_SUPINE_FEETFIRST_CORONAL             :
      case MDC_PRONE_HEADFIRST_CORONAL              :
      case MDC_PRONE_FEETFIRST_CORONAL              :
      case MDC_SUPINE_HEADFIRST_TRANSAXIAL          :
      case MDC_SUPINE_FEETFIRST_TRANSAXIAL          :
      case MDC_PRONE_HEADFIRST_TRANSAXIAL           :
      case MDC_PRONE_FEETFIRST_TRANSAXIAL           :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_SAGITTAL   :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_SAGITTAL   :
      case MDC_DECUBITUS_LEFT_HEADFIRST_SAGITTAL    :
      case MDC_DECUBITUS_LEFT_FEETFIRST_SAGITTAL    :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_CORONAL    :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_CORONAL    :
      case MDC_DECUBITUS_LEFT_HEADFIRST_CORONAL     :
      case MDC_DECUBITUS_LEFT_FEETFIRST_CORONAL     :
      case MDC_DECUBITUS_RIGHT_HEADFIRST_TRANSAXIAL :
      case MDC_DECUBITUS_RIGHT_FEETFIRST_TRANSAXIAL :
      case MDC_DECUBITUS_LEFT_HEADFIRST_TRANSAXIAL  :
      case MDC_DECUBITUS_LEFT_FEETFIRST_TRANSAXIAL  : break;
      default:
        MdcPrntWarn("stack frames : Probably file with Non-Orthogonal slices");
    }
  }

  /* apply read options */
  msg = MdcApplyReadOptions(ofi);
  if (msg != NULL) {
    MdcCleanUpFI(ofi);
    sprintf(mdcbufr,"stack frames : %s",msg); return(mdcbufr);
  }

  /* write the file */
  if (total[MDC_CONVS] > 0) {
    /* go through conversion formats */
    for (c=1; c<MDC_MAX_FRMTS; c++) {
      convert = convs[c];
      /* write output format when selected */
      while (convert -- > 0) {
        if (MdcWriteFile(ofi, c, mdc_nrstack++, NULL) != MDC_OK) {
          MdcCleanUpFI(ofi);
          return("stack frames : Failure to write file");
        }
      }
    }
  }

  MdcCleanUpFI(ofi);

  return(NULL);

}

char *MdcStackFiles(Int8 stack)
{
  char *msg=NULL;

  if (MDC_CONVERT != MDC_YES)
    return("In order to stack specify an output format");

  if (mdc_arg_total[MDC_FILES] == 1)
    return("In order to stack at least two files are required");

  switch (stack) {
    case MDC_STACK_SLICES: msg = MdcStackSlices();
        break;
    case MDC_STACK_FRAMES: msg = MdcStackFrames();
        break;
  }

  return(msg);
}
