File: elx_compare_landmarks.py

package info (click to toggle)
elastix 5.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 42,480 kB
  • sloc: cpp: 68,403; lisp: 4,118; python: 1,013; xml: 182; sh: 177; makefile: 33
file content (137 lines) | stat: -rw-r--r-- 6,060 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import sys, subprocess
import os
import os.path
import re
import shutil
import math
#import numpy
from optparse import OptionParser

#-------------------------------------------------------------------------------
# the main function
def main():
  # usage, parse parameters
  usage = "usage: %prog [options] arg";
  parser = OptionParser( usage );

  # option to debug and verbose
  parser.add_option( "-v", "--verbose", action="store_true", dest="verbose" );

  # options to control files
  parser.add_option( "-d", "--directory", dest="directory", help="elastix output directory" );
  parser.add_option( "-f", "--fixedlandmarks", dest="flm", help="fixed image landmarks" );
  parser.add_option( "-b", "--baselinetp", dest="btp", help="baseline transform parameter file" );
  parser.add_option( "-t", "--threshold", dest="threshold", help="threshold on landmark error" );
  parser.add_option( "-p", "--path", dest="path", help="path where executables can be found" );

  (options, args) = parser.parse_args();

  # Check if option -d and -f and -b are given
  if options.directory == None :
    parser.error( "The option directory (-d) should be given" );
  if options.flm == None :
    parser.error( "The option directory (-f) should be given" );
  if options.btp == None :
    parser.error( "The option directory (-b) should be given" );
  if options.threshold == None : threshold = 0.5;
  else :                         threshold = options.threshold;

  # Get the transform parameters files
  tpFileName   = os.path.join( options.directory, "TransformParameters.0.txt" );
  tpFileName_b = options.btp;

  # Sanity checks
  if not os.path.exists( tpFileName ) :
    print( "ERROR: the file " + tpFileName + " does not exist" );
    return 1;

  # Below we use programs that are compiled with elastix, and are thus available
  # in the binary directory. The user of this script has to supply the path
  # to the binary directory via the command line.
  # In order to make sure that python is able to find these programs we add
  # the paths to the local environment.
  _path = os.path.dirname( options.path );
  _path += os.pathsep + os.getenv('PATH');
  os.environ['PATH'] = _path;

  # output file of the transformix runs; and copies, for later debugging.
  landmarkstemp = os.path.join( options.directory, "outputpoints.txt" );
  landmarks1full = os.path.join( options.directory, "outputpoints_current.txt" );
  landmarks2full = os.path.join( options.directory, "outputpoints_baseline.txt" );

  # Remove copies otherwise os.rename will not work on Windows:
  # "On Windows, if dst already exists, OSError will be raised"
  if( os.path.exists( landmarks1full ) ) : os.remove( landmarks1full );
  if( os.path.exists( landmarks2full ) ) : os.remove( landmarks2full );

  #
  # Transform the fixed image landmarks by the current result
  #
  print( "Transforming fixed image landmarks using " + tpFileName );
  landmarks1 = os.path.join( options.directory, "landmarks_current.txt" );
  subprocess.call( [ "transformix", "-def", options.flm, "-out", options.directory, "-tp", tpFileName ],
    stdout=subprocess.PIPE );

  # Parse file to extract only the column with the output points
  f1 = open( landmarkstemp, 'r' );
  f2 = open( landmarks1, 'w' );
  for line in f1 :
    f2.write( line.strip().split(';')[4].strip().strip( "OutputPoint = [ " ).rstrip( " ]" ) + "\n" );
  f1.close(); f2.close();
  shutil.move( landmarkstemp, landmarks1full ); # for later inspection

  #
  # Transform the fixed image landmarks by the baseline result
  #
  print( "Transforming fixed image landmarks using " + tpFileName_b );
  landmarks2 = os.path.join( options.directory, "landmarks_baseline.txt" );
  subprocess.call( [ "transformix", "-def", options.flm, "-out", options.directory, "-tp", tpFileName_b ],
    stdout=subprocess.PIPE );
  # shutil.copyfile( landmarkstemp, landmarks2 ); // this should not be necessary

  # Parse file to extract only the column with the output points
  f1 = open( landmarkstemp, 'r' );
  f2 = open( landmarks2, 'w' );
  for line in f1 :
    f2.write( line.strip().split(';')[4].strip().strip( "OutputPoint = [ " ).rstrip( " ]" ) + "\n" );
  f1.close(); f2.close();
  shutil.move( landmarkstemp, landmarks2full ); # for later inspection

  # Compute the distance between all transformed landmarks
  f1 = open( landmarks1, 'r' ); f2 = open( landmarks2, 'r' );
  distances = [];
  for line1, line2 in zip( f1, f2 ) :
    floats1 = [ float(x) for x in line1.split() ];
    floats2 = [ float(x) for x in line2.split() ];
    diffSquared = [ (m - n) * (m - n) for m, n in zip( floats1, floats2 ) ];
    distance = math.sqrt( sum( diffSquared ) );
    distances.append( distance );

  # Compute some statistics on the distances
  distances.sort();
  minDistance  = "{0:.3f}".format( distances[ 0 ] );
  Q1           = "{0:.3f}".format( distances[ int( len( distances ) * 1.0 / 4.0 ) ] );
  medDistance  = "{0:.3f}".format( distances[ int( len( distances ) * 2.0 / 4.0 ) ] );
  Q3           = "{0:.3f}".format( distances[ int( len( distances ) * 3.0 / 4.0 ) ] );
  maxDistance  = "{0:.3f}".format( distances[ -1 ] );
  meanDistance = "{0:.3f}".format( sum( distances ) / float( len( distances ) ) );

  # With numpy it would be:
  #l1 = numpy.loadtxt( landmarks1 );
  #l2 = numpy.loadtxt( landmarks2 );
  #meandistance = numpy.mean( numpy.sum( (l1-l2)**2, axis=-1)**0.5 );

  # Report
  print( "The landmark distance between current and baseline is:" );
  print( "min   | Q1    | med   | Q3    | max   | mean" );
  print( minDistance + " | " +  Q1 + " | " +  medDistance + " | " +  Q3 + " | " + maxDistance + " | " +  meanDistance  );
  if float( Q3 ) < float( threshold ) :
    print( "SUCCESS: third quartile landmark distance is lower than " + str( threshold ) + " mm" );
    return 0;
  else :
    print( "FAILURE: third quartile landmark distance is higher than " + str( threshold ) + " mm" );
    return 1;

#-------------------------------------------------------------------------------
if __name__ == '__main__':
    sys.exit(main())