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

IN PROGRESS

Description:
This script was written to manually extract grayscale pixel values surrounding tissue boarders.


Getting started:
The user may either hard code the path to the subjectID folder or choose to open a file dialog to browse for the
folder.
The accepted trials are then loaded into a listbox.
Select the trial you would like to analyze by 'double-clicking' the name.
The first frame will appear for analysis.
Move the four red dots to the appropriate locations
    Transducer/Skin boundary
    Skin/Fat boundary
    Fat/Muscle boundary
    Muscle/Bone boundary
When you are satisfied with the locations, hit 'enter' and the data will be saved as a numpy binary file in a folder
named TrainingData (Make sure this folder is defined prior to running this code).


    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
import SimpleITK as sitk
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
import lxml.etree as etree
import XMLparser
import math
import Plot_thicknessForce
import pandas


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

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

        self.mainApp = App(None)
        self.mainApp.getFiles()
        self.mainApp.ListBox = self

        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.lstFilesTDMS:
        #     self.lb.insert("end", item[-30:-5])

        self.loc = []
        self.res = []
        uu = 0
        for x in self.mainApp.masterList:
            if x[1] == 1:
                for item in self.mainApp.lstFilesTDMS:
                    if item[-30:-27] == x[0]:
                        self.lb.insert("end", item[-30:-5])
                        self.loc.append(self.mainApp.lstFilesTDMS.index(item))
                        RefDs = dicom.read_file(self.mainApp.lstFilesDCM[uu])
                        seq = RefDs.SequenceofUltrasoundRegions
                        conv_fact = seq[0].PhysicalDeltaY * 10
                        self.res.append([item[-30:-5], conv_fact])
                        uu +=1

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

    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(int(self.loc[value[0]]))
        except:
            pass

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


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):
        self.obj = obj
        self.press = None
        self.background = None

    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

        self.obj.center = (self.obj.center[0], 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[1])

class App(tk.Tk):

    def __init__(self,parent):
        tk.Tk.__init__(self,parent)
        self.withdraw()
        self.parent = parent
        self.mm = 0
        self.protocol("WM_DELETE_WINDOW", self.dest)
        # self.FileSelectionApp = FileSelectionApp()
        # self.getFiles()
        # self.main()

    def dest(self):
        self.destroy()
        sys.exit()

    def main(self):
        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()
        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("Select tissue type on right and then draw rectangle enclosing the respective tissue")

        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"""

        # Hard code Subject Directory
        self.directoryname = '/home/morrile2/Documents/MULTIS_test/MULTIS001-1'

        self.lstFilesDCM = []  # create an empty list for DICOM files
        self.lstFilesTDMS = []  # create an empty list for TDMS files

        self.masterList, self.num_accept = XMLparser.getAcceptance(self.directoryname)

        #Find DICOM and TDMS files
        for dirName, subdirList, fileList in os.walk(self.directoryname):
            for filename in fileList:
                if ".ima" in filename.lower():  # check whether the file's DICOM
                    self.lstFilesDCM.append(os.path.join(dirName, filename))
                elif ".tdms" in filename.lower():  # check whether the file's TDMS
                    self.lstFilesTDMS.append(os.path.join(dirName, filename))

        # Sort variables by trial number
        self.lstFilesTDMS.sort()
        self.lstFilesDCM.sort()


    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.location = kk

        for x in self.masterList:
            if x[0] == self.lstFilesTDMS[self.location][-30:-27]:
                if x[1] == 1:
                    if self.mm == 0:
                        for DCM in self.lstFilesDCM:
                            if DCM[-65:-62] == self.lstFilesTDMS[self.location][-30:-27]:
                                self.DCM_img = DCM

                                print("Verify that file names correlations")
                                print(self.DCM_img)
                                print(self.lstFilesTDMS[self.location])

                                try:
                                    self.getFrames()
                                    # app.destroy()  # Closes list window because the file selection is complete

                                    if self.mm &lt; len(self.frames):
                                        self.showPlot()
                                except:
                                    tkMessageBox.showerror("Error",
                                                           "The trial you have selected does not have an associated "
                                                           "deltaT XML in the TimeSynchronization folder")
                                    self.ListBox.deiconify()
                else:
                    tkMessageBox.showerror("Error", "The trial you have selected is not an accepted trial")
                    self.ListBox.deiconify()

    def getFrames(self):
        """Get the frames to be analyzed and save the data for those frames. Different for indentation and anatomical
        trials. Indentation contains frames that start at indentation and go through the peak force of the indentation,
        while anatomical analyzes all frames between start and end pulses from minimum to maximum force"""

        #Find the xml file with delta_t
        self.analysis_path = os.path.dirname(os.path.dirname(self.lstFilesTDMS[self.location])) + '/TimeSynchronization'
        self.split_name = os.path.split(self.lstFilesTDMS[self.location])
        self.tail = self.split_name[1][0:25]+'_dT.xml'

        self.delta_t_file = self.find_file(self.tail, self.analysis_path)
        print(self.delta_t_file)

        if self.delta_t_file == None:
            return
        else:

            # Extract force information from TDMS file
            self.data = tdsmParserMultis.parseTDMSfile(self.lstFilesTDMS[self.location])

            Fx = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load Fx'])
            Fy = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load Fy'])
            Fz = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load Fz'])
            Mx = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load Mx'])
            My = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load My'])
            Mz = np.array(self.data[u'State.6-DOF Load'][u'6-DOF Load Mz'])

            F_mag = []
            for f in range(len(Fx)):
                F_mag.append(math.sqrt(Fx[f]**2+Fy[f]**2+Fz[f]**2))

            pulse = np.array(self.data[u'Sensor.Run Number Pulse Train'][u'Run Number Pulse Train'])
            pulse = pulse[:]

            Peaks = peakutils.indexes(pulse, thres=0.5 * max(pulse), min_dist=100)

            doc1 = ET.parse(self.delta_t_file)
            root1 = doc1.getroot()
            loc1 = root1.find("Location")
            dT_str = loc1.find("dT").text
            self.dT = float(dT_str)

            # Read metadata from the dicom image
            self.RefDs = dicom.read_file(self.lstFilesDCM[self.location])
            self.frameTimeVector = self.RefDs.FrameTimeVector
            seq = self.RefDs.SequenceofUltrasoundRegions
            self.conv_fact = seq[0].PhysicalDeltaY * 10  # Extract conversion factor from dicom metadata (Original is in cm, convert
            # to mm)

            if self.lstFilesTDMS[self.location][-8:-7] == 'I':
                indentation = True
                anatomical = False
            elif self.lstFilesTDMS[self.location][-8:-7] == 'A':
                indentation = False
                anatomical = True

            force_list = list(F_mag)

            time = np.arange(len(F_mag))
            data_i = zip(time, Fx, Fy, Fz, Mx, My, Mz, F_mag)

            if indentation:
                # Indentation frames from start of indentation to peak force during indentation
                # Find indentation start time in TDMS timeline , denoted by tdms b/c used for tdms
                avg = self.createAverageFit(F_mag, 300)
                index_range = 200
                for items in range(len(avg[0:-index_range])):
                    if abs(avg[items + index_range] - avg[items]) &gt; 1.5:
                        time_start_tdms = items + index_range / 2
                        break

                time_max_tdms = force_list.index(max(force_list))  # note max and min reversed as Fx is negative force

                start_frame, start_frame_time_tdms = self.findFrame(time_start_tdms)
                max_frame, max_frame_time_tdms = self.findFrame(time_max_tdms)

                frame_lst_i = [start_frame]
                data_i_final = [data_i[start_frame_time_tdms]]

                for t in data_i[start_frame_time_tdms + 1:max_frame_time_tdms]:
                    inc_frame, inc_frame_time_tdms = self.findFrame(t[0])
                    if inc_frame not in frame_lst_i:
                        frame_lst_i.append(inc_frame)
                        data_i_final.append(data_i[inc_frame_time_tdms])

                self.frames = frame_lst_i
                self.DATA = data_i_final

            elif anatomical:
                # Anatomical frames from minimum to maximum normal force (Fx)
                time_preIndent_tdms = Peaks[0]  # 230 ms is location first pulse.
                time_postIndent_tdms = Peaks[-1]

                # Sort the data by the magnitude (F_mag) to get anatomical frame list from minimum to maximum
                data_a = zip(time, Fx, Fy, Fz, Mx, My, Mz, F_mag)
                data_a.sort(key=lambda t: abs(t[7]))

                frame_lst_a = []
                data_a_sort = []

                for t in data_a:
                    try:
                        min_frame, min_frame_time_tdms = self.findFrame(t[0])
                    except:
                        continue
                    if min_frame not in frame_lst_a:
                        if min_frame_time_tdms &gt; time_preIndent_tdms and min_frame_time_tdms &lt; time_postIndent_tdms:
                            frame_lst_a.append(min_frame)
                            data_a_sort.append(data_i[min_frame_time_tdms])

                final_a_sort = zip(frame_lst_a, data_a_sort)
                final_a_sort.sort(key=lambda t: abs(t[1][7]))

                # #Analyze the min, max, and middle force
                self.frames = [final_a_sort[0][0], final_a_sort[int(len(final_a_sort)/2)][0], final_a_sort[len(final_a_sort)-1][0]]
                self.DATA = [final_a_sort[0][1], final_a_sort[int(len(final_a_sort)/2)][1], final_a_sort[len(final_a_sort)-1][1]]


                # #Analyze from min to max
                # for d in final_a_sort:
                #     self.frames.append(d[0])
                #     self.DATA.append(d[1])



    def createAverageFit(self, Fx, avgThres):
        """Filter the tdms normal force data"""
        averagelist = []
        for items in range(len(Fx)):
            if Fx[items] != Fx[items - avgThres]:
                num2avg = Fx[items:(items + avgThres)]
                averagelist.append(np.average(num2avg))
            else:
                continue

        averagelist = [averagelist[0]] * (avgThres / 2) + averagelist[0:-(avgThres) / 2]
        return averagelist


    def find_file(self, name, path):
        for root, dirs, files in os.walk(path):
            if name in files:
                return os.path.join(root, name)


    def findFrame(self, initial_time):
        """Find the frame corresponding to the specified tdms time and return the adjusted tdms time to match the
        selected frame"""
        adjusted_time = self.dT + initial_time
        for f in range(len(self.frameTimeVector)):
            f += 1
            frame_time = sum(self.frameTimeVector[0:f])
            if adjusted_time &lt;= frame_time:
                timeDiff_up = frame_time - adjusted_time
                timeDiff_low = adjusted_time - sum(self.frameTimeVector[0:f - 1])
                if timeDiff_up &lt; timeDiff_low:
                    frame_frame = f
                    readjusted_time_tdms = frame_time - self.dT
                else:
                    frame_frame = f - 1
                    readjusted_time_tdms = sum(self.frameTimeVector[0:f - 1]) - self.dT
                break

        return frame_frame, int(readjusted_time_tdms)


    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 = sitk.ImageFileReader()
            reader.SetFileName(self.DCM_img)
            self.img_all = reader.Execute()
            self.deiconify()

        else:
            self.fig.clear()


        # Read in the appropriate dicom frame and set up plot title
        img = self.img_all[:, :, self.frames[self.mm]]
        self.nda = sitk.GetArrayFromImage(img)
        plt.imshow(self.nda[:, :, 0], cmap="gray")
        split_name = os.path.split(self.lstFilesTDMS[self.location])
        self.pltname = split_name[1][0:25] + '_' + str(self.frames[self.mm])
        plt.title('\n'+split_name[1][0:25] + '  :  Frame = ' + str(self.frames[self.mm]) + '\n\n' + str(self.mm + 1) + '  of  ' + str(
            len(self.DATA)) + '\n')

        # Create four circles used to indicate tissue interfaces (Top of skin, skin/fat, fat/muscle, bottom of muscle)
        r = 7
        if self.mm == 0:
            y = [60, 90, 160, 360]
        else:
            y = self.y_new

        circs = [matplotlib.patches.Circle((512, y[0]), radius=r, alpha=0.5, fc='r'),
                 matplotlib.patches.Circle((512, y[1]), radius=r, alpha=0.5, fc='r'),
                 matplotlib.patches.Circle((512, y[2]), radius=r, alpha=0.5, facecolor='r'),
                 matplotlib.patches.Circle((512, y[3]), radius=r, alpha=0.5, facecolor='r')]

        self.drs = []

        # Create main GUI

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


        # text = Text(root_plot)
        # text.insert(INSERT, "TEST...")
        # text.grid(row=1, column=0, sticky="EW")

        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)
            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="Save and Quit", command=self.end_program)
        self.btn2.grid(row=2, column=0, sticky='e')

        # Press enter when you are happy with circle locations to calculate and record skin, fat, and muscle thicknesses
        cid = self.fig.canvas.mpl_connect('key_press_event',
                                     lambda event: self.on_key(event))

        self.canvas.draw()

    def next_image(self):
        """Gets next image for analysis - Triggered by 'Next Image' button"""
        # global location, mm, selectedFrames, root_plot, y_new
        self.mm += 1

        if self.mm &lt; len(self.frames):
            self.y_new = []
            for p in self.drs:
                self.y_new.append(p.getlocation())
            self.fig.clear()
            self.showPlot()
        elif self.mm == len(self.frames) - 1:
            self.btn1.config(state="disabled")


    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"""
        # global app, root_plot
        #
        split_name = os.path.split(self.lstFilesTDMS[self.location])
        xml_name = os.path.dirname(os.path.dirname(self.lstFilesTDMS[self.location])) + '/Analysis/' + split_name[1][0:25] + '_manThick.xml'

        if os.path.exists(xml_name):
            self.prettyPrintXml(xml_name)

        self.destroy()
        self.quit()


    def on_exit(self):
        """Closes all windows if the user selects okay, does not pretty print the xml file"""
        self.destroy()
        app.quit()


    def on_key(self, event):
        """Enter key was pressed, locations of each point (4) are found and sent to be calculated and saved to xml file"""
        if event.key == 'enter':
            self.coords = []
            for dr in self.drs:
                coord = dr.getlocation()
                self.coords.append(int(coord))
            self.get_boxes()  # Calculate the skin, fat and muscle thicknesses


    def get_boxes(self):

        self.coords.sort()
        unit_pix = int(3/self.conv_fact)
        print(self.conv_fact, unit_pix)

        # try:
        #     self.df = pandas.read_csv('training.csv', index_col=0, parse_dates=True)
        # except:

        if self.coords[0]-unit_pix &gt; 0 and self.coords[3]+unit_pix &lt; 650:
            T_S = self.nda[(self.coords[0]-unit_pix):(self.coords[0]+unit_pix), 512-int(unit_pix/2):512 + int(unit_pix/2), 0]
            S_F = self.nda[(self.coords[1]-unit_pix):(self.coords[1]+unit_pix), 512-int(unit_pix/2):512 + int(unit_pix/2), 0]
            F_M = self.nda[(self.coords[2]-unit_pix):(self.coords[2]+unit_pix), 512-int(unit_pix/2):512 + int(unit_pix/2), 0]
            M_B = self.nda[(self.coords[3]-unit_pix):(self.coords[3]+unit_pix), 512-int(unit_pix/2):512 + int(unit_pix/2), 0]

            path = '/home/morrile2/Documents/Multis/app/UltrasoundThickness/TrainingData/'
            np.save(path + self.pltname + '_TS', np.array(T_S))
            np.save(path + self.pltname + '_SF', np.array(S_F))
            np.save(path + self.pltname + '_FM', np.array(F_M))
            np.save(path + self.pltname + '_MB', np.array(M_B))

        else:
            print("Data not saved because points are out of range of the image")

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