<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># !/usr/bin/env python

"""

Version 1.0.0

Description:

    Original Author:
        Erica Morrill
        Department of Biomedical Engineering
        Lerner Research Institute
        Cleveland Clinic
        Cleveland, OH
        morrile2@ccf.org

"""

import matplotlib

matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import matplotlib.pyplot as plt
from matplotlib import patches
import os
import dicom
from SimpleITK import ImageFileReader, GetArrayFromImage
import tkFileDialog
from Tkinter import *
import Tkinter as tk
import tkMessageBox
import tdsmParserMultis
import numpy as np
import peakutils
import xml.etree.ElementTree as ET
from lxml import etree
import XMLparser
import math
import getpass
import time
from scipy import signal


class FileSelectionApp(tk.Tk):
    """Application to display all of the trials in a list"""

    def __init__(self):
        tk.Tk.__init__(self)
        self.main()

    def main(self):
            try:
                self.mainApp = App(None)
                self.mainApp.getFiles()

                self.mainApp.ListBox = self

                self.columnconfigure(0, weight=1)
                self.rowconfigure(0, weight=1)

                self.title('Select Location for Analysis')

                self.lb = tk.Listbox()
                self.sb = tk.Scrollbar(orient=VERTICAL, command=self.lb.yview)
                self.lb.config(yscrollcommand=self.sb.set)

                self.lb.grid(column=0, row=0, sticky=(N, W, E, S))
                self.sb.grid(column=1, row=0, sticky=(N, S))
                self.lb.config(width=40, height=30)

                for item in self.mainApp.locations:
                    self.lb.insert("end", item)

                self.lb.bind("&lt;Double-Button-1&gt;", self.OnDouble)
                self.protocol("WM_DELETE_WINDOW", self.on_exit)

            except:

                tkMessageBox.showerror("Error",
                                       "Directory is not setup in the expected format, check the wiki page for correct format")
                self.destroy()
                self.mainApp.destroy()
                self.quit()
                self.mainApp.quit()

                return


    def OnDouble(self, event):
        """When a location name is specified using a double-click, the execute function begins analysis"""
        widget = event.widget
        selection = widget.curselection()
        value = map(int, selection)
        try:
            self.mainApp.main()
            self.withdraw()
            self.mainApp.execute(value[0])
        except:
            return

    def yview(self, *args):
        apply(self.yview, args)

    def on_exit(self):
        self.quit()


class DraggablePatch:
    # draggable rectangle with the animation blit techniques; see
    # http://www.scipy.org/Cookbook/Matplotlib/Animations

    # Modified slightly from matplotlib Advanced User's Guide
    # http://matplotlib.org/users/event_handling.html

    lock = None  # only one can be animated at a time

    def __init__(self, obj, b, m):
        self.obj = obj
        self.press = None
        self.background = None
        self.yint = b
        self.slope = m

    def connect(self):
        """connect to all the events we need"""
        self.cidpress = self.obj.figure.canvas.mpl_connect('button_press_event', self.on_press)
        self.cidrelease = self.obj.figure.canvas.mpl_connect('button_release_event', self.on_release)
        self.cidmotion = self.obj.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)

    def on_press(self, event):
        """on button press we will see if the mouse is over us and store some data"""
        if event.inaxes != self.obj.axes: return
        if DraggablePatch.lock is not None: return
        contains, attrd = self.obj.contains(event)
        if not contains: return

        x0, y0 = self.obj.center
        self.press = x0, y0, event.xdata, event.ydata

        DraggablePatch.lock = self

        # draw everything but the selected patch and store the pixel buffer
        canvas = self.obj.figure.canvas
        axes = self.obj.axes
        self.obj.set_animated(True)
        canvas.draw()
        self.background = canvas.copy_from_bbox(self.obj.axes.bbox)

        # now redraw just the patch
        axes.draw_artist(self.obj)

        # and blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_motion(self, event):
        """on motion we will move the object if the mouse is over us"""
        if DraggablePatch.lock is not self:
            return
        if event.inaxes != self.obj.axes: return

        if abs(self.slope) &lt; 0.5:
            self.obj.center = (event.xdata, self.slope*event.xdata+self.yint)
        else:
            self.obj.center = ((event.ydata - self.yint) / self.slope, event.ydata)

        canvas = self.obj.figure.canvas

        axes = self.obj.axes

        # restore the background region
        canvas.restore_region(self.background)

        # redraw just the current patch
        axes.draw_artist(self.obj)

        # blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_release(self, event):
        """on release we reset the press data"""
        if DraggablePatch.lock is not self:
            return

        self.press = None
        DraggablePatch.lock = None

        # turn off the patch animation property and reset the background
        self.obj.set_animated(False)
        self.background = None

        # redraw the full figure
        self.obj.figure.canvas.draw()

    def disconnect(self):
        """disconnect all the stored connection ids"""
        self.obj.figure.canvas.mpl_disconnect(self.cidpress)
        self.obj.figure.canvas.mpl_disconnect(self.cidrelease)
        self.obj.figure.canvas.mpl_disconnect(self.cidmotion)

    def getlocation(self):
        return (self.obj.center)

class App(tk.Tk):

    def __init__(self,parent):
        tk.Tk.__init__(self,parent)
        self.withdraw()
        self.parent = parent

        self.protocol("WM_DELETE_WINDOW", self.on_exit)
        # self.FileSelectionApp = FileSelectionApp()
        # self.getFiles()
        # self.main()


    def main(self):
        self.num_saved = 0
        self.mm = 0
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        self.rowconfigure(2, weight=1)
        self.rowconfigure(3, weight=1)
        self.minsize(600, 550)

        self.h = self.winfo_screenheight()

        self.fig = plt.figure(1)
        self.fig = plt.figure(figsize=(self.h/80, self.h/100))

        self.frame = tk.Frame(self)
        self.frame.grid(row=0, column=0)

        self.title("Drag points to tissue boundaries and hit 'Next Image' to save")

        self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame)

        self.canvas.get_tk_widget().grid(row=0, column=0)

        self.canvas._tkcanvas.grid(row=0, column=0)

        self.toolbar = NavigationToolbar2TkAgg(self.canvas, self)
        self.toolbar.update()
        self.toolbar.grid(row=1, column=0, sticky='ewns')

        # self.get_image(self.DCM_accepted[self.mm])

    def getFiles(self):
        """Get accepted trials for the selected subject, this includes the Ultrasound dicom images, Data tdms files,
        and the TimeSynchronization txt file. The program can either open a File Dialog box to search for the directory,
        or the user can hard code the directory"""
        self.fname = '/home/morrile2/Documents/MULTIS Data/MULTIS_invitro/CMULTIS002-2/CMULTIS002-2_UL_US_CT.xml'
        self.locations, self.positions = self.get_positions(self.fname)

    def get_positions(self, fname):
        tree = ET.parse(fname)
        root = tree.getroot()
        locations = []
        positions = []
        for loc in list(list(root)[0]):
            pos = loc.find('Anatomical')
            locations.append(pos.get('file'))
            pos2 = pos.find('USPosition')
            positions.append([float(pos2.find('x').get('value')), float(pos2.find('y').get('value')),
                              float(pos2.find('z').get('value')), float(pos2.find('roll').get('value')),
                              float(pos2.find('pitch').get('value')), float(pos2.find('yaw').get('value'))])

        return locations, positions

    def create_xml(self):
        """Create xml file with forces, moments, and thicknesses"""

        #Define path for the analysis folder
        split_name = os.path.split(self.locations[self.loc_idx])
        analysis_path = os.path.join(os.path.join(os.path.dirname(self.fname), 'TissueThickness'), 'CTManual_zSlice')

        # Check for Analysis directory
        if not os.path.isdir(analysis_path):
            os.makedirs(analysis_path)

        if self.num_saved == 0:
            self.TIME = time.strftime("%Y%m%d%H%M")

        # Name of the xml file
        self.xml_name = os.path.join(analysis_path, split_name[1][0:-5] + '_manThick' + self.TIME + '.xml')

        TDMS_src = os.path.split(self.locations[self.loc_idx])
        subID = os.path.split(os.path.split(self.fname)[0])
        root = ET.Element('TA_sfm_CT', attrib={"version":'1.0.0'})
        ET.SubElement(root, 'FileInfo', attrib={"user":getpass.getuser(), "timestamp":str(self.TIME)})
        subj = ET.SubElement(root, 'Subject', attrib={"ID": subID[1]})
        src = ET.SubElement(subj, 'Source', attrib={"Filename":TDMS_src[1]})
        frm = ET.SubElement(src, 'CTFrame', value=str(self.slice))

        Thick = ET.SubElement(frm, "Thickness")
        ET.SubElement(Thick, "Skin", units="mm").text = str(self.skin)
        ET.SubElement(Thick, "Fat", units='mm').text = str(self.fat)
        ET.SubElement(Thick, "Muscle", units='mm').text = str(self.muscle)

        Position = ET.SubElement(frm, "USPosition", attrib={"CoordinateSys":"CT"})
        ET.SubElement(Position, "x", units='m').text = str(self.positions[self.loc_idx][0])
        ET.SubElement(Position, "y", units='m').text = str(self.positions[self.loc_idx][1])
        ET.SubElement(Position, "z", units='m').text = str(self.positions[self.loc_idx][2])
        ET.SubElement(Position, "roll", units='rad').text = str(self.positions[self.loc_idx][3])
        ET.SubElement(Position, "pitch", units='rad').text = str(self.positions[self.loc_idx][4])
        ET.SubElement(Position, "yaw", units='rad').text = str(self.positions[self.loc_idx][5])

        tree = ET.ElementTree(root)
        tree.write(self.xml_name, xml_declaration=True)

    def execute(self, kk):
        """Initiate the main application to show the Ultrasound images and interactive GUI for the thickness measurements"""
        # global lstFilesDCM, lstFilesTDMS, mm, location, selectedFrames, conv_fact, data, app, masterList, DCM_img

        self.loc_idx = kk

        self.getFrames(self.positions[self.loc_idx])

        self.showPlot()

    def getFrames(self, position):
        # print position
        self.z_pos = position[2]*1000 #Convert z position to mm
        self.x_pos = position[0]*1000
        self.y_pos = position[1]*1000
        T = self.get_transformation_matrix(position)

        # Project the z-axis onto the x-y plane
        self.p2 = np.cross(np.dot(T, np.array([0,0,1,0]))[0:3], np.array([0,0,1]))

    def get_transformation_matrix(self, p):
        ''' Transform from optotrak global coordinates to optotrak position sensor coordinates'''

        q1 = p[0]
        q2 = p[1]
        q3 = p[2]
        q4 = p[3]
        q5 = p[4]
        q6 = p[5]

        T = np.zeros((4, 4))

        T[0, 0] = math.cos(q6) * math.cos(q5)
        T[1, 0] = math.sin(q6) * math.cos(q5)
        T[2, 0] = -math.sin(q5)

        T[0, 1] = math.cos(q6) * math.sin(q5) * math.sin(q4) - math.sin(q6) * math.cos(q4)
        T[1, 1] = math.sin(q6) * math.sin(q5) * math.sin(q4) + math.cos(q6) * math.cos(q4)
        T[2, 1] = math.cos(q5) * math.sin(q4)

        T[0, 2] = math.cos(q6) * math.sin(q5) * math.cos(q4) + math.sin(q6) * math.sin(q4)
        T[1, 2] = math.sin(q6) * math.sin(q5) * math.cos(q4) - math.cos(q6) * math.sin(q4)
        T[2, 2] = math.cos(q5) * math.cos(q4)

        T[0, 3] = q1
        T[1, 3] = q2
        T[2, 3] = q3
        T[3, 3] = 1

        return T

    def get_distance(self, A, B):
        return math.sqrt((A[0] - B[0]) ** 2 + (A[1] - B[1]) ** 2)

    def showPlot(self):
        """Read and show the appropriate frame of the dicom image in tkinter GUI"""

        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.minsize(600, 550)

        # Read dicom image
        if self.mm == 0:
            reader = ImageFileReader()
            reader.SetFileName(
                '/home/morrile2/Documents/MULTIS Data/MULTIS_invitro/CMULTIS002-2/CT/CT_CMULTIS002-2.nii')
            self.img_all = reader.Execute()
            self.deiconify()
            self.origin = self.img_all.GetOrigin()
            self.spacing = self.img_all.GetSpacing()

        else:
            self.fig.clear()

        if self.spacing[0] == self.spacing[1]:
            self.conv_fact = self.spacing[0]
        else:
            print "Spacing is not equal in x and y directions"

        self.slice = int((self.z_pos - self.origin[2])/self.spacing[2])
        pos_x0 = int((-self.x_pos - self.origin[0])/self.spacing[0])
        pos_y0 = int((-self.y_pos - self.origin[1]) / self.spacing[1])

        self.p1 = np.array([pos_x0, pos_y0])

        self.m = -self.p2[0] / self.p2[1]
        self.b = self.p1[1] - self.m * self.p1[0]

        # Read in the appropriate dicom frame and set up plot title
        img = self.img_all[:, :, self.slice]
        self.nda = GetArrayFromImage(img)
        plt.imshow(self.nda, cmap="gray")

        # Create four circles used to indicate tissue interfaces (Top of skin, skin/fat, fat/muscle, bottom of muscle)
        r = 2
        circs = [matplotlib.patches.Circle(self.p1[0:2], radius=r, alpha=0.5, fc='r'),
                 matplotlib.patches.Circle(self.p1[0:2]-10*np.array([-self.p2[1], self.p2[0]]), radius=r, alpha=0.5, fc='b'),
                 matplotlib.patches.Circle(self.p1[0:2]-20*np.array([-self.p2[1], self.p2[0]]), radius=r, alpha=0.5, facecolor='y'),
                 matplotlib.patches.Circle(self.p1[0:2]-30*np.array([-self.p2[1], self.p2[0]]), radius=r, alpha=0.5, facecolor='g')]

        self.drs = []

        # Create main GUI

        self.title("Drag points to tissue boundaries and click 'Next Image' to continue")

        self.ax = plt.gca()
        # Plot the four circles and make them draggable using the DraggablePatch Class
        for circ in circs:
            self.ax.add_patch(circ)
            dr = DraggablePatch(circ, self.b, self.m)
            dr.connect()
            self.drs.append(dr)

        # self.btn1 = tk.Button(self, text="Next Image", command=self.next_image)
        # self.btn1.grid(row=2, column=0)
        self.btn2 = tk.Button(self, text="Done", command=self.end_program)
        self.btn2.grid(row=2, column=0, sticky='e')

        self.canvas.draw()

    def end_program(self):
        """Checks to see if there was an xml file created. If so, it is pretty printed to the filename and the program
        closes all windows. Triggered by the 'Close Program' button"""

        # Get locations of red dots and send to be calculated and saved in xml document
        self.coords = []
        for dr in self.drs:
            coord = dr.getlocation()
            self.coords.append(coord)
        self.calc_thicknesses()  # Calculate the skin, fat and muscle thicknesses
        self.create_xml()


        png_path = os.path.join(os.path.split(self.xml_name)[0], 'ThicknessPNG')
        # Check for Analysis directory
        if not os.path.isdir(png_path):
            os.makedirs(png_path)

        self.fig.savefig(os.path.join(png_path, os.path.split(self.xml_name)[1][0:-4]+'.png'))

        try:
            if os.path.exists(self.xml_name):
                self.prettyPrintXml(self.xml_name)
        except:
            pass

        plt.close(self.fig)
        self.ListBox.deiconify()


    def on_exit(self):
        """Closes all windows if the user selects okay, does not pretty print the xml file"""
        if tkMessageBox.askokcancel("Quit", "Do you want to quit without pretty-printing the xml file and saving summary figure?"):
            self.destroy()
            app.quit()


    def prettyPrintXml(self, xmlFilePathToPrettyPrint):
        """Pretty print the xml file after all frames have been analyzed"""
        assert xmlFilePathToPrettyPrint is not None
        parser = etree.XMLParser(resolve_entities=False, remove_blank_text=True)
        document = etree.parse(xmlFilePathToPrettyPrint, parser)
        document.write(xmlFilePathToPrettyPrint, pretty_print=True, encoding='utf-8')


    def calc_thicknesses(self):
        """Calculate thicknesses of skin, fat and muscle layers (in pixels) and convert to mm"""

        self.skin = self.get_distance(self.coords[1], self.coords[0]) * self.conv_fact
        self.fat = self.get_distance(self.coords[2], self.coords[1]) * self.conv_fact
        self.muscle = self.get_distance(self.coords[3], self.coords[2]) * self.conv_fact

        print(' ')
        print('*****Thickness Calculations*****')
        print('Skin_thickness = %f mm' % self.skin)
        print('Fat_thickness = %f mm' % self.fat)
        print('Muscle_thickness = %f mm' % self.muscle)
        print(' ')


if __name__ == "__main__":
    app = FileSelectionApp()
    app.mainloop()
</pre></body></html>