diff --git a/.gitignore b/.gitignore index bf1a354..553d27b 100644 --- a/.gitignore +++ b/.gitignore @@ -162,7 +162,7 @@ cython_debug/ tests/unit_tests/test_data/* !tests/unit_tests/test_data/.gitkeep tests/testing_framework.ipynb -/data +/src/ChartExtractor/data/ config.yaml /dist /.venv diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..e3dc41e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include src/ChartExtractor/data * \ No newline at end of file diff --git a/data/centroids/intraop_checkbox_centroids.json b/data/centroids/intraop_checkbox_centroids.json deleted file mode 100644 index d212a50..0000000 --- a/data/centroids/intraop_checkbox_centroids.json +++ /dev/null @@ -1 +0,0 @@ -{"nibp": [0.6640051736786033, 0.9419880870796785], "direct_laryngoscopy": [0.3745175389924118, 0.9203751713267544], "ventilation_w_adjunct": [0.12079679327908506, 0.942004126119335], "exhaled": [0.9793098082230612, 0.2934645839044226], "peripheral_iv_line": [0.5554011615953947, 0.9201863778097589], "dl_view_3": [0.5193791069748679, 0.9420278662966008], "lma_2_5": [0.3384455504105611, 0.9422331728572736], "video_laryngoscopy": [0.37457361443381193, 0.9422279131259138], "eye_protection": [0.019497888616777873, 0.9205752381441886], "lateral": [0.8362091580380284, 0.9863633183707967], "capnography": [0.6641874556107953, 0.9861761067708332], "fowler": [0.8361551959510815, 0.964258355034722], "dl_view_1": [0.4830675471304328, 0.9421592653508772], "ecg": [0.6639942535449063, 0.9202395547788742], "bronchoscope": [0.374580735202041, 0.9640837873492324], "sitting": [0.763657800896506, 0.986135959201389], "lma_n": [0.23891669071081917, 0.9526501165021931], "lma_2": [0.31128883617346365, 0.9419474683616592], "natural": [0.2389491449626819, 0.9198293728298613], "lma_5": [0.3384424811306943, 0.9640729366547881], "dl_view_2B": [0.48307233746542305, 0.9857786572551168], "trendelenburg": [0.835817783483478, 0.9200923451206142], "other_airway_device": [0.3746054948383921, 0.9860811974597956], "prone": [0.7635518450333931, 0.9420404245476971], "lma_4": [0.31128005957109117, 0.9639338792945907], "lithotomy": [0.7635700251383075, 0.9640273323282166], "desflurane": [0.14002809929125237, 0.29287284556606363], "warming": [0.01941187960489325, 0.9424072436951756], "dl_view_4": [0.519429349519039, 0.9640085263614765], "gastric_tube": [0.5554537306790147, 0.9860240971080044], "easy_ventilation": [0.12092031183805574, 0.9200412440606724], "sevoflurane": [0.10348839962121212, 0.29282387823807565], "difficult_ventilation": [0.12065055567301819, 0.9640881533260234], "ett_n": [0.23899093798283, 0.9855529485334431], "ted_stockings": [0.019442582685601388, 0.9643782751964545], "lma_3": [0.2841578165690104, 0.9636762181332238], "urinary_catheter": [0.5554447331040668, 0.9640256162052265], "reverse_trendelenburg": [0.8360488098241875, 0.942099186769006], "isoflurane": [0.020207430886689935, 0.2921123046875], "lma_1": [0.2840890352045329, 0.9418152383726242], "dl_view_2A": [0.48307642107755183, 0.9639145165158991], "inhaled": [0.9430393677731257, 0.2937332035533169], "supine": [0.7634042217248556, 0.9199488503974781], "central_iv_line": [0.5554198732026265, 0.9420639420001827], "temperature": [0.6641053647858105, 0.9640828279194078], "halothane": [0.05703608535693593, 0.29240771912691893], "safety_checklist": [0.019480006143426973, 0.9865548274168496]} \ No newline at end of file diff --git a/data/centroids/intraop_digit_box_centroids.json b/data/centroids/intraop_digit_box_centroids.json deleted file mode 100644 index 05be393..0000000 --- a/data/centroids/intraop_digit_box_centroids.json +++ /dev/null @@ -1 +0,0 @@ -{"anes_start_hr_tens": [0.13911949943773674, 0.013369123236338298], "anes_start_hr_ones": [0.15721902003432767, 0.013571323691474069], "anes_start_min_tens": [0.22090960878314397, 0.014005410692426893], "anes_start_min_ones": [0.23869317626953124, 0.014041139072842069], "surg_start_hr_tens": [0.38360502485795456, 0.014187573273976645], "surg_start_hr_ones": [0.40164046593868374, 0.014254151185353597], "surg_start_min_tens": [0.46472482207327176, 0.014369942707485622], "surg_start_min_ones": [0.482512909860322, 0.0143140039867825], "surg_end_hr_tens": [0.618216258655895, 0.014513668547736275], "surg_end_hr_ones": [0.6361968698212594, 0.01455810898674859], "surg_end_min_tens": [0.6994017999822443, 0.014602517795562747], "surg_end_min_ones": [0.7172081187855114, 0.014667471663157145], "anes_end_hr_tens": [0.8719075076941287, 0.01352246109644572], "anes_end_hr_ones": [0.8902006613991477, 0.0132419000837538], "anes_end_min_tens": [0.9542928059895835, 0.012451155911551581], "anes_end_min_ones": [0.9722854743726327, 0.012303776582082112], "code_row00_col0": [0.01965130476518111, 0.059872158304850265], "code_row00_col1": [0.03787677683974757, 0.05999438798692491], "code_row00_col2": [0.055829354026100855, 0.06012187550862631], "code_row01_col0": [0.019576300996722598, 0.08334075283474392], "code_row01_col1": [0.038005254803281846, 0.08347510681152344], "code_row01_col2": [0.055974067225600735, 0.08371321411132812], "code_row02_col0": [0.019536614013440683, 0.10675453321668837], "code_row02_col1": [0.03806926241787997, 0.10688449096679689], "code_row02_col2": [0.05606221609404592, 0.10713659328884548], "code_row03_col0": [0.019595429189277418, 0.13005185885959203], "code_row03_col1": [0.03808548597856001, 0.13024395480685763], "code_row03_col2": [0.0561968080925219, 0.13051276109483506], "code_row04_col0": [0.019555827487598765, 0.1535512668185764], "code_row04_col1": [0.038117367831143466, 0.15380257636176214], "code_row04_col2": [0.056229486638849434, 0.1540682359483507], "code_row05_col0": [0.019590035062847715, 0.1770147413465712], "code_row05_col1": [0.038207032637162644, 0.17725083482530385], "code_row05_col2": [0.05621500997832327, 0.1775667419433594], "code_row06_col0": [0.019714153174198036, 0.20053575371636284], "code_row06_col1": [0.038278043804746684, 0.20075292290581595], "code_row06_col2": [0.056275177926728225, 0.20098105061848956], "code_row07_col0": [0.019722325642903646, 0.22393027140299476], "code_row07_col1": [0.03830903128421668, 0.2241332722981771], "code_row07_col2": [0.056377909573641694, 0.22438831041124127], "code_row08_col0": [0.01980833833867853, 0.24715672336154512], "code_row08_col1": [0.03837091885191022, 0.24729718424479166], "code_row08_col2": [0.05638481648763022, 0.24747532552083334], "code_row09_col0": [0.020114752104788115, 0.33702040744357636], "code_row09_col1": [0.038588575998942054, 0.33714195692274307], "code_row09_col2": [0.0565352713844993, 0.33719212103949653], "code_row10_col0": [0.020117323441938915, 0.3594726671006945], "code_row10_col1": [0.03865392812093099, 0.3595256863064237], "code_row10_col2": [0.05656009396639737, 0.3595597601996528], "ett_n_whole": [0.29325402092211167, 0.9849094346788195], "ett_n_frac": [0.31133834931344695, 0.98497509765625]} \ No newline at end of file diff --git a/data/centroids/preop_postop_checkbox_centroids.json b/data/centroids/preop_postop_checkbox_centroids.json deleted file mode 100644 index 345fa63..0000000 --- a/data/centroids/preop_postop_checkbox_centroids.json +++ /dev/null @@ -1 +0,0 @@ -{"male": [0.7097751102721293, 0.06405002027366595], "surg_hist_1_pneumonia": [0.8100601142749453, 0.20666120883874728], "1_own_finger": [0.38268726793012364, 0.5872511293288559], "major": [0.19050992780134823, 0.5074000793813962], "mouth_swelling": [0.6273365348073665, 0.2875367181789108], "asa_6": [0.017422484925679237, 0.48789099478024495], "surg_hist_2_other": [0.8829118987209678, 0.26609813667877374], "emergent": [0.19073120214532246, 0.4255852129306012], "greater_than_3_own_finger": [0.12675341787140523, 0.5681244717425075], "plastic": [0.37379505504261357, 0.426826399596811], "surg_hist_1_non_surgical_site_infection": [0.7555846973279255, 0.186982292309142], "mallampati_score_1": [0.12669704114801386, 0.5485069201731543], "surg_hist_2_ponv": [0.9380086243551885, 0.24618467239469122], "mallampati_score_2": [0.2271348467131741, 0.548474580820541], "pain_manage_po": [0.8281112510279606, 0.7462134445815058], "surg_hist_1_ards": [0.8920186516428676, 0.1869320917854532], "asa_5": [0.01751553287536524, 0.46738927151864035], "smoking_vaping": [0.016772940512479205, 0.16735742116113853], "gastric_ulcer": [0.40083001290401954, 0.22719218078970213], "surg_hist_1_aki": [0.6916554116670405, 0.20678199312979717], "neuro": [0.27308988044896954, 0.5074180515579313], "aldrete_breathing_1": [0.30001964465663, 0.7660942911069993], "hypertension": [0.10893891646341083, 0.20757844338221854], "absent": [0.1266993355636962, 0.6278613167032165], "arv": [0.6364134126264702, 0.30817699766995615], "urologic": [0.3738045753588516, 0.4676057799936039], "hiv": [0.19980895314680525, 0.2660010615120158], "sedation": [0.7183695397384621, 0.4868598775584795], "pain_manage_other": [0.8282147946882477, 0.8063414385165387], "difficult_breathing": [0.7188452070561703, 0.2868945905005025], "videoscopic": [0.053134612915428446, 0.5073434601722404], "cardiothoracic": [0.27309775612571024, 0.4061896915547331], "surg_hist_1_surgical_site_infection": [0.6370159045741128, 0.18737735681366502], "epidural": [0.7184261379211524, 0.4263995140031066], "epilepsy": [0.01723417288188538, 0.2665699327256944], "tuberculosis": [0.10883883243542537, 0.16731042980171784], "anticoagulant": [0.1633350548751807, 0.3072164249531707], "npo": [0.8101735462146132, 0.3862115300050255], "years": [0.5373552220784115, 0.0632548614858884], "surg_hist_2_aki": [0.6914529521157297, 0.26654664085343566], "difficult_airway": [0.10893976068572757, 0.18709245041518185], "hives_itching": [0.545887028695674, 0.2880258032369334], "female": [0.764400262908692, 0.06396781537686175], "surg_hist_1_dvt": [0.5913692813855039, 0.2072478502061632], "ent": [0.27312286425625504, 0.4265843391641539], "ponv_prevention": [0.47329231517737, 0.4676865034493787], "aldrete_activity_0": [0.5185560892643541, 0.7255351919430738], "vascular": [0.3737728964578972, 0.4879994988692435], "surg_hist_1_ponv": [0.9381198971883723, 0.1865940972936084], "gynecologic": [0.2732011031496087, 0.46755460040889985], "arrhythmia": [0.1997998592005582, 0.1670149750291256], "less_than_3_own_finger": [0.31874068313428283, 0.5680081708584612], "diabetes": [0.40083833490642445, 0.16738581642909356], "pain_manage_iv": [0.8281206292208682, 0.7265258432131761], "asthma": [0.01682352829587897, 0.2075829424941749], "asa_1": [0.01694081475289815, 0.38651900656181476], "spinal": [0.7184883957274222, 0.40605815329746897], "transplant": [0.3737721352219772, 0.44729789510805007], "open": [0.052969322508792174, 0.4879411706757126], "other_pain_medication": [0.7369941480232006, 0.3073918564110471], "ace_arb": [0.24580574291174495, 0.3074608232821637], "3_own_fingers": [0.12683198165285153, 0.5877105934187682], "esophageal_reflux": [0.40092395837227124, 0.24713143117804282], "surg_hist_2_stroke": [0.636694575405577, 0.26697450015419405], "trauma": [0.05301018150609455, 0.4264880378232366], "blood_transfusion_consent": [0.4733341977546849, 0.4263673952336897], "ICU": [0.9199869227067137, 0.746690088461714], "non_communicable_disease": [0.05269991216096771, 0.38646943744860196], "infection": [0.05310197577712257, 0.44623051586485746], "aldrete_consciousness_2": [0.0986424763997396, 0.8064659131144919], "opioids": [0.6820556153901267, 0.3077886827256944], "surg_hist_1_cardiac_arrest": [0.7280200351064097, 0.20661997798451207], "copd": [0.016883951701235734, 0.2270901325069673], "cirrhosis": [0.017024149582906968, 0.2467289310923794], "cancer": [0.1996101109262859, 0.22662557001838904], "mallampati_score_4": [0.41916687595787233, 0.547935761033443], "booking_for_blood": [0.8101040498529704, 0.4057844338221857], "surg_hist_2_dvt": [0.5911415443237888, 0.26725240357159175], "valvulophathy": [0.1995887418074661, 0.207046830026727], "surg_hist_2_non_surgical_site_infection": [0.7555409642269736, 0.24653180538422875], "alcohol": [0.016845743979373426, 0.1871694146764209], "stroke": [0.30021814276346936, 0.20714628734923246], "hepatitis": [0.10890520914129471, 0.2664766702930829], "loose_teeth": [0.30048356002977966, 0.2465753680669773], "insulin": [0.5820595490913452, 0.3085571853013066], "thyroid_disease": [0.40074434173924695, 0.18702440906546966], "prosthetic_teeth": [0.3006508163135778, 0.2664223418654058], "aldrete_activity_2": [0.09933187506035372, 0.7266276527092469], "current_op_type_other": [0.3737913617829196, 0.507255428202668], "gastro_intestinal": [0.27317127039177763, 0.4470745285658809], "HDU": [0.9200597424180125, 0.766514804059302], "full": [0.12679975470097252, 0.6082998889231542], "heart_failure": [0.19967133103755486, 0.1865831777115314], "aldrete_consciousness_1": [0.29990513949112835, 0.8063175869768823], "local": [0.7184101188696171, 0.4672767155519006], "anesthesia_consent": [0.47332733981727226, 0.38745551072505485], "PACU": [0.9199238491514654, 0.726822875262701], "aldrete_airway_2": [0.0990323292125355, 0.7464424884639985], "aldrete_breathing_0": [0.5185392982689767, 0.7651288962559394], "beta_blocker": [0.30983090098014476, 0.3076941781741137], "aldrete_breathing_2": [0.09893247905530428, 0.7660714132743971], "abnormal_bleeding": [0.3002988741690652, 0.22677160287600512], "urgent": [0.19065451784757526, 0.4060416145547789], "block": [0.7184036201343202, 0.4468706761410361], "aspirin": [0.10884894412100028, 0.3074031739708973], "prior_conditions_other": [0.4010420287891248, 0.2669847062317252], "surg_hist_1_none": [0.5913730505741004, 0.1875013581214592], "normal": [0.2539818552312288, 0.6279372372989765], "unknown": [0.9378929885304526, 0.28633013808936403], "mallampati_score_3": [0.32796039100659147, 0.5482885028326024], "limited_flexion": [0.3825818873744642, 0.6076612570243969], "patient_age_gt_55": [0.12658167350805544, 0.6474893634639984], "renal_failure": [0.40077711966239277, 0.20758602851733823], "dvt_prophylaxis": [0.47326230087158594, 0.48715975505985026], "plan_for_procedure_other": [0.8100773189854964, 0.4664466017338267], "surgical_consent": [0.4733497135338792, 0.40694421815035636], "asa_2": [0.017308589290393785, 0.40616416957922147], "aldrete_airway_1": [0.300096702758205, 0.7463373138249269], "aldrete_bp_0": [0.5185677443508897, 0.7857233429847404], "surg_hist_2_cardiac_arrest": [0.7278757332006329, 0.2663927637289839], "aldrete_airway_0": [0.5185260243393017, 0.7452966879682931], "statin": [0.44633345949212516, 0.3084550402903417], "asa_4": [0.017637982954058728, 0.44696626933136885], "general": [0.7185274757494767, 0.3865880712319536], "surg_hist_2_pneumonia": [0.8101366350335177, 0.26617976316931646], "minor": [0.19057112990383895, 0.46746640710663373], "surg_hist_1_other": [0.8828343655770284, 0.20656168726870888], "aldrete_activity_1": [0.30007803446938547, 0.7266936020879019], "breast": [0.27296870424796893, 0.38659579949629935], "angina": [0.108902415833785, 0.24655331403051897], "snores": [0.38254244425650424, 0.6468134365862571], "2_own_fingers": [0.25412596682802535, 0.5877778919956139], "special_monitoring": [0.8100684333570075, 0.4261992751450567], "orthopedic": [0.3737260230154133, 0.4065608845314785], "has_beard": [0.2538931206272739, 0.6476732313368055], "transfusion": [0.3002884490334057, 0.1670298376250685], "surg_hist_2_ards": [0.8920204330517344, 0.24647923591122992], "pain_manage_block": [0.8280919572991426, 0.7659520070929279], "obstetric": [0.3736900677597314, 0.38694987193325114], "asa_3": [0.0176060896208792, 0.42650170327348325], "hepato-biliary": [0.27317725563353523, 0.48800280833104887], "oral_diabetic": [0.5007442414741579, 0.3084873932063232], "elective": [0.1905486014090847, 0.3864018983004386], "aldrete_consciousness_0": [0.5185246011591035, 0.805541017052723], "myocardial_infarction": [0.10889757877331602, 0.22706407156604078], "asa_emergency": [0.017387317766983545, 0.5072838427448831], "caesarean_section": [0.05276799738882452, 0.40608084409836437], "intermediate": [0.19048951801500824, 0.4880037534836439], "pain_manage_epidural_spinal": [0.8281895940801933, 0.7866171332465278], "deep_vein_thrombosis": [0.19963525361421577, 0.246279600979989], "limited_extension": [0.2539550742312101, 0.6082857344777961], "aldrete_bp_2": [0.09885294255647552, 0.7867095583196274], "ward": [0.9201430656274919, 0.7862714658146017], "surg_hist_2_surgical_site_infection": [0.6367374985982357, 0.2470623872098867], "surg_hist_2_none": [0.5911997545354865, 0.24739793993855083], "months": [0.5916235585189892, 0.06344459141067593], "prominent": [0.382547135299853, 0.6271841048748173], "surg_hist_1_stroke": [0.6369251422790819, 0.20701133076628744], "allergy_other": [0.8192627381441886, 0.2865202379728618], "aldrete_bp_1": [0.29991865045526184, 0.7866755056994702], "new_iv_access": [0.8100420042582487, 0.4467009177403143], "diuretic": [0.3918713680674965, 0.30823329706917024], "transfusion_reaction": [0.300250593608266, 0.18665035957202575]} \ No newline at end of file diff --git a/data/centroids/preop_postop_digit_box_centroids.json b/data/centroids/preop_postop_digit_box_centroids.json deleted file mode 100644 index c1e06e3..0000000 --- a/data/centroids/preop_postop_digit_box_centroids.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "time_of_assessment_day_tens": [ - 0.15468954144102154, - 0.042437080552842885 - ], - "time_of_assessment_day_ones": [ - 0.1726757609049479, - 0.04262437422010633 - ], - "time_of_assessment_month_tens": [ - 0.2368930691805753, - 0.04287110375298394 - ], - "time_of_assessment_month_ones": [ - 0.25493515199603456, - 0.04285875125461155 - ], - "time_of_assessment_year_tens": [ - 0.3188067349520597, - 0.04268514251708984 - ], - "time_of_assessment_year_ones": [ - 0.3367655343720407, - 0.042664884270562066 - ], - "time_of_assessment_hour_tens": [ - 0.4006649206912879, - 0.04238754492865669 - ], - "time_of_assessment_hour_ones": [ - 0.41850465901692707, - 0.04235902421739367 - ], - "time_of_assessment_min_tens": [ - 0.48237484648733425, - 0.04202077602810329 - ], - "time_of_assessment_min_ones": [ - 0.5003854462594696, - 0.04201409250895182 - ], - "mrn_0": [ - 0.043132897926099374, - 0.06288078308105469 - ], - "mrn_1": [ - 0.06150590561375473, - 0.0629414291381836 - ], - "mrn_2": [ - 0.08022945612127132, - 0.06310372467041016 - ], - "mrn_3": [ - 0.09938011539343633, - 0.06329738430447049 - ], - "mrn_4": [ - 0.11804346257990057, - 0.06351617516411676 - ], - "mrn_5": [ - 0.1365130966648911, - 0.06374136250813803 - ], - "mrn_6": [ - 0.1547234228885535, - 0.06384244944254558 - ], - "mrn_7": [ - 0.17304614905155066, - 0.06392825300428603 - ], - "mrn_8": [ - 0.1911018047910748, - 0.06404552764892578 - ], - "age_hundreds": [ - 0.4823733132102273, - 0.06343703138563368 - ], - "age_tens": [ - 0.5006270511511601, - 0.06337633192274304 - ], - "age_ones": [ - 0.5188975423177082, - 0.06344039543999566 - ], - "height_hundreds": [ - 0.8190626960523201, - 0.06391554870605468 - ], - "height_tens": [ - 0.8374046741832386, - 0.06380536431206596 - ], - "height_ones": [ - 0.855728837446733, - 0.06360227491590711 - ], - "weight_hundreds": [ - 0.9295284423828125, - 0.0627165010240343 - ], - "weight_tens": [ - 0.9478284246271308, - 0.06259974568684896 - ], - "weight_ones": [ - 0.9662317301432293, - 0.06252708672417534 - ], - "preop_sys_hundreds": [ - 0.6453529348662406, - 0.38691094021267364 - ], - "preop_sys_tens": [ - 0.6634829767400567, - 0.3868124810112847 - ], - "preop_sys_ones": [ - 0.6819208873401988, - 0.38675338270399306 - ], - "preop_dia_hundreds": [ - 0.6453008108428031, - 0.40639874267578124 - ], - "preop_dia_tens": [ - 0.6634539388020833, - 0.40634455973307293 - ], - "preop_dia_ones": [ - 0.6819633049242424, - 0.40622759467230907 - ], - "preop_hr_hundreds": [ - 0.6453158994732482, - 0.42635049913194445 - ], - "preop_hr_tens": [ - 0.6634481275153883, - 0.42625968831380207 - ], - "preop_hr_ones": [ - 0.6818428104285038, - 0.42624617784288193 - ], - "preop_rr_tens": [ - 0.6498018909801137, - 0.44655365261501734 - ], - "preop_rr_ones": [ - 0.6771458962180398, - 0.44653945583767357 - ], - "preop_ox_hundreds": [ - 0.6453209709398674, - 0.4669107096354167 - ], - "preop_ox_tens": [ - 0.6634457046046401, - 0.4668378228081598 - ], - "preop_ox_ones": [ - 0.6819136075106533, - 0.4667773166232639 - ], - "hgb_tens": [ - 0.5364240371241714, - 0.5476180881076389 - ], - "hgb_ones": [ - 0.5544196130001183, - 0.5477348849826389 - ], - "hgb_frac": [ - 0.5724961085464015, - 0.5477246853298612 - ], - "hct_tens": [ - 0.5364426620945786, - 0.5672394883897569 - ], - "hct_ones": [ - 0.5544710804332388, - 0.5673274197048611 - ], - "hct_frac": [ - 0.5725381950609612, - 0.567301247829861 - ], - "plt_hundreds": [ - 0.5363769845673533, - 0.5867337320963542 - ], - "plt_tens": [ - 0.5545034790039063, - 0.5867478488498264 - ], - "plt_ones": [ - 0.5725672385475853, - 0.5868329182942709 - ], - "na_hundreds": [ - 0.5364422866358901, - 0.6066811550564235 - ], - "na_tens": [ - 0.5545727650035512, - 0.6066547580295139 - ], - "na_ones": [ - 0.5726344438032671, - 0.606734836154514 - ], - "k_ones": [ - 0.5409838460286458, - 0.6266727376302084 - ], - "k_frac": [ - 0.5680798876213304, - 0.6267250298394097 - ], - "cl_hundreds": [ - 0.5365160300514915, - 0.6462582356770834 - ], - "cl_tens": [ - 0.5546106863310843, - 0.6463158013237849 - ], - "cl_ones": [ - 0.572778740160393, - 0.6464029215494792 - ], - "urea_tens": [ - 0.6360943270596591, - 0.5478733154296875 - ], - "urea_ones": [ - 0.6544080958510891, - 0.5479909993489583 - ], - "urea_frac": [ - 0.672474306048769, - 0.5480004991319444 - ], - "creatinine_tens": [ - 0.6361362563624526, - 0.5675699924045138 - ], - "creatinine_ones": [ - 0.6545011578184186, - 0.5676763210720486 - ], - "creatinine_frac": [ - 0.6725286384351327, - 0.5677245144314236 - ], - "ca_ones": [ - 0.6408305035215436, - 0.5871601155598959 - ], - "ca_frac": [ - 0.6679470473780776, - 0.5872530056423612 - ], - "mg_ones": [ - 0.6362913781368371, - 0.6070683485243056 - ], - "mg_tenths": [ - 0.6546053836706913, - 0.607212828233507 - ], - "mg_hundredths": [ - 0.6726405103278881, - 0.6072709960937499 - ], - "po4_ones": [ - 0.6363386748342803, - 0.627128138563368 - ], - "po4_tenths": [ - 0.6546668331261838, - 0.6272381184895834 - ], - "po4_hundredths": [ - 0.6726723706794508, - 0.6272917209201389 - ], - "albumin_tens": [ - 0.6410343535452179, - 0.6467655164930555 - ], - "albumin_ones": [ - 0.668089618104877, - 0.6469366455078125 - ], - "aldrete_tens": [ - 0.4098162194454309, - 0.7053829345703125 - ], - "aldrete_ones": [ - 0.42775323116418085, - 0.7053526394314236 - ], - "pacu_sys_hundreds": [ - 0.7556598011363637, - 0.7268235107421875 - ], - "pacu_sys_tens": [ - 0.7738790949041193, - 0.7267857042100695 - ], - "pacu_sys_ones": [ - 0.7921430479107482, - 0.7268016628689236 - ], - "pacu_dia_hundreds": [ - 0.7556770167495265, - 0.7465413140190973 - ], - "pacu_dia_tens": [ - 0.7738378425366952, - 0.7465599338107639 - ], - "pacu_dia_ones": [ - 0.7921358975497159, - 0.746550439453125 - ], - "pacu_hr_hundreds": [ - 0.7556921386718749, - 0.7662866427951389 - ], - "pacu_hr_tens": [ - 0.7738375207149621, - 0.7662563286675347 - ], - "pacu_hr_ones": [ - 0.7921045143821023, - 0.7662747395833333 - ], - "pacu_rr_tens": [ - 0.7602950217507102, - 0.7865857421875001 - ], - "pacu_rr_ones": [ - 0.7876730291193181, - 0.7863983289930555 - ], - "pacu_ox_hundreds": [ - 0.7556496840968276, - 0.8065984429253472 - ], - "pacu_ox_tens": [ - 0.7739150205669981, - 0.8065129611545139 - ], - "pacu_ox_ones": [ - 0.7921999437736742, - 0.8064880262586807 - ] -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ff75434..9a6d9a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,33 +1,34 @@ -[tool.poetry] +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] name = "ChartExtractor" -version = "0.1.0" -description = "ChartExtractor uses computer vision to convert images of paper charts to digital data." +version = "1.0.0a" +dependencies = [ + "numpy", + "onnxruntime", + "opencv-python", + "pillow", + "scikit-learn", + "scipy" +] authors = [ - "RyanDoesMath ", - "mattbeck1 ", - "hvalenty ", - "charbelmarche33 ", + {name = "Ryan Folks", email = "vcz2aj@uvahealth.org"}, + {name = "Charbel Marche", email = "qdy4zt@virginia.edu"}, + {name = "Hannah Valenty", email = "unc6kr@virginia.edu"}, + {name = "Matthew Beck", email = "mtb2tgk@virginia.edu"} ] -license = "GPL-3.0-or-later" +maintainers = [ + {name = "Ryan Folks", email = "vcz2aj@uvahealth.org"}, + {name = "Charbel Marche", email = "qdy4zt@virginia.edu"} +] +description = "A library for digitizing smartphone images of paper surgical records." readme = "README.md" -packages = [{include = "ChartExtractor"}] - +license = "GPL-3.0" +license-files = ["LICENSE"] +keywords = ["computer vision"] +classifiers = ["Development Status :: 3 - Alpha"] -[tool.poetry.dependencies] -python = "^3.12" -opencv-python-headless = "^4.10.0.84" -numpy = "^2.1.2" -pillow = "^10.4.0" -scikit-learn = "^1.5.2" -onnxruntime = "^1.20.1" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.6.9" -ultralytics = "^8.3.6" - -[tool.poetry.group.test.dependencies] -pytest = "^8.3.3" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[tool.setuptools.packages.find] +where = ["src"] \ No newline at end of file diff --git a/ChartExtractor/__init__.py b/src/ChartExtractor/__init__.py similarity index 100% rename from ChartExtractor/__init__.py rename to src/ChartExtractor/__init__.py diff --git a/ChartExtractor/extraction/__init__.py b/src/ChartExtractor/extraction/__init__.py similarity index 100% rename from ChartExtractor/extraction/__init__.py rename to src/ChartExtractor/extraction/__init__.py diff --git a/ChartExtractor/extraction/blood_pressure_and_heart_rate.py b/src/ChartExtractor/extraction/blood_pressure_and_heart_rate.py similarity index 100% rename from ChartExtractor/extraction/blood_pressure_and_heart_rate.py rename to src/ChartExtractor/extraction/blood_pressure_and_heart_rate.py diff --git a/ChartExtractor/extraction/checkboxes.py b/src/ChartExtractor/extraction/checkboxes.py similarity index 89% rename from ChartExtractor/extraction/checkboxes.py rename to src/ChartExtractor/extraction/checkboxes.py index da5479b..e9b18af 100644 --- a/ChartExtractor/extraction/checkboxes.py +++ b/src/ChartExtractor/extraction/checkboxes.py @@ -3,26 +3,17 @@ # Built-in Imports import json from pathlib import Path -from PIL import Image from typing import Dict, List, Literal, Tuple # Internal Imports from ..utilities.annotations import BoundingBox from ..utilities.detections import Detection -from ..utilities.detection_reassembly import ( - untile_detections, - non_maximum_suppression, - intersection_over_minimum, -) -from ..utilities.image_conversion import pil_to_cv2 -from ..utilities.tiling import tile_image -from ..object_detection_models.object_detection_model import ObjectDetectionModel # External Imports import numpy as np -DATA_FILEPATH: Path = Path(__file__).parents[2] / "data" +DATA_FILEPATH: Path = Path(__file__) / ".." / ".." / "data" FILEPATH_TO_INTRAOP_CENTROIDS: Path = ( DATA_FILEPATH / "centroids" / "intraop_checkbox_centroids.json" ) @@ -68,10 +59,7 @@ def extract_checkboxes( checkbox_bboxes: List[BoundingBox] = [det.annotation for det in detections] names: Dict[str, str] = find_checkbox_names( - checkbox_bboxes, - centroids, - image_width, - image_height + checkbox_bboxes, centroids, image_width, image_height ) return names diff --git a/ChartExtractor/extraction/drug_doses_and_fluids.py b/src/ChartExtractor/extraction/drug_doses_and_fluids.py similarity index 96% rename from ChartExtractor/extraction/drug_doses_and_fluids.py rename to src/ChartExtractor/extraction/drug_doses_and_fluids.py index b383a04..95f4774 100644 --- a/ChartExtractor/extraction/drug_doses_and_fluids.py +++ b/src/ChartExtractor/extraction/drug_doses_and_fluids.py @@ -8,7 +8,6 @@ from typing import Dict, List, Optional, Tuple # Internal imports -from ..extraction.extraction_utilities import average_with_nones, get_detection_by_name from ..label_clustering.cluster import Cluster from ..utilities.detections import Detection @@ -16,7 +15,7 @@ import numpy as np -DATA_FILEPATH: Path = Path(__file__).parents[2] / "data" +DATA_FILEPATH: Path = Path(__file__) / ".." / ".." / "data" FILEPATH_TO_NUMBER_BOX_CENTROIDS: Path = ( DATA_FILEPATH / "centroids" / "intraop_digit_box_centroids.json" ) diff --git a/ChartExtractor/extraction/extraction.py b/src/ChartExtractor/extraction/extraction.py similarity index 87% rename from ChartExtractor/extraction/extraction.py rename to src/ChartExtractor/extraction/extraction.py index 2de2bec..651c93d 100644 --- a/ChartExtractor/extraction/extraction.py +++ b/src/ChartExtractor/extraction/extraction.py @@ -1,13 +1,12 @@ """Consolidates all the functionality for extracting data from charts into one function.""" # Built-in imports -from functools import partial, reduce +from functools import reduce import json from operator import concat -import os from pathlib import Path from PIL import Image -from typing import Any, Dict, List, Literal, Tuple +from typing import Any, Dict, List, Tuple # Internal Imports from ..extraction.blood_pressure_and_heart_rate import ( @@ -41,64 +40,70 @@ ) from ..object_detection_models.onnx_yolov11_detection import OnnxYolov11Detection from ..object_detection_models.onnx_yolov11_pose_single import OnnxYolov11PoseSingle -from ..object_detection_models.object_detection_model import ObjectDetectionModel from ..point_registration.homography import ( find_homography, - transform_point, transform_box, transform_keypoint, ) -from ..utilities.annotations import BoundingBox, Keypoint +from ..utilities.annotations import BoundingBox from ..utilities.detections import Detection -from ..utilities.detection_reassembly import ( - untile_detections, - intersection_over_minimum, - non_maximum_suppression, -) -from ..utilities.image_conversion import pil_to_cv2 -from ..utilities.tiling import tile_image # External Imports import numpy as np -PATH_TO_DATA: Path = (Path(os.path.dirname(__file__)) / ".." / ".." / "data").resolve() +PATH_TO_DATA: Path = (Path(__file__) / ".." / ".." / "data").resolve() PATH_TO_MODELS: Path = PATH_TO_DATA / "models" PATH_TO_MODEL_METADATA = PATH_TO_DATA / "model_metadata" -MODEL_CONFIG: Dict = json.loads(open(str(PATH_TO_DATA/"config.json"), 'r').read()) +MODEL_CONFIG: Dict = json.loads(open(str(PATH_TO_DATA / "config.json"), "r").read()) INTRAOP_DOC_MODEL = OnnxYolov11Detection( PATH_TO_MODELS / MODEL_CONFIG["intraoperative_document_landmarks"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["intraoperative_document_landmarks"]["name"].replace(".onnx", ".json") + PATH_TO_MODEL_METADATA + / MODEL_CONFIG["intraoperative_document_landmarks"]["name"].replace( + ".onnx", ".json" + ), + MODEL_CONFIG["intraoperative_document_landmarks"]["imgsz"], + MODEL_CONFIG["intraoperative_document_landmarks"]["imgsz"], ) PREOP_POSTOP_DOC_MODEL = OnnxYolov11Detection( PATH_TO_MODELS / MODEL_CONFIG["preop_postop_document_landmarks"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["preop_postop_document_landmarks"]["name"].replace(".onnx", ".json"), + PATH_TO_MODEL_METADATA + / MODEL_CONFIG["preop_postop_document_landmarks"]["name"].replace(".onnx", ".json"), + MODEL_CONFIG["preop_postop_document_landmarks"]["imgsz"], + MODEL_CONFIG["preop_postop_document_landmarks"]["imgsz"], ) NUMBERS_MODEL = OnnxYolov11Detection( PATH_TO_MODELS / MODEL_CONFIG["numbers"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["numbers"]["name"].replace(".onnx", ".json") + PATH_TO_MODEL_METADATA / MODEL_CONFIG["numbers"]["name"].replace(".onnx", ".json"), + MODEL_CONFIG["numbers"]["imgsz"], + MODEL_CONFIG["numbers"]["imgsz"], ) SYSTOLIC_MODEL = OnnxYolov11PoseSingle( PATH_TO_MODELS / MODEL_CONFIG["systolic"]["name"], PATH_TO_MODEL_METADATA / MODEL_CONFIG["systolic"]["name"].replace(".onnx", ".json"), - 608, - 608, + MODEL_CONFIG["systolic"]["imgsz"], + MODEL_CONFIG["systolic"]["imgsz"], ) DIASTOLIC_MODEL = OnnxYolov11PoseSingle( PATH_TO_MODELS / MODEL_CONFIG["diastolic"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["diastolic"]["name"].replace(".onnx", ".json"), - 608, - 608, + PATH_TO_MODEL_METADATA + / MODEL_CONFIG["diastolic"]["name"].replace(".onnx", ".json"), + MODEL_CONFIG["diastolic"]["imgsz"], + MODEL_CONFIG["diastolic"]["imgsz"], ) HEART_RATE_MODEL = OnnxYolov11PoseSingle( PATH_TO_MODELS / MODEL_CONFIG["heart_rate"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["heart_rate"]["name"].replace(".onnx", ".json"), - 608, - 608, + PATH_TO_MODEL_METADATA + / MODEL_CONFIG["heart_rate"]["name"].replace(".onnx", ".json"), + MODEL_CONFIG["heart_rate"]["imgsz"], + MODEL_CONFIG["heart_rate"]["imgsz"], ) CHECKBOXES_MODEL = OnnxYolov11Detection( PATH_TO_MODELS / MODEL_CONFIG["checkboxes"]["name"], - PATH_TO_MODEL_METADATA / MODEL_CONFIG["checkboxes"]["name"].replace(".onnx", ".json"), + PATH_TO_MODEL_METADATA + / MODEL_CONFIG["checkboxes"]["name"].replace(".onnx", ".json"), + MODEL_CONFIG["checkboxes"]["imgsz"], + MODEL_CONFIG["checkboxes"]["imgsz"], ) @@ -123,11 +128,10 @@ def digitize_sheet(intraop_image: Image.Image, preop_postop_image: Image.Image) def run_models( - intraop_image: Image.Image, - preop_postop_image: Image.Image + intraop_image: Image.Image, preop_postop_image: Image.Image ) -> Dict[str, List[Detection]]: """Runs all the models and puts their output into a dictionary. - + Args: `intraop_image` (Image.Image): A smartphone photograph of the intraoperative side of the paper @@ -135,7 +139,7 @@ def run_models( `preop_postop_image` (Image.Image): A smartphone photograph of the preoperative/postoperative side of the paper anesthesia record. - + Returns: A dictionary containing all the detections on both images. The structure of the dictionary is set up as: @@ -165,11 +169,11 @@ def run_models( def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Detection]]: """Runs all the models on the preoperative/postoperative image and outputs to a dictionary. - + Args: `intraop_image` (Image.Image): A smartphone photograph of the intraoperative side of the paper anesthesia record. - + Returns: A dictionary containing all of the detections on the intraoperative image. The structure of the dictionary is set up as: @@ -186,8 +190,7 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete # landmarks landmark_tile_size: int = compute_tile_size( - MODEL_CONFIG["intraoperative_document_landmarks"], - intraop_image.size + MODEL_CONFIG["intraoperative_document_landmarks"], intraop_image.size ) detections_dict["landmarks"] = detect_objects_using_tiling( intraop_image, @@ -199,7 +202,9 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete ) # numbers - digit_tile_size: int = compute_tile_size(MODEL_CONFIG["numbers"], intraop_image.size) + digit_tile_size: int = compute_tile_size( + MODEL_CONFIG["numbers"], intraop_image.size + ) detections_dict["numbers"] = detect_objects_using_tiling( intraop_image, NUMBERS_MODEL, @@ -218,7 +223,7 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete tile_size, MODEL_CONFIG["checkboxes"]["horz_overlap_proportion"], MODEL_CONFIG["checkboxes"]["vert_overlap_proportion"], - nms_threshold=0.8 + nms_threshold=0.8, ) # systolic @@ -231,9 +236,11 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete MODEL_CONFIG["systolic"]["horz_overlap_proportion"], MODEL_CONFIG["systolic"]["vert_overlap_proportion"], ) - + # diastolic - dia_tile_size: int = compute_tile_size(MODEL_CONFIG["diastolic"], intraop_image.size) + dia_tile_size: int = compute_tile_size( + MODEL_CONFIG["diastolic"], intraop_image.size + ) detections_dict["diastolic"] = detect_objects_using_tiling( intraop_image.copy(), DIASTOLIC_MODEL, @@ -242,9 +249,11 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete MODEL_CONFIG["diastolic"]["horz_overlap_proportion"], MODEL_CONFIG["diastolic"]["vert_overlap_proportion"], ) - + # heart rate - hr_tile_size: int = compute_tile_size(MODEL_CONFIG["heart_rate"], intraop_image.size) + hr_tile_size: int = compute_tile_size( + MODEL_CONFIG["heart_rate"], intraop_image.size + ) detections_dict["heart_rate"] = detect_objects_using_tiling( intraop_image.copy(), HEART_RATE_MODEL, @@ -258,15 +267,15 @@ def run_intraoperative_models(intraop_image: Image.Image) -> Dict[str, List[Dete def run_preoperative_postoperative_models( - preop_postop_image: Image.Image + preop_postop_image: Image.Image, ) -> Dict[str, List[Detection]]: """Runs all the models on the preoperative/postoperative image and outputs to a dictionary. - + Args: `preop_postop_image` (Image.Image): A smartphone photograph of the preoperative/postoperative side of the paper anesthesia record. - + Returns: A dictionary containing all of the detections on the preoperative/postoperative image. The structure of the dictionary is set up as: @@ -277,7 +286,7 @@ def run_preoperative_postoperative_models( } """ detections_dict: Dict[str, List[Detection]] = dict() - + # landmarks landmark_tile_size: int = compute_tile_size( MODEL_CONFIG["preop_postop_document_landmarks"], @@ -293,7 +302,9 @@ def run_preoperative_postoperative_models( ) # numbers - digit_tile_size: int = compute_tile_size(MODEL_CONFIG["numbers"], preop_postop_image.size) + digit_tile_size: int = compute_tile_size( + MODEL_CONFIG["numbers"], preop_postop_image.size + ) detections_dict["numbers"] = detect_objects_using_tiling( preop_postop_image, NUMBERS_MODEL, @@ -312,15 +323,17 @@ def run_preoperative_postoperative_models( tile_size, MODEL_CONFIG["checkboxes"]["horz_overlap_proportion"], MODEL_CONFIG["checkboxes"]["vert_overlap_proportion"], - nms_threshold=0.8 + nms_threshold=0.8, ) return detections_dict -def assign_meaning_to_detections(detections_dict: Dict[str, List[Detection]]) -> Dict[str, Any]: +def assign_meaning_to_detections( + detections_dict: Dict[str, List[Detection]], +) -> Dict[str, Any]: """Imputes values to the detections to get the data encoded by the provider onto the chart. - + Examples of assigning meaning include getting mmHg and timestamp values for blood pressure markers, assigning meaning to checked/unchecked checkbox detections, etc. @@ -337,18 +350,20 @@ def assign_meaning_to_detections(detections_dict: Dict[str, List[Detection]]) -> data["intraoperative"] = assign_meaning_to_intraoperative_detections( detections_dict["intraoperative"] ) - data["preoperative_postoperative"] = assign_meaning_to_preoperative_postoperative_detections( - detections_dict["preoperative_postoperative"] + data["preoperative_postoperative"] = ( + assign_meaning_to_preoperative_postoperative_detections( + detections_dict["preoperative_postoperative"] + ) ) return data def assign_meaning_to_intraoperative_detections( intraop_detections_dict: Dict[str, List[Detection]], - image_size: Tuple[int, int] = (3300, 2550) + image_size: Tuple[int, int] = (3300, 2550), ) -> Dict[str, Any]: """Imputes values to the detections on the intraoperative side of the chart. - + Args: intraop_detections_dict (Dict[str, List[Detection]]): The detections from all models on the intraoperative side of the chart. @@ -362,7 +377,7 @@ def assign_meaning_to_intraoperative_detections( """ h = create_intraoperative_homography_matrix(intraop_detections_dict["landmarks"]) corrected_detections_dict: Dict[str, List[Detection]] = dict() - for (key, detections) in intraop_detections_dict.items(): + for key, detections in intraop_detections_dict.items(): if len(detections) == 0: continue remap_func = ( @@ -371,23 +386,21 @@ def assign_meaning_to_intraoperative_detections( else transform_keypoint ) corrected_detections_dict[key] = [ - Detection(remap_func(det.annotation, h), det.confidence) for det in detections + Detection(remap_func(det.annotation, h), det.confidence) + for det in detections ] - + extracted_data: Dict[str, Any] = dict() # extract drug code and surgical timing extracted_data["codes"] = extract_drug_codes( - corrected_detections_dict["numbers"], - *image_size + corrected_detections_dict["numbers"], *image_size ) extracted_data["timing"] = extract_surgical_timing( - corrected_detections_dict["numbers"], - *image_size + corrected_detections_dict["numbers"], *image_size ) extracted_data["ett_size"] = extract_ett_size( - corrected_detections_dict["numbers"], - *image_size + corrected_detections_dict["numbers"], *image_size ) # extract inhaled volatile drugs @@ -407,7 +420,7 @@ def assign_meaning_to_intraoperative_detections( extracted_data["inhaled_volatile"] = extract_inhaled_volatile( corrected_detections_dict["numbers"], legend_locations, - corrected_detections_dict["landmarks"] + corrected_detections_dict["landmarks"], ) # extract bp and hr @@ -418,9 +431,9 @@ def assign_meaning_to_intraoperative_detections( corrected_detections_dict["diastolic"], corrected_detections_dict["heart_rate"], ], - list() + list(), ) - + extracted_data["bp_and_hr"] = extract_heart_rate_and_blood_pressure( bp_and_hr_dets, time_clusters, @@ -448,10 +461,10 @@ def assign_meaning_to_intraoperative_detections( def assign_meaning_to_preoperative_postoperative_detections( preop_postop_detections_dict: Dict[str, List[Detection]], - image_size: Tuple[int, int] = (3300, 2550) + image_size: Tuple[int, int] = (3300, 2550), ) -> Dict[str, Any]: """Imputes values to the detections on the preoperative/postoperative side of the chart. - + Args: intraop_detections_dict (Dict[str, List[Detection]]): The detections from all models on the preoperative/postoperative side of the chart. @@ -465,7 +478,7 @@ def assign_meaning_to_preoperative_postoperative_detections( preop_postop_detections_dict["landmarks"] ) corrected_detections_dict: Dict[str, List[Detection]] = dict() - for (key, detections) in preop_postop_detections_dict.items(): + for key, detections in preop_postop_detections_dict.items(): if len(detections) == 0: continue remap_func = ( @@ -474,21 +487,19 @@ def assign_meaning_to_preoperative_postoperative_detections( else transform_keypoint ) corrected_detections_dict[key] = [ - Detection(remap_func(det.annotation, h), det.confidence) for det in detections + Detection(remap_func(det.annotation, h), det.confidence) + for det in detections ] extracted_data: Dict[str, Any] = dict() extracted_data.update( extract_preop_postop_digit_data( - corrected_detections_dict["numbers"], - *image_size + corrected_detections_dict["numbers"], *image_size ) ) extracted_data["checkboxes"] = extract_checkboxes( - corrected_detections_dict["checkboxes"], - "preoperative", - *image_size + corrected_detections_dict["checkboxes"], "preoperative", *image_size ) return extracted_data @@ -506,16 +517,21 @@ def digitize_intraop_record(image: Image.Image) -> Dict: the paper anesthesia record. """ landmark_tile_size: int = compute_tile_size( - MODEL_CONFIG["intraoperative_document_landmarks"], - image.size - ) - uncorrected_document_landmark_detections: List[Detection] = detect_objects_using_tiling( - image, - INTRAOP_DOC_MODEL, - landmark_tile_size, - landmark_tile_size, - MODEL_CONFIG["intraoperative_document_landmarks"]["horz_overlap_proportion"], - MODEL_CONFIG["intraoperative_document_landmarks"]["vert_overlap_proportion"], + MODEL_CONFIG["intraoperative_document_landmarks"], image.size + ) + uncorrected_document_landmark_detections: List[Detection] = ( + detect_objects_using_tiling( + image, + INTRAOP_DOC_MODEL, + landmark_tile_size, + landmark_tile_size, + MODEL_CONFIG["intraoperative_document_landmarks"][ + "horz_overlap_proportion" + ], + MODEL_CONFIG["intraoperative_document_landmarks"][ + "vert_overlap_proportion" + ], + ) ) image: Image.Image = homography_intraoperative_chart( image, @@ -623,7 +639,9 @@ def digitize_preop_postop_record(image: Image.Image) -> Dict: MODEL_CONFIG["preop_postop_document_landmarks"]["horz_overlap_proportion"], MODEL_CONFIG["preop_postop_document_landmarks"]["vert_overlap_proportion"], ) - image: Image.Image = homography_preoperative_chart(image, document_landmark_detections) + image: Image.Image = homography_preoperative_chart( + image, document_landmark_detections + ) digit_tile_size: int = compute_tile_size(MODEL_CONFIG["numbers"], image.size) digit_detections: List[Detection] = detect_objects_using_tiling( image, @@ -643,10 +661,10 @@ def digitize_preop_postop_record(image: Image.Image) -> Dict: def create_homography_matrix( landmark_detections: List[Detection], corner_landmark_names: List[str], - destination_landmarks: List[BoundingBox] + destination_landmarks: List[BoundingBox], ) -> np.ndarray: """Creates a homography matrix from the corner landmarks. - + Args: landmark_detections (List[Detection]): The list of detected landmarks. @@ -662,7 +680,11 @@ def create_homography_matrix( dest_points = [ bb.center for bb in sorted( - list(filter(lambda x: x.category in corner_landmark_names, destination_landmarks)), + list( + filter( + lambda x: x.category in corner_landmark_names, destination_landmarks + ) + ), key=lambda bb: bb.category, ) ] @@ -681,9 +703,11 @@ def create_homography_matrix( return find_homography(src_points, dest_points) -def create_intraoperative_homography_matrix(landmark_detections: List[Detection]) -> np.ndarray: +def create_intraoperative_homography_matrix( + landmark_detections: List[Detection], +) -> np.ndarray: """Creates a homography matrix for the intraoperative side of the chart. - + Args: landmark_detections (List[Detection]): The list of detected landmarks. @@ -701,14 +725,16 @@ def create_intraoperative_homography_matrix(landmark_detections: List[Detection] dst_landmarks: List[BoundingBox] = label_studio_to_bboxes( str(PATH_TO_DATA / "intraop_document_landmarks.json") )["unified_intraoperative_preoperative_flowsheet_v1_1_front.png"] - return create_homography_matrix(landmark_detections, corner_landmark_names, dst_landmarks) + return create_homography_matrix( + landmark_detections, corner_landmark_names, dst_landmarks + ) def create_preoperative_postoperative_homography_matrix( - landmark_detections: List[Detection] + landmark_detections: List[Detection], ) -> np.ndarray: """Creates a homography matrix for the intraoperative side of the chart. - + Args: landmark_detections (List[Detection]): The list of detected landmarks. @@ -726,7 +752,9 @@ def create_preoperative_postoperative_homography_matrix( dst_landmarks: List[BoundingBox] = label_studio_to_bboxes( str(PATH_TO_DATA / "preoperative_document_landmarks.json") )["unified_intraoperative_preoperative_flowsheet_v1_1_back.png"] - return create_homography_matrix(landmark_detections, corner_landmark_names, dst_landmarks) + return create_homography_matrix( + landmark_detections, corner_landmark_names, dst_landmarks + ) def homography_intraoperative_chart( @@ -835,7 +863,7 @@ def homography_preoperative_chart( def compute_tile_size(model_config: Dict, image_size: Tuple[int, int]) -> int: """Finds the tile size for a model based on how its training dataset was generated. - + Args: model_config (Dict): The model's config dictionary. @@ -872,7 +900,7 @@ def make_bp_and_hr_detections( sys_tile_size: int = compute_tile_size(MODEL_CONFIG["systolic"], image.size) dia_tile_size: int = compute_tile_size(MODEL_CONFIG["diastolic"], image.size) hr_tile_size: int = compute_tile_size(MODEL_CONFIG["heart_rate"], image.size) - + sys_dets: List[Detection] = detect_objects_using_tiling( image.copy(), SYSTOLIC_MODEL, @@ -923,7 +951,7 @@ def make_intraop_checkbox_detections(image: Image.Image) -> Dict: tile_size, MODEL_CONFIG["checkboxes"]["horz_overlap_proportion"], MODEL_CONFIG["checkboxes"]["vert_overlap_proportion"], - nms_threshold=0.8 + nms_threshold=0.8, ) intraop_checkboxes = extract_checkboxes( detections, @@ -952,7 +980,7 @@ def make_preop_postop_checkbox_detections(image: Image.Image): tile_size, MODEL_CONFIG["checkboxes"]["horz_overlap_proportion"], MODEL_CONFIG["checkboxes"]["vert_overlap_proportion"], - nms_threshold=0.8 + nms_threshold=0.8, ) preop_postop_checkboxes = extract_checkboxes( detections, diff --git a/ChartExtractor/extraction/extraction_utilities.py b/src/ChartExtractor/extraction/extraction_utilities.py similarity index 100% rename from ChartExtractor/extraction/extraction_utilities.py rename to src/ChartExtractor/extraction/extraction_utilities.py diff --git a/ChartExtractor/extraction/inhaled_volatile.py b/src/ChartExtractor/extraction/inhaled_volatile.py similarity index 100% rename from ChartExtractor/extraction/inhaled_volatile.py rename to src/ChartExtractor/extraction/inhaled_volatile.py diff --git a/ChartExtractor/extraction/intraoperative_digit_boxes.py b/src/ChartExtractor/extraction/intraoperative_digit_boxes.py similarity index 98% rename from ChartExtractor/extraction/intraoperative_digit_boxes.py rename to src/ChartExtractor/extraction/intraoperative_digit_boxes.py index 8984bb0..42545eb 100644 --- a/ChartExtractor/extraction/intraoperative_digit_boxes.py +++ b/src/ChartExtractor/extraction/intraoperative_digit_boxes.py @@ -12,7 +12,7 @@ from ..utilities.detections import Detection -DATA_FILEPATH: Path = Path(__file__).parents[2] / "data" +DATA_FILEPATH: Path = Path(__file__) / ".." / ".." / "data" FILEPATH_TO_NUMBER_BOX_CENTROIDS: Path = ( DATA_FILEPATH / "centroids" / "intraop_digit_box_centroids.json" ) diff --git a/ChartExtractor/extraction/physiological_indicators.py b/src/ChartExtractor/extraction/physiological_indicators.py similarity index 100% rename from ChartExtractor/extraction/physiological_indicators.py rename to src/ChartExtractor/extraction/physiological_indicators.py diff --git a/ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py b/src/ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py similarity index 99% rename from ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py rename to src/ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py index a897182..76ad96f 100644 --- a/ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py +++ b/src/ChartExtractor/extraction/preoperative_postoperative_digit_boxes.py @@ -11,7 +11,7 @@ from ..utilities.detections import Detection -DATA_FILEPATH: Path = Path(__file__).parents[2] / "data" +DATA_FILEPATH: Path = Path(__file__) / ".." / ".." / "data" FILEPATH_TO_NUMBER_BOX_CENTROIDS: Path = ( DATA_FILEPATH / "centroids" / "preop_postop_digit_box_centroids.json" ) diff --git a/ChartExtractor/image_registration/__init__.py b/src/ChartExtractor/image_registration/__init__.py similarity index 100% rename from ChartExtractor/image_registration/__init__.py rename to src/ChartExtractor/image_registration/__init__.py diff --git a/ChartExtractor/image_registration/homography.py b/src/ChartExtractor/image_registration/homography.py similarity index 100% rename from ChartExtractor/image_registration/homography.py rename to src/ChartExtractor/image_registration/homography.py diff --git a/ChartExtractor/label_clustering/__init__.py b/src/ChartExtractor/label_clustering/__init__.py similarity index 100% rename from ChartExtractor/label_clustering/__init__.py rename to src/ChartExtractor/label_clustering/__init__.py diff --git a/ChartExtractor/label_clustering/cluster.py b/src/ChartExtractor/label_clustering/cluster.py similarity index 100% rename from ChartExtractor/label_clustering/cluster.py rename to src/ChartExtractor/label_clustering/cluster.py diff --git a/ChartExtractor/label_clustering/clustering_methods.py b/src/ChartExtractor/label_clustering/clustering_methods.py similarity index 100% rename from ChartExtractor/label_clustering/clustering_methods.py rename to src/ChartExtractor/label_clustering/clustering_methods.py diff --git a/ChartExtractor/label_clustering/isolate_labels.py b/src/ChartExtractor/label_clustering/isolate_labels.py similarity index 100% rename from ChartExtractor/label_clustering/isolate_labels.py rename to src/ChartExtractor/label_clustering/isolate_labels.py diff --git a/ChartExtractor/object_detection_models/__init__.py b/src/ChartExtractor/object_detection_models/__init__.py similarity index 100% rename from ChartExtractor/object_detection_models/__init__.py rename to src/ChartExtractor/object_detection_models/__init__.py diff --git a/ChartExtractor/object_detection_models/object_detection_model.py b/src/ChartExtractor/object_detection_models/object_detection_model.py similarity index 100% rename from ChartExtractor/object_detection_models/object_detection_model.py rename to src/ChartExtractor/object_detection_models/object_detection_model.py diff --git a/ChartExtractor/object_detection_models/onnx_yolov11_detection.py b/src/ChartExtractor/object_detection_models/onnx_yolov11_detection.py similarity index 94% rename from ChartExtractor/object_detection_models/onnx_yolov11_detection.py rename to src/ChartExtractor/object_detection_models/onnx_yolov11_detection.py index 0664083..5e49462 100644 --- a/ChartExtractor/object_detection_models/onnx_yolov11_detection.py +++ b/src/ChartExtractor/object_detection_models/onnx_yolov11_detection.py @@ -71,11 +71,11 @@ def __init__( @staticmethod def load_classes(model_metadata_filepath: Path) -> Dict: """Loads the classes from a yaml file into a list. - + Args: model_metadata_filepath (Path): The path to the model metadata. - + Raises: Exception: Any exception relating to loading a file. @@ -87,7 +87,9 @@ def load_classes(model_metadata_filepath: Path) -> Dict: potential_err_msg += "yaml file. Ensure the model metadata filepath is " potential_err_msg += "correct and the model's yaml file is correctly formatted." try: - classes: Dict[str, str] = json.loads(open(model_metadata_filepath, 'r').read()) + classes: Dict[str, str] = json.loads( + open(model_metadata_filepath, "r").read() + ) except FileNotFoundError as e: print(potential_err_msg) print(e) @@ -97,7 +99,7 @@ def __call__( self, images: List[np.array], confidence: float = 0.5, - iou_threshold: float = 0.1 + iou_threshold: float = 0.1, ) -> List[List[Detection]]: """Runs the model on a list of images. @@ -148,17 +150,19 @@ def detect( image: np.array = np.expand_dims(image, axis=0) pred_results = self.model.run(None, {"images": image}) detections = self.postprocess_results(pred_results, confidence, iou_threshold) - detections = self.scale_detections_back_to_input_size(detections, original_im_width, original_im_height) + detections = self.scale_detections_back_to_input_size( + detections, original_im_width, original_im_height + ) detections = [ Detection( BoundingBox( - str(self.classes[int(d[5].item())]), + str(self.classes[str(int(d[5].item()))]), d[0].item(), d[1].item(), d[2].item(), d[3].item(), - ), - d[4].item() + ), + d[4].item(), ) for d in detections ] @@ -182,12 +186,14 @@ def preprocess_image( A preprocessed image. """ if resize_method == "letterbox": - image, _ = self.letterbox(image, (self.input_im_width, self.input_im_height)) + image, _ = self.letterbox( + image, (self.input_im_width, self.input_im_height) + ) else: image: np.array = cv2.resize( - image, - (self.input_im_width, self.input_im_height), - interpolation=cv2.INTER_LINEAR, + image, + (self.input_im_width, self.input_im_height), + interpolation=cv2.INTER_LINEAR, ) image: np.array = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = image.astype(np.float32) @@ -334,7 +340,7 @@ def non_max_suppression( sort_index = np.flip(predictions[:, index_of_confidence].argsort()) predictions = predictions[sort_index] - boxes = predictions[:, indexes_of_box[0]:indexes_of_box[1]+1] + boxes = predictions[:, indexes_of_box[0] : indexes_of_box[1] + 1] categories = predictions[:, index_of_category] ious = self.batch_iou(boxes, boxes) ious = ious - np.eye(rows) @@ -349,7 +355,7 @@ def non_max_suppression( keep = keep & ~condition return keep[sort_index.argsort()] - + def scale_detections_back_to_input_size( self, detections: np.ndarray, @@ -357,7 +363,7 @@ def scale_detections_back_to_input_size( original_im_height: int, ) -> np.ndarray: """Scales the detections back to the original image's size. - + Currently only works with when scale_method is "resize". Args: @@ -370,16 +376,16 @@ def scale_detections_back_to_input_size( Returns: The detections which have been rescaled to their original image. """ - width_scalar: np.float32 = original_im_width/self.input_im_width - height_scalar: np.float32 = original_im_height/self.input_im_height - + width_scalar: np.float32 = original_im_width / self.input_im_width + height_scalar: np.float32 = original_im_height / self.input_im_height + detections[:, 0] *= width_scalar detections[:, 1] *= height_scalar detections[:, 2] *= width_scalar detections[:, 3] *= height_scalar return detections - + @staticmethod def draw_detections( image: np.ndarray, @@ -417,7 +423,6 @@ def get_color(category: int) -> Tuple[int, int, int]: mask_img = image.copy() - # Draw bounding boxes, masks, and text annotations for detection in detections: category = detection.annotation.category diff --git a/ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py b/src/ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py similarity index 93% rename from ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py rename to src/ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py index d9777e9..9611f72 100644 --- a/ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py +++ b/src/ChartExtractor/object_detection_models/onnx_yolov11_pose_single.py @@ -37,7 +37,7 @@ class OnnxYolov11PoseSingle(ObjectDetectionModel): """Provides a wrapper for a yolov11 pose ONNX model. - + This class inherits from the `ObjectDetectionModel` interface, enabling us to use the onnx model within our program through a consistent interface. @@ -75,11 +75,11 @@ def __init__( @staticmethod def load_classes(model_metadata_filepath: Path) -> Dict: """Loads the classes from a yaml file into a list. - + Args: model_metadata_filepath (Path): The path to the model metadata. - + Raises: Exception: Any exception relating to loading a file. @@ -91,7 +91,9 @@ def load_classes(model_metadata_filepath: Path) -> Dict: potential_err_msg += "yaml file. Ensure the model metadata filepath is " potential_err_msg += "correct and the model's yaml file is correctly formatted." try: - classes: Dict[str, str] = json.loads(open(model_metadata_filepath, 'r').read()) + classes: Dict[str, str] = json.loads( + open(model_metadata_filepath, "r").read() + ) except FileNotFoundError as e: print(potential_err_msg) print(e) @@ -101,7 +103,7 @@ def __call__( self, images: List[np.array], confidence: float = 0.5, - iou_threshold: float = 0.1 + iou_threshold: float = 0.1, ) -> List[List[Detection]]: """Runs the model on a list of images. @@ -152,20 +154,22 @@ def detect( image: np.array = np.expand_dims(image, axis=0) pred_results = self.model.run(None, {"images": image}) detections = self.postprocess_results(pred_results, confidence, iou_threshold) - detections = self.scale_detections_back_to_input_size(detections, original_im_width, original_im_height) + detections = self.scale_detections_back_to_input_size( + detections, original_im_width, original_im_height + ) return [ Detection( Keypoint( Point(d[4].item(), d[5].item()), BoundingBox( - str(self.classes[int(d[7].item())]), + str(self.classes[str(int(d[7].item()))]), d[0].item(), d[1].item(), d[2].item(), d[3].item(), ), ), - d[6].item() + d[6].item(), ) for d in detections ] @@ -188,12 +192,14 @@ def preprocess_image( A preprocessed image. """ if resize_method == "letterbox": - image, _ = self.letterbox(image, (self.input_im_width, self.input_im_height)) + image, _ = self.letterbox( + image, (self.input_im_width, self.input_im_height) + ) else: image: np.array = cv2.resize( - image, - (self.input_im_width, self.input_im_height), - interpolation=cv2.INTER_LINEAR, + image, + (self.input_im_width, self.input_im_height), + interpolation=cv2.INTER_LINEAR, ) image: np.array = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = image.astype(np.float32) @@ -243,12 +249,14 @@ def postprocess_results( y2 = y + (h / 2) kpx = filtered_output[5, :] kpy = filtered_output[6, :] - - predictions = np.stack((x1, y1, x2, y2, kpx, kpy, confidences, class_indices), axis=1) + + predictions = np.stack( + (x1, y1, x2, y2, kpx, kpy, confidences, class_indices), axis=1 + ) predictions = predictions[self.keypoint_not_in_box(predictions)] predictions = predictions[self.non_max_suppression(predictions, iou_threshold)] return predictions - + @staticmethod def letterbox( image: np.ndarray, @@ -307,7 +315,7 @@ def batch_iou(self, these_boxes: np.ndarray, those_boxes: np.ndarray) -> np.ndar these_areas = box_area(these_boxes.T) those_areas = box_area(those_boxes.T) - + top_left = np.maximum(these_boxes[:, None, :2], those_boxes[:, :2]) bottom_right = np.minimum(these_boxes[:, None, 2:], those_boxes[:, 2:]) @@ -319,9 +327,7 @@ def batch_iou(self, these_boxes: np.ndarray, those_boxes: np.ndarray) -> np.ndar these_areas[:, None] + those_areas - intersection_areas ) - def non_max_suppression( - self, predictions: np.ndarray, iou_threshold - ) -> np.ndarray: + def non_max_suppression(self, predictions: np.ndarray, iou_threshold) -> np.ndarray: """Performs non-maximum suppression on a list of predicted boxes. Args: @@ -344,7 +350,7 @@ def non_max_suppression( sort_index = np.flip(predictions[:, index_of_confidence].argsort()) predictions = predictions[sort_index] - boxes = predictions[:, indexes_of_box[0]:indexes_of_box[1]+1] + boxes = predictions[:, indexes_of_box[0] : indexes_of_box[1] + 1] categories = predictions[:, index_of_category] ious = self.batch_iou(boxes, boxes) ious = ious - np.eye(rows) @@ -357,12 +363,12 @@ def non_max_suppression( condition = (iou > iou_threshold) & (categories == category) keep = keep & ~condition - + return keep[sort_index.argsort()] - + def keypoint_not_in_box(self, predictions: np.ndarray) -> np.ndarray: """Generates a mask that can filter keypoints that aren't in the box. - + Args: predictions (np.ndarray): A numpy ndarray of bounding boxes in the format @@ -377,11 +383,11 @@ def keypoint_not_in_box(self, predictions: np.ndarray) -> np.ndarray: y2 = predictions[:, 3] kpx = predictions[:, 4] kpy = predictions[:, 5] - kpx_in_bounds = np.logical_and(x1 np.ndarray: """Scales the detections back to the original image's size. - + Currently only works with when scale_method is "resize". Args: @@ -403,9 +409,9 @@ def scale_detections_back_to_input_size( Returns: The detections which have been rescaled to their original image. """ - width_scalar: np.float32 = original_im_width/self.input_im_width - height_scalar: np.float32 = original_im_height/self.input_im_height - # Rescale bounding box. + width_scalar: np.float32 = original_im_width / self.input_im_width + height_scalar: np.float32 = original_im_height / self.input_im_height + # Rescale bounding box. detections[:, 0] *= width_scalar detections[:, 1] *= height_scalar detections[:, 2] *= width_scalar @@ -416,7 +422,6 @@ def scale_detections_back_to_input_size( return detections - @staticmethod def draw_detections( image: np.ndarray, @@ -454,7 +459,6 @@ def get_color(category: int) -> Tuple[int, int, int]: mask_img = image.copy() - # Draw bounding boxes, masks, and text annotations for detection in detections: category = detection.annotation.category diff --git a/ChartExtractor/object_detection_models/ultralytics_yolov11_pose.py b/src/ChartExtractor/object_detection_models/ultralytics_yolov11_pose.py similarity index 100% rename from ChartExtractor/object_detection_models/ultralytics_yolov11_pose.py rename to src/ChartExtractor/object_detection_models/ultralytics_yolov11_pose.py diff --git a/ChartExtractor/object_detection_models/ultralytics_yolov8.py b/src/ChartExtractor/object_detection_models/ultralytics_yolov8.py similarity index 100% rename from ChartExtractor/object_detection_models/ultralytics_yolov8.py rename to src/ChartExtractor/object_detection_models/ultralytics_yolov8.py diff --git a/ChartExtractor/point_registration/__init__.py b/src/ChartExtractor/point_registration/__init__.py similarity index 100% rename from ChartExtractor/point_registration/__init__.py rename to src/ChartExtractor/point_registration/__init__.py diff --git a/ChartExtractor/point_registration/homography.py b/src/ChartExtractor/point_registration/homography.py similarity index 100% rename from ChartExtractor/point_registration/homography.py rename to src/ChartExtractor/point_registration/homography.py diff --git a/ChartExtractor/utilities/__init__.py b/src/ChartExtractor/utilities/__init__.py similarity index 100% rename from ChartExtractor/utilities/__init__.py rename to src/ChartExtractor/utilities/__init__.py diff --git a/ChartExtractor/utilities/annotations.py b/src/ChartExtractor/utilities/annotations.py similarity index 100% rename from ChartExtractor/utilities/annotations.py rename to src/ChartExtractor/utilities/annotations.py diff --git a/ChartExtractor/utilities/detection_reassembly.py b/src/ChartExtractor/utilities/detection_reassembly.py similarity index 100% rename from ChartExtractor/utilities/detection_reassembly.py rename to src/ChartExtractor/utilities/detection_reassembly.py diff --git a/ChartExtractor/utilities/detections.py b/src/ChartExtractor/utilities/detections.py similarity index 100% rename from ChartExtractor/utilities/detections.py rename to src/ChartExtractor/utilities/detections.py diff --git a/ChartExtractor/utilities/image_conversion.py b/src/ChartExtractor/utilities/image_conversion.py similarity index 100% rename from ChartExtractor/utilities/image_conversion.py rename to src/ChartExtractor/utilities/image_conversion.py diff --git a/ChartExtractor/utilities/tiling.py b/src/ChartExtractor/utilities/tiling.py similarity index 100% rename from ChartExtractor/utilities/tiling.py rename to src/ChartExtractor/utilities/tiling.py