"""
* Copyright (c) 2009 Stanford University, School of Medicine All rights reserved. 
* 
* STANFORD ACADEMIC SOFTWARE LICENSE AGREEMENT 
* FOR AngioLocalize SOURCE CODE: 
* 
* 1.  This is a legal agreement between you, RECIPIENT, and STANFORD UNIVERSITY.  
*       By accepting, receiving, and using AngioLocalize.including any accompanying information, materials or 
*       manuals (Program), you are agreeing to be bound by the terms of this Agreement.  
*       If you do not agree to the terms of this Agreement, promptly return the Program to the STANFORD.

* 2.  STANFORD grants to RECIPIENT a royalty-free, nonexclusive, and nontransferable license to use the Program 
*       and accompanying source code furnished hereunder, upon the terms and conditions set out below. 
*       documentation and/or other materials provided with the distribution. 
* 3.  RECIPIENT acknowledges that the Program is a research tool still in the development stage and that 
*       it is being supplied as is, without any accompanying services, support or improvements from STANFORD.
* 4.  RECIPIENT agrees to use the Program solely for internal non-commercial purposes. 
* 5.  RECIPIENT may create derivatives and further transfer the Program under the following terms and conditions: 
*        A) Recipient agrees to not commercially exploit the Program and agrees all uses will be for non-commercial purposes; and,
*        B) Recipient may not mark any derivatives with the title of Program and may not to use iPad mark for any derivatives; and,
*        C) Recipient agrees to mark and acknowledge all derivatives as follows: "This is a derivative work based on AngioLocalize:
*           Frameless AVM localization with 2D Digital Subtraction Angiography (DSA) and 3D Rotational Angiography (DRA) Images developed
*           by Dimitre Hristov, Lina Liu.
*        D) Recipient agrees to make all improvements, enhancements and derivatives available under 
*           similar terms and conditions to the research community. 
* 6.   RECIPIENT warrants that RECIPIENT will not remove or export any part of the software or Program from the United 
*       States except in full compliance with all United States and other applicable laws and regulations.
* 7.  RECIPIENT will use the Program in compliance with all applicable laws, policies and regulations including, but not 
*       limited to, any approvals, informed consent and patient confidentiality principles.
* 8.   RECIPIENT will indemnify, hold harmless, and defend Stanford against any claim of any kind arising out of or
*       related to the exercise of any rights granted under this Agreement or the breach of this Agreement by RECIPIENT.
* 9.  Title and copyright to the Program and any associated documentation shall at all times remain with STANFORD, 
*       and RECIPIENT agrees to preserve same.
""" 
#----------------------------------------------------------------------------------
#! Macro module Registration
#/*!
# \file    Registration.py
# \author  DimitreH
# \date    2009-12-09
#
# 
#*/
#----------------------------------------------------------------------------------

from mevis import *

## Global varibles
source = None 
dest = None
srcID = 0
regMatrix = ""
wind = None

  
## initialize some varibles when window pops up
def initWindow():
  ctx.field("PatientID").setValue(ctx.parent().field("PatientID").value);
  ctx.field("PatientsName").setValue(ctx.parent().field("PatientsName").value);
  ctx.field("PatientsBirthDate").setValue(ctx.parent().field("PatientsBirthDate").value);
  ctx.field("PatientsSex").setValue(ctx.parent().field("PatientsSex").value); 
  ctx.field("ImgRegManual.interactive").setValue(False);
  ctx.field("SrcImgLoad.autoLoad").touch();
  ctx.field("TargetImgLoad.autoLoad").touch();
  patientID = getPatientRecordID();
  if (patientID != "0"):
    loadGUILists(patientID);

## cleanUp when windows are closed
def cleanUp():
  ctx.field("SrcImgLoad.close").touch();
  ctx.field("TargetImgLoad.close").touch();
  cancelonRegManual();
  imgReg = ctx.module("ImgRegManual");
  imgReg.call("closeTransWin");
  
## get the DB record ID for a patient given MRO number  
def getPatientRecordID():
  patientID = 0;
  if (ctx.field("PatientID").value == ""):
    return str(patientID);
  
  queryString = "SELECT PatientID FROM Patients WHERE MRO = " + "'" + ctx.field("PatientID").value + "'";
  result = sendQuery(queryString);
  
  if (str(result) != "()"):
    for item in result:
      patientID = item["patientid"];
  return str(patientID);

## send a query to the Database and return the result set
def sendQuery(query):
  ctx.field("DBConn.Statement").setValue(query);
  dbModule = ctx.module("DBConn");
  result = dbModule.call("sendQuery");
  if (result == -1):
    MLAB.logError("The following query to database failed. \n" + query + "\n.");
  dbModule.call("close");
  return result;

## update Records in GUI
def updateRecords():
  patientID = getPatientRecordID();
  if (patientID!= "0"):
    loadGUILists(patientID);

## load Image List for a patient
def loadImageList(patientID):
    imgList = ctx.controlDebug("list3D");
    imgList.clearItems();
    ##init DB
    queryString = "select imageID, modality, seriesDescr from images where imagetype = 2 and patientID = " + patientID + ";";
    result = sendQuery(queryString);
    if (str(result) != "()"):      
      for item in result:
        imgName = item["imageid"];
        imgM = item["modality"];
        series = item["seriesdescr"];
        imgList.insertItem([str(imgName), imgM, series]);
        
## load GUI List       
def loadGUILists(patientID):
    loadImageList(patientID);
    loadRegItem();   
    
## load Registration Items    
def loadRegItem():
    regImage = ctx.controlDebug("registeredImg");
    regImage.clearItems();
    queryString = "select b.affineM as matrix, b.srcImage as src, a.modality as srcM, a.seriesDescr as srcDesc, c.modality as trgM, c.seriesDescr as tgtDesc, b.targetImage as target , d.approvalStatus as status, to_char(d.approvaldate,'YYYY-MM-DD HH12:MI:SS') as savedTime, b.status as genre from images a, regmatrices b, images c, approvals d where b.srcImage = a.imageID and b.targetImage = c.imageID and d.objID = b.matrixID and d.objType = 0 and b.type = 0 and a.patientID = " + getPatientRecordID() + ";"
    result = sendQuery(queryString);
    
    if (str(result) != "()"):
      for item in result:
        matrixReg = item["matrix"];
        srcImg = item["src"];
        tgtImg = item["target"];
        srcMod = item["srcm"];
        srcDesc = item["srcdesc"];
        targetMod = item["trgm"];
        targetDesc = item["tgtdesc"];
        statusNum = item["status"];
        approvalDate = item["savedtime"];
        genreNum = item["genre"];
        status = None;
        genre = None;
        if (statusNum == 0):
          status = "false";
        else:
          status = "true";
        if (genreNum == 0):
          genre = "direct";
        else:
          genre = "inferred";
        regImage.insertItem([str(srcImg), srcMod, srcDesc, str(tgtImg), targetMod, targetDesc, status, genre, approvalDate, matrixReg]);

## pop up the context menu     
def selectChanged3D():
   ctx.control("popup3D").popup();

## getImage File Name bsaed on image ID
def getImageFileName(imgID):
  queryString = "select imageName, imageDir from images where imageID = " + str(imgID);
  result = sendQuery(queryString);
  imgName = None;
  if (str(result) != "()"):
    for item in result:
      imgName = item["imagename"];
  return imgName; 

## load Images into either srcImgLoad or TargetImgLoad control
def loadImage(fileName, srcOrTgt):
  ##srcOrTgt: 1--src   2--target
  image = getImageFileName(fileName);
  fullName = os.path.normpath(image + ".dcm");
  source = os.path.normpath(fullName);
  if (srcOrTgt == 1):
    global srcID;
    srcID = fileName;
    if source != None:
      ctx.field("SrcImgLoad.filename").setValue(source);
    ctx.control("srcLabel").setTitle("Source:" + source);
  else:
    global targetID;
    targetID = fileName;
    if source != None:
      ctx.field("TargetImgLoad.filename").setValue(source);
    ctx.control("targetLabel").setTitle("Target:" + source);
    
## select 3D As Source
def select3DAsSource():
  list3D = ctx.control("list3D");
  srcItem = list3D.selectedItem();
  loadImage(srcItem.text(0), 1);
    
## check if the registration exists given a srcID and target image ID   
def regExist(srcID, targetID):
  queryString = "SELECT matrixID as Num, affineM FROM REGMATRICES WHERE srcImage = " + str(srcID) + " AND targetImage = " + str(targetID) + ";";
  result = sendQuery(queryString);
  if (str(result) == "()"):
    return 0;
  number = 0;
  number = result[0]["num"];
  global regMatrix;
  regMatrix = result[0]["affinem"];
  return number;  

## check if 3D-3D image registration exist
def reg3DExist(srcID, targetID):
  queryString = "SELECT matrixID as Num, affineM, approvalStatus FROM REGMATRICES, approvals WHERE objID = matrixID and objType = 0 and srcImage = " + str(srcID) + " AND type = 0 AND targetImage = " + str(targetID) + ";";
  result = sendQuery(queryString);
  if (str(result) == "()"):
    return 0;
  approval = result[0]["approvalstatus"];
  if (approval == 1):
    return -1;
  number = 0;
  number = result[0]["num"];
  global regMatrix;
  regMatrix = result[0]["affinem"];
  return number; 

## once the user selects a 3D image as target, ImageRegManual page will be popped up for registration.
## If the registration already exists, user can redo, delete or cancel the registration
def select3DAsDest():
  redoOp = False;
  list3D = ctx.control("list3D");
  targetItem = list3D.selectedItem();
  loadImage(targetItem.text(0),2);
  global srcID;
  if (regExist(srcID, targetID)):
    answer = MLAB.showQuestion("The Registration already existed. Do you want to redo or delete?", "", "MeVisLab", ["Redo", "Delete", "Cancel"], 0);
    if (answer== 2):
      return;
    elif (answer == 1):
     deleteReg(srcID, targetID);
     return;
    else:
      redoOp = True;
  global regMatrix; 
  if (redoOp == False):
    regMatrix = getInitialRegMatrix();
  register(regMatrix);

## do registration with given Src and target Image.
def register(regMatrix):
  ctx.field("SrcImgLoad.load").touch();
  ctx.field("TargetImgLoad.load").touch();
  ctx.connectField("ImgRegManual.inImage", "SrcImgLoad.output0");
  ctx.connectField("ImgRegManual.inReferenceImage", "TargetImgLoad.output0");
  global wind;
  wind = ctx.showWindow("RegManualW");
  title = "Patient: " + ctx.field("PatientsName").value + "   with MRO: " + ctx.field("PatientID").value + " DOB: " + ctx.field("PatientsBirthDate").value + " " + ctx.field("PatientsSex").value;
  wind.setTitle(title);
  wind.showMaximized();
  setMatrixValues(regMatrix);

## redo the registration
def redo():
  regList = ctx.control("registeredImg");
  item = regList.selectedItem();
  global targetID;
  global srcID;
  srcID = item.text(0);
  targetID = item.text(3);
  loadImage(item.text(0),1);
  loadImage(item.text(3),2);
  register(item.text(9));

## set the initial values in ImRegManual module
def setMatrixValues(currentMatrix):
  ctx.field("ImgRegManual.initialAlignMatrix").setStringValue(currentMatrix);
  ctx.field("ImgRegManual.initialAlignMatrix").touch();
  ctx.field("ImgRegManual.MemCache.clear").touch();
  ctx.field("ImgRegManual.MemCache.update").touch();
  ctx.field("ImgRegManual.AngioTransformEdit.reset").touch();
  ctx.field("ImgRegManual.interactive").setBoolValue(False);
  ctx.field("ImgRegManual.reformat.resetMatrix").touch();

## delete the registration from database and file system
def deleteReg(srcID, targetID):
  query = "select matrixID from regmatrices where srcImage = " + str(srcID) + " AND targetImage = " + str(targetID) + ";";
  result = sendQuery(query);
  if (result == -1):
    return;
  if (str(result) != "()"):
    regToDel = result[0]["matrixid"];
  query = "DELETE FROM REGMATRICES WHERE srcImage = " + str(srcID) + " AND targetImage = " + str(targetID) + ";";
  result = sendQuery(query);
  query = "DELETE FROM APPROVALS WHERE objType = 0 and objID = " + str(regToDel) + ";";
  result = sendQuery(query);
  MLAB.log("The registration between Record ID: " + str(srcID) + " and " + str(targetID) + " has been deleted from database.");
  delete3DReg(ctx.parent().field("PatientID").value, srcID, targetID);
  loadRegItem();

## pop up the context menu
def selectRegImg():
  ctx.control("popupReg").popup();

## delete the registration from the registered image list
def delete():
  regList = ctx.control("registeredImg");
  item = regList.selectedItem();
  deleteReg(item.text(0), item.text(3));

## save or update the registration into a file and database and return the record ID in database
def save3DReg(insertOp, srcID, targetID, status, regS, type):
 saveRegToFile(regS, srcID, targetID, ctx.parent().field("PatientID").value);
 if (insertOp == 1): 
   queryS = "INSERT INTO REGMATRICES(srcImage, targetImage, affineM, status, type)values(" + str(srcID) + "," + str(targetID) + ",'" + regS + "'" + "," + str(status) + "," + str(type) + ");";
 else:
   queryS = "UPDATE REGMATRICES set affineM = " + "'" + regS + "'" + ", status = " + str(status) + " where srcImage = " + str(srcID) + " AND targetImage = " + str(targetID) + ";"; 
 result = sendQuery(queryS);
 if (result == -1):
   return 0;
 query = "select matrixID from regmatrices where type = " + str(type) + " and srcImage = " + str(srcID) + " and targetImage = " + str(targetID) + ";";
 result = sendQuery(query);
 imgID = 0;
 if (str(result) != "()"): 
   for item in result:
      imgID = item["matrixid"]; 
 return imgID;

## Save 2D registration into File and Database
## Due to the lack of store procedure support of MevisLab, the database queries are made individually
## type is inferred or not.
def save2DReg(insertOp, srcID, targetID, status, regS, perspect, postAffine, type):
 saveRegToFile(regS, srcID, targetID, ctx.parent().field("PatientID").value); 
 if (insertOp == 1): 
   queryS = "INSERT INTO REGMATRICES(srcImage, targetImage, affineM, perspectM, postAffineM, status, type)values(" + str(srcID) + "," + str(targetID) + ",'" + regS + "'" + "," + "'" + perspect + "','" + postAffine + "'," + str(status) + "," + str(type) + ");";
 else:
   queryS = "UPDATE REGMATRICES set affineM = " + "'" + regS + "'" + ", perspectM = '" + perspect + "', postAffineM = '" + postAffine + "', status = " + str(status) + " where srcImage = " + str(srcID) + " AND targetImage = " + str(targetID) + ";"; 
 result = sendQuery(queryS);
 if (result == -1):
   return 0;
 query = "select matrixID from regmatrices where type = " + str(type) + " and srcImage = " + str(srcID) + " and targetImage = " + str(targetID) + ";";
 result = sendQuery(query);
 imgID = 0;
 for item in result:
    imgID = item["matrixid"]; 
 return imgID;

## get the matrix that can be stored in database. 
## The final matrix is the multiplication of matrix in reformat and the one in InitialAlignment
def getFinalMatrix():
  regMatrix = ctx.field("ImgRegManual.regMatrix").stringValue();
  imgRegM = ctx.module("ImgRegManual");
  return imgRegM.call("getFinalMatrix");

## save or update the approval status for a given object(a registration here)
def saveApprovalStatus(imgID, status, insertOp):
 if (insertOp == 0):
   queryS = "Update APPROVALS SET approvalStatus = " + str(status) + ", approvaldate = now() where UserID = 1 AND objType = 0 AND objID = " + str(imgID) + ";";
 else:
   queryS = "INSERT INTO APPROVALS(userID, objID, objType, patientID, approvalStatus)VALUES(1," + str(imgID) + ", 0 , " + getPatientRecordID() + "," + str(status) + ");";
 ctx.field("DBConn.Statement").setValue(queryS);
 result = sendQuery(queryS);
 
## get the inverse of a matrix 
def getInversedM(regMatrix):
  ctx.field("ImgRegManual.MatrixArithmetic.matrixA").setStringValue(regMatrix);
  ctx.field("ImgRegManual.MatrixArithmetic.operation").setStringValue("Inverse");
  return ctx.field("ImgRegManual.MatrixArithmetic.outputMatrixC").stringValue();

## multiple regMatrix and Affine Matrix
def regMultiply(regMatrix, Affine):
  ctx.field("ImgRegManual.MatrixArithmetic.matrixA").setStringValue(regMatrix);
  ctx.field("ImgRegManual.MatrixArithmetic.matrixB").setStringValue(Affine);
  ctx.field("ImgRegManual.MatrixArithmetic.operation").setStringValue("Multiply");
  return ctx.field("ImgRegManual.MatrixArithmetic.outputMatrixC").stringValue();

## propagate the registration to 3D and 2D registration
def propagateTo2D(targetID, regMatrix, srcID, approvalStatus):
  queryString = "SELECT srcImage, affineM, perspectM, postAffineM, approvalStatus, status FROM REGMATRICES, approvals WHERE objID = matrixID and targetImage = " + str(targetID) + " AND type = 1 " + ";";
  result = sendQuery(queryString);
  if (str(result) == "()"):
    return;
  newTarget = 0;
  for item in result:
     newSrc = item["srcimage"];
     oldApprovalS = item["approvalstatus"];
     Affine = item["affinem"];
     perspect = item["perspectm"];
     postAffine = item["postaffinem"];
     if (newSrc != 0):
       insertOp = 0;
       status = 1; # status is inferred
       newAffine = regMultiply(Affine, regMatrix);
       regS = newAffine.replace("\n", " ");
       if (directRegExist(newSrc, srcID)):
         continue;
       retV = regExist(newSrc, srcID);
       if (retV == 0): 
          # means it do not exist
          insertOp = 1;
       newApprovalS = approvalStatus & oldApprovalS;
       regID = save2DReg(insertOp, newSrc, srcID, status, regS, perspect, postAffine, 1);
       saveApprovalStatus(regID, newApprovalS, insertOp);
      
## propagate the registration to 3D and 3D registration
##For example if registration for 3D image 1 and 2 exist, 2 and 3 exist, 
## we need to infer registration for 1 and 3 and save it into database 
def propagateTo3D(targetID, regMatrix, srcID, approvalStatus):
  queryString = "SELECT targetImage, affineM, approvalStatus, status FROM REGMATRICES, approvals WHERE objID = matrixID and srcImage = " + str(targetID) + " AND type = 0 " + ";";
  result = sendQuery(queryString);
  if (str(result) == "()"):
    return;
  newTarget = 0;
  for item in result:
    newTarget = item["targetimage"];
    oldApprovalS = item["approvalstatus"];
    if (int(newTarget) == int(srcID)):
      continue;
    if(directRegExist(srcID, newTarget)):
        continue;  
    Affine = item["affinem"];
    if (newTarget != 0):
      insertOp = 0;
      status = 1; # status is inferred
      newAffine = regMultiply(regMatrix, Affine);
      regS = newAffine.replace("\n", " ");
      ret = regExist(srcID, newTarget);
      if (ret== 0):
         insertOp = 1;
      newApprovalS = approvalStatus & oldApprovalS;
      regID = save3DReg(insertOp, srcID, newTarget, status, regS, 0);
      saveApprovalStatus(regID, newApprovalS, insertOp);
      saveRegToFile(regS, srcID, newTarget,ctx.parent().field("PatientID").value);
       # update the DB now, find the inverse of the reg
      inverseM = getInversedM(newAffine);
      regS = inverseM.replace("\n", " "); 
      status = 1; # inferred status for inversed reg
      regID = save3DReg(insertOp, newTarget, srcID, status, regS, 0);
      saveApprovalStatus(regID, newApprovalS, insertOp);
      

## This function is to check if the registration exists and is direct
def directRegExist(srcID, targetID):
  query = "SELECT matrixID from RegMatrices where srcImage = " + str(srcID) + " and targetImage = " + str(targetID) + " and status = 0;"
  result = sendQuery(query);
  if(str(result) != "()"):
    return True;
  query = "SELECT matrixID from RegMatrices, approvals where srcImage = " + str(srcID) + " and targetImage = " + str(targetID) + " and status = 1 and approvals.objID = regmatrices.matrixID and approvals.objType = 0 and approvalStatus = 1;" 
  result = sendQuery(query);
  if(str(result) != "()"):
    return True;
  return False;

## the function is called when user clicks "save" button
def saveOnRegManual():
  saveRegAndApprovalStatus(0);

## the function is called when user clicks "save and approve" button 
def approveOnRegManual():
   saveRegAndApprovalStatus(1);

## the function is called when user clicks "cancel" button
def cancelonRegManual():
  MLAB.log("The user reviewed the image and canceled.");
  ctx.disconnectField("ImgRegManual.inReferenceImage");
  ctx.disconnectField("ImgRegManual.inImage");
  
## save the registration and approval status    
def saveRegAndApprovalStatus(status): 
   insertOp = 0;
   global srcID;
   global targetID;
   regStatus = 0;
   if (regExist(srcID, targetID) == 0):
     insertOp = 1;
   regMatrix = getFinalMatrix();
   regS1 = regMatrix.replace("\n", " ");  
   regID = save3DReg(insertOp, srcID, targetID, regStatus, regS1, 0);
   saveApprovalStatus(regID, str(status), insertOp);
   saveRegToFile(regS1, srcID, targetID,ctx.parent().field("PatientID").value);
   # update the DB now, find the inverse of the reg
   inverseM = getInversedM(regMatrix);
   regS = inverseM.replace("\n", " ");  
   if (regExist(targetID, srcID) == 0):
     insertOp = 1;
   else:
      insertOp = 0;
   regID = save3DReg(insertOp, targetID, srcID, regStatus, regS, 0);
   saveApprovalStatus(regID, str(status), insertOp);
   saveRegToFile(regS, targetID, srcID,ctx.parent().field("PatientID").value);
   propagateTo3D(targetID, regS1, srcID, status);
   propagateTo3D(srcID, regS, targetID, status);
   
   propagateTo2D(targetID, regS, srcID, status);
   propagateTo2D(srcID, regS1, targetID, status);
   global wind;
   if(wind != None):
     wind.close();
   loadRegItem(); 

## show Help
def help():
  ctx.showHtmlHelp();

## get the initial Matrix when there is no registration
def getInitialRegMatrix():
  im1=ctx.field("TargetImgLoad.output0");
  im2=ctx.field("SrcImgLoad.output0");
  v1=im1.mapVoxelToWorld(im1.size3D());
  v10=im1.mapVoxelToWorld((0,0,0));
  v2=im2.mapVoxelToWorld(im2.size3D());
  v20=im2.mapVoxelToWorld((0,0,0));
  print "1 0 0 "+str(-(v1[0]+v10[0]-v2[0]-v20[0])/2)+" 0 1 0 "+str(-(v1[1]+v10[1]-v2[1]-v20[1])/2)+" 0 0 1 "+str(-(v1[2]+v10[2]-v2[2]-v20[2])/2)+" 0 0 0 1"
  return "1 0 0 "+str(-(v1[0]+v10[0]-v2[0]-v20[0])/2)+" 0 1 0 "+str(-(v1[1]+v10[1]-v2[1]-v20[1])/2)+" 0 0 1 "+str(-(v1[2]+v10[2]-v2[2]-v20[2])/2)+" 0 0 0 1"
#//# MeVis signature v1
#//# key: MFowDQYJKoZIhvcNAQEBBQADSQAwRgJBANEfsmYse2e1dRhkQ9AQbreCq9uxwzWLoGom13MNYmyfwoJqQOEXljLFAgw2eEjaT12G4CdqKWhRxh9ANP6n7GMCARE=:VI/mB8bT4u+mRtf/ru8yUQi8BzpaS3UeL2x62YxsUYnVqCWuLrVNLiukIIjnJMKQXlc8ezmgOIcVAV7pgvgKpQ==
#//# owner: MeVis
#//# date: 2009-06-10T04:12:25
#//# hash: vHSfj+Qt8xrIkDVP4W0Rep0YH6a3+MySa0+Cs+AoqEK/IbB+f5szwmyVTENOjzz/JEgm2t31FtHmUUMFcQg5VQ==
#//# MeVis end

