/* 

   GePetReorder_.java
   
   (c) G.Comis - Dip. di Fisiopatologia Clinica - Firenze
       19/05/2003

   Plugin che si aggiunge a (e completa) GePetOp_.java (21/02/01). 

   Questo nuovo plugin tiene conto di due obiezioni formulate da utenti 
   nell'ambito della mailing list di ImageJ:

   1. Luc BOUCHER, Universite' de Montreal (Canada) - 11/02/03

      Nonostante l'algoritmo di ridenominazione dei file di GePetOp_, i suoi
      studi vengono comunque caricati in ordine non corretto;

   2. Mark MENDELKERN, University of California, Irvine (USA) - 04/04/03

      La normalizzazione delle slice viene effettuata singolarmente, ma non
      complessivamente su tutto il volume. Pertanto non e' possibile effet-
      tuare una corretta ricostruzione tridimensionale (ad esempio con Mip_).

   A seguito di tali segnalazioni e' stato deciso di riscrivere completamente
   il plugin (Arf, 09/04/03), impiegando le informazioni contenute nello
   header del ciascun file Dicom. 
   Le informazioni utili sono nella fattispecie:

   - il campo "0020,1041 Slice location" che fornisce l'ordinata della slice
     (e potrebbe pertanto risolvere il problema 1.);

   - il campo "0028,1053 Rescale slope" che fornisce il fattore di normalizza-
     zione della slice (e potrebbe consentire le denormalizzazione della slice,
     e la successiva rinormalizzazione di tutto il volume, problema 2.).

*/
  
import ij.*;
import ij.gui.*;
import ij.io.*;
import ij.measure.*;
import ij.process.*;
import ij.plugin.*;
import ij.plugin.frame.*;
import ij.plugin.PlugIn;
import ij.plugin.filter.PlugInFilter;

import java.io.*;
import java.util.*;
import java.lang.String;

/**************************************************/
public class GePetReorder_ implements PlugInFilter {
/**************************************************/

   static ImagePlus img;
   static boolean globalError;

//---------------------------------------------
   public int setup(String arg, ImagePlus imp){
//---------------------------------------------
            
      this.img = imp;

      if (imp == null){
         IJ.showMessage("No open images.");
         return DONE;
      }

 		if (arg.equals ("about")) {
         return DONE;
      }

//    Si abilita l'elaborazione di stack a 8 o 16 bit ...
      return DOES_8G+DOES_16+STACK_REQUIRED+NO_CHANGES;
   }                                  

//-------------------------------------     
   public void run(ImageProcessor ip) { 
//-------------------------------------     
      
      int iii, jjj;

      double sliceLocation;
      double rescaleSlope;
      double rescaleIntercept;

      String sliceLabel;
      String searchString;
      String patientName;
      String studyDate;

// Si ottiene il numero di slice dello stack originale.

      ImageStack is = img.getStack();      
      int stackSize = is.getSize();

// Si cerca il nome del paziente per impiegarlo come titolo dell'immagine.

      IJ.showStatus("Searching for patient name ...");

      sliceLabel = is.getSliceLabel(1);

      if (sliceLabel == null) {
         IJ.showMessage("Error", "Possibly this is not a DICOM study.");
         IJ.showStatus("Done");
         return;
      }

      searchString = "Patient's Name: ";
      patientName = getStringValue(sliceLabel, searchString, 1);
      if (globalError) {
         IJ.showStatus("Done");
         return;
      }

      patientName = patientName.trim();

      searchString = "Study Date: ";
      studyDate = getStringValue(sliceLabel, searchString, 1);
      if (globalError) {
         IJ.showStatus("Done");
         return;
      }
      
      studyDate = studyDate.trim();
      if (studyDate.length() == 8)
         studyDate = studyDate.substring(6,8) + "/" + studyDate.substring(4,6) + "/" +
                     studyDate.substring(0,4);

//    Si apre una dialog box per la richiesta del numero di proiezioni da costruire ...

      IJ.showStatus("Choosing operating mode ...");
       
      boolean bSlicesRearrangement = true;
      boolean bGlobalNormalization = true;

      GenericDialog gd = new GenericDialog("GePetReorder", IJ.getInstance());

      gd.addMessage("Patient name: " + patientName);
      gd.addMessage("Study date: " + studyDate);

      gd.addCheckbox("Slices rearrangement     ", true);
      gd.addCheckbox("Global normalization     ", true);

      gd.showDialog();
      if (gd.wasCanceled())
         return;

      bSlicesRearrangement = gd.getNextBoolean();
      bGlobalNormalization = gd.getNextBoolean();

      if(!bSlicesRearrangement && !bGlobalNormalization) {
         IJ.showMessage("GePetReorder", "Nothing to do.");
         IJ.showStatus("Done");
         return;
      }

// Si creano e valorizzano due vettori contenenti i valori dei campi
// "Slice Location" e "Rescale Slope" per tutte le fette. Se anche per una
// sola di esse non esiste il campo (o non e' valorizzato), si esce con un
// errore.

      IJ.showStatus("Evaluating slice location / reslope scale for each slice ...");

      double[] sliceLocationArray = new double[stackSize];
      double[] rescaleSlopeArray = new double[stackSize];
      double[] rescaleInterceptArray = new double[stackSize];

      globalError = false;

      for (jjj=1; jjj<=stackSize; ++jjj) {

         sliceLabel = is.getSliceLabel(jjj);

         searchString = "Slice Location: ";
         sliceLocationArray[jjj-1] = getValue(sliceLabel, searchString, jjj);
         if (globalError) {
            IJ.showStatus("Done");
            return;
         }

         searchString = "Rescale Slope: ";
         rescaleSlopeArray[jjj-1] = getValue(sliceLabel, searchString, jjj);
         if (globalError) {
            IJ.showStatus("Done");
            return;
         }

         searchString = "Rescale Intercept: ";
         rescaleInterceptArray[jjj-1] = getValue(sliceLabel, searchString, jjj);
         if (globalError) {
            IJ.showStatus("Done");
            return;
         }
      }

// Si crea e valorizza un vettore "sliceOrder" contenente gli indici delle slice
// dello stack originale in ordine (crescente) di "Slice Location".

      IJ.showStatus("Reordering slices ...");

      int[] sliceOrder = new int[stackSize];
      double[] tmpArray = new double[stackSize];

      for (jjj=0; jjj<stackSize; ++jjj)         
         tmpArray[jjj] = sliceLocationArray[jjj];

      int index = 0;      
      int minIndex = 0;
      double minValue;

      for (iii=0; iii<stackSize; ++iii) {

         minValue = Double.MAX_VALUE;

         for (jjj=0; jjj<stackSize; ++jjj) {
            if (tmpArray[jjj] < minValue) {
               minValue = tmpArray[jjj];
               minIndex = jjj;
            }
         }

         tmpArray[minIndex] = Double.MAX_VALUE;
         sliceOrder[index] = minIndex;
         index++;         
      }

// Si normalizza a 1.0 il vettore che contiene i valori di "Rescale Slope":
// questo perche' talvolta il valore di "Rescale Slope" e' superiore a 1.0.

      IJ.showStatus("Normalizing rescale slope ...");

      double maxRescaleSlope = Double.MIN_VALUE;

      for (iii=0; iii<stackSize; ++iii)
         if (rescaleSlopeArray[iii] > maxRescaleSlope)
            maxRescaleSlope = rescaleSlopeArray[iii];

      for (iii=0; iii<stackSize; ++iii)
         rescaleSlopeArray[iii] /= maxRescaleSlope;

// Si crea un nuovo stack, nel quale si inseriscono le slice originali nel
// corretto ordine (cioe' in ordine decrescente di "Slice Location") e/o
// moltiplicate per il loro "Rescale Slope" (normalizzato a 1.0).

      IJ.showStatus("Creating the reordered stack ...");
 
      int currSlice;
      int newSlice = 1;
      double currRescaleSlope;
      double currRescaleIntercept;
      String currLabel;

      ImageProcessor currIp, newIp, newImgIp;
      ImagePlus newImg = img.createImagePlus();
      ImageStack newStack = img.createEmptyStack();      

      if (bSlicesRearrangement && bGlobalNormalization) {   // Riordinamento e normalizzazione
         for (iii=stackSize-1; iii>=0; --iii) {    
         
            currSlice = sliceOrder[iii] + 1;
            currRescaleSlope = rescaleSlopeArray[currSlice-1];
            currRescaleIntercept = rescaleInterceptArray[currSlice-1];
            currIp = is.getProcessor(currSlice);
            currLabel = is.getSliceLabel(currSlice);

            newStack.addSlice(currLabel, currIp.getPixelsCopy());

            newIp = newStack.getProcessor(newSlice);
            newIp.add(-(currRescaleIntercept));
            newIp.multiply(currRescaleSlope);

            newSlice++; 
         }    
      }

      else if (bSlicesRearrangement) {                      // Solo riordinamento
         for (iii=stackSize-1; iii>=0; --iii) {    
         
            currSlice = sliceOrder[iii] + 1;
            currRescaleSlope = rescaleSlopeArray[currSlice-1];
            currIp = is.getProcessor(currSlice);
            currLabel = is.getSliceLabel(currSlice);

            newStack.addSlice(currLabel, currIp.getPixelsCopy());

            newIp = newStack.getProcessor(newSlice);
            newSlice++; 
         }    
      }

      else if (bGlobalNormalization) {                      // Sola normalizzazione
         for (iii=0; iii<stackSize; ++iii) {    
         
            currSlice = iii+1;
            currRescaleSlope = rescaleSlopeArray[currSlice-1];
            currRescaleIntercept = rescaleInterceptArray[currSlice-1];
            currIp = is.getProcessor(currSlice);
            currLabel = is.getSliceLabel(currSlice);

            newStack.addSlice(currLabel, currIp.getPixelsCopy());

            newIp = newStack.getProcessor(newSlice);
            newIp.add(-(currRescaleIntercept));
            newIp.multiply(currRescaleSlope);

            newSlice++; 
         }    
      }
      else {
         IJ.showMessage("Internal error", "Illegal operating mode choice.");
         IJ.showStatus("Done");
         return;
      }

// Si crea una nuova ImagePlus destinata a contenere lo stack appena
// realizzato, e la si mostra dopo aver ottimizzato i parametri
// di visualizzazione.

      newImg.setStack(patientName, newStack);
      newImgIp = newImg.getProcessor();
      newImgIp.resetMinAndMax();
      newImg.updateAndDraw();
      newImg.show();

		IJ.register(GePetReorder_.class);
      IJ.showStatus("Done");
      
      return;
	}

//---------------------------------------------------------------------
   static public double getValue(String inpString, String searchString,
                                 int currSlice) {
//---------------------------------------------------------------------

// Il metodo "getValue" ritorna il valore numerico eventualmente presente
// dopo il campo "searchString" nella stringa "inpString". Se il campo
// non e' presente, oppure non e' valorizzato, ritorna "NaN" e setta
// il flag "globalError".

      char currChar;

      int nPos;
      int nPosNL;
      int iii;
      int tmpStrLength;
      
      String newLine = "\n";
      String tmpStr;
      String legalVal = "0123456789-+eEdD.";
      
      double outVal;

      if (inpString.length() < searchString.length()) {
         IJ.showMessage("Error", "Possibly this is not a DICOM study.");
         globalError = true;
         return(Double.NaN);
      }

      nPos = inpString.indexOf(searchString);
      if (nPos < 0) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": header doesn't contain \"" + searchString + "\" field.");
         globalError = true;
         return(Double.NaN);
      }

      nPosNL = inpString.indexOf(newLine, nPos);
      if (nPosNL < 0) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": header doesn't contain a new line character after \"" +
            searchString + "\" field.");
         globalError = true;
         return(Double.NaN);
      }

      tmpStr = inpString.substring(nPos+searchString.length(), nPosNL);
      tmpStr = tmpStr.trim();
      tmpStrLength = tmpStr.length();

      if (tmpStrLength < 1) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": No value in \"" + searchString + "\" field.");
         globalError = true;
         return(Double.NaN);
      }

      for (iii=0; iii<tmpStrLength; ++iii) {
         currChar = tmpStr.charAt(iii);
         if (legalVal.indexOf(currChar) < 0) {
            IJ.showMessage("Error", "Image #" + currSlice + 
               ": illegal character[s] in \"" + searchString + "\" field.");
            globalError = true;
            return(Double.NaN);
         }
      }

      outVal = Double.parseDouble(tmpStr);
      return(outVal);
   }

//---------------------------------------------------------------------------
   static public String getStringValue(String inpString, String searchString,
                                       int currSlice) {
//--------------------------------------------------------------------------- 

// Il metodo "getStringValue" ritorna la stringa eventualmente presente
// dopo il campo "searchString" nella stringa "inpString". Se il campo
// non e' presente, oppure non e' valorizzato, ritorna "null" e setta
// il flag "globalError".
      
      char currChar;

      int nPos;
      int nPosNL;
      int iii;
      int outValLength;
      
      String newLine = "\n";      
      String outVal;

      if (inpString.length() < searchString.length()) {
         IJ.showMessage("Error", "Possibly this is not a DICOM study.");
         globalError = true;
         return(null);
      }

      nPos = inpString.indexOf(searchString);
      if (nPos < 0) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": header doesn't contain \"" + searchString + "\" field.");
         globalError = true;
         return(null);
      }

      nPosNL = inpString.indexOf(newLine, nPos);
      if (nPosNL < 0) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": header doesn't contain a new line character after \"" +
            searchString + "\" field.");
         globalError = true;
         return(null);
      }

      outVal = inpString.substring(nPos+searchString.length(), nPosNL);
      outVal = outVal.trim();
      outValLength = outVal.length();

      if (outValLength < 1) {
         IJ.showMessage("Error", "Image #" + currSlice + 
            ": No value in \"" + searchString + "\" field.");
         globalError = true;
         return(null);
      }
      return(outVal);
   }
}

/*** EOF ***/

