├── CHANGELOG.md ├── LICENSE ├── README.md ├── __pycache__ ├── calendarium.cpython-310.pyc ├── dbms.cpython-310.pyc ├── engine.cpython-310.pyc ├── exporter.cpython-310.pyc ├── launcher.cpython-310.pyc ├── qc.cpython-310.pyc ├── tools.cpython-310.pyc └── westgards.cpython-310.pyc ├── biovarase.db ├── biovarase.py ├── calendarium.py ├── dbms.py ├── ddof ├── dimensions ├── elements ├── engine.py ├── exporter.py ├── frames ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── action.cpython-310.pyc │ ├── actions.cpython-310.pyc │ ├── analytical.cpython-310.pyc │ ├── analytical_goals.cpython-310.pyc │ ├── batch.cpython-310.pyc │ ├── counts.cpython-310.pyc │ ├── data.cpython-310.pyc │ ├── elements.cpython-310.pyc │ ├── export_rejections.cpython-310.pyc │ ├── license.cpython-310.pyc │ ├── main.cpython-310.pyc │ ├── plots.cpython-310.pyc │ ├── quick_data_analysis.cpython-310.pyc │ ├── rejection.cpython-310.pyc │ ├── rejections.cpython-310.pyc │ ├── result.cpython-310.pyc │ ├── set_zscore.cpython-310.pyc │ ├── tea.cpython-310.pyc │ ├── test.cpython-310.pyc │ ├── tests.cpython-310.pyc │ ├── unit.cpython-310.pyc │ ├── units.cpython-310.pyc │ ├── youden.cpython-310.pyc │ └── zscore.cpython-310.pyc ├── action.py ├── actions.py ├── analytical.py ├── analytical_goals.py ├── batch.py ├── counts.py ├── data.py ├── elements.py ├── export_rejections.py ├── license.py ├── main.py ├── plots.py ├── quick_data_analysis.py ├── rejection.py ├── rejections.py ├── result.py ├── set_zscore.py ├── tea.py ├── test.py ├── tests.py ├── unit.py ├── units.py ├── youden.py └── zscore.py ├── launcher.py ├── qc.py ├── show_error_bar ├── test ├── init └── test_qc.py ├── tools.py ├── westgards.py └── zscore /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Biovarase 2 | ## changelog 3 | 4 | **2021-03-18** 5 | 6 | Changed the way to pass the engine.py class to the whole app. 7 | 8 | Now we make. 9 | 10 | ```python 11 | 12 | class App(tk.Tk): 13 | """Biovarase Main Application start here""" 14 | def __init__(self): 15 | super().__init__() 16 | 17 | self.engine = Engine() 18 | 19 | ``` 20 | 21 | and so we can call it so 22 | 23 | ```python 24 | 25 | self.nametowidget(".").engine.get_frame(self, 8) 26 | 27 | last_id = self.nametowidget(".").engine.write(sql, args) 28 | 29 | ``` 30 | 31 | and so on, this because self.nametowidget(".") returns the actual widget whose path name is "." and "." is the root. 32 | 33 | 34 | 35 | **2019-12-13** 36 | 37 | Add Youden chart. 38 | 39 | Youden charts are used to compares two levels of controls and can help to differentiate between systematic and random error. 40 | 41 | To know the whole story go here: 42 | 43 | [Bio-Rad Blackboard - Youden Charts](https://www.youtube.com/watch?v=XUsMMjjL5eA) 44 | 45 | 46 | 47 | To show a Youden chart select first a test, after select at least two batches and on main menu select File/Youden, 48 | 49 | if the number of results are the same the program will show the plot. 50 | 51 | 52 | ![alt tag](https://user-images.githubusercontent.com/5463566/70846762-e077e600-1e5c-11ea-8fec-627182b73fb2.png) 53 | 54 | 55 | 56 | 57 | **2019-11-18** 58 | 59 | Add to main frame the expiration date for selected batch. 60 | 61 | If the expiration date is less than the current date it will color the row as red ,if 15 days is missing it will color yellow. 62 | 63 | In the database the expiration date field of the batches is datetime, but python return it as a string so we have to do this to compare the two dates. 64 | 65 | ```python 66 | 67 | def get_expiration_date(self, expiration_date): 68 | return (datetime.datetime.strptime(expiration_date, "%d-%m-%Y").date() - datetime.date.today()).days 69 | 70 | ``` 71 | Update in the database years of batches and results with this statements: 72 | 73 | ``` 74 | UPDATE batches SET expiration = datetime(expiration, '+6 years') 75 | UPDATE results SET recived = datetime(recived, '+4 years') 76 | ``` 77 | 78 | **2019-09-18** 79 | 80 | add fix in tools.py because with Python 3.7.3 and Tk '8.6.9' the color in treview doesn't work 81 | 82 | [Treeview: wrong color change](https://bugs.python.org/issue36468) 83 | 84 | 85 | #### Fix for setting text colour for Tkinter 8.6.9 86 | # From: https://core.tcl.tk/tk/info/509cafafae 87 | # 88 | # Returns the style map for 'option' with any styles starting with 89 | # ('!disabled', '!selected', ...) filtered out. 90 | 91 | # style.map() returns an empty list for missing options, so this 92 | # should be future-safe. 93 | 94 | 95 | **2019-09-17** 96 | 97 | could have missed the unittest?!?!? 98 | 99 | Add a folder named test, to launch unittest do somenthing 100 | 101 | ``` 102 | bc@hal9000:~/Biovarase-master/test$ python3 -m unittest 103 | ...... 104 | ---------------------------------------------------------------------- 105 | Ran 6 tests in 0.001s 106 | 107 | OK 108 | ``` 109 | 110 | this will run all the test named test*. py 111 | 112 | all tests are still at a primordial stage but this is the right way. 113 | 114 | **2019-09-16** 115 | 116 | Delete Biovarase icon file, from now on we will load icon as a base 64 data. 117 | 118 | In the engine file we write base 64 data of the incon file 119 | 120 | 121 | ```python 122 | def get_icon(self): 123 | return """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAAXNSR0IArs4c 124 | 6QAAAPBQTFRFAAAAAGbMas7///8A3/3/qeM7AFrH+v//AF/HAJn//v//ndgA 125 | AJgAhJGdipaii5ejhZKeACelwOoSZs0wACmlKC41Y8wwKS81p9s6ACykACSl 126 | LasAACmhJKcAdMgA3v3/NbcAa8QAyeoA1PYAAFzJACSk9/wAs+8UEJ8AXMYA 127 | ruYA//+bZs0yTLcA2/A6AF/JAGDW//94JSoy////IikyjNgmYGt0m9cF4f// 128 | gtUABJsA//9b//9NfN0y5PRgACaiACalX2lzQbMA//7//f8AP7IAACql2/QM 129 | Xb8A//+p6P//yOoAAGLWFKkAACWl8fqIerPNswAAAAF0Uk5TAEDm2GYAAAABY 130 | ktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgREA0rdM 131 | kpsgAAAJBJREFUGNNVytUSwjAURdHcpKXQUsHd3d3dHf7/b4CZQHPP215zCPn 132 | OsHTdMog9U1UU1RRgE+h3Q0MBSPtK72KT/Ji2EDhS1IlBwgBSj7oAwQ4BXGqd 133 | OWO2gPxahrfTP0BQnjVH55j7J+DzspuWOZQ5QH1wKma1ZCPBL7Ao7VmuOqmkH 134 | /wSXxWiz7XHf4x84g33ag0Bx8dLigAAAABJRU5ErkJggg==""" 135 | 136 | ``` 137 | 138 | and in the main file... 139 | 140 | ```python 141 | def set_icon(self): 142 | icon = tk.PhotoImage(data=self.engine.get_icon()) 143 | self.call('wm', 'iconphoto', self._w, '-default', icon) 144 | 145 | ``` 146 | 147 | and remember to delete icon from kwars....;) 148 | 149 | ```python 150 | kwargs={"style":"clam", "title":"Biovarase", "engine":Engine(*args)} 151 | 152 | ``` 153 | 154 | if you like you can use this link to convert your file 155 | 156 | [Base64.Guru](https://base64.guru/converter/encode/image/gif) 157 | 158 | 159 | **2019-08-25** 160 | 161 | Hello everybody, 162 | 163 | here new items of the project: 164 | 165 | - Possibility to set the value of ddof used to calculate the standard deviation with munpy, see check box on the left of the 166 | 167 | statusbar and clicking on it see what happends to sd value. See below link for more info. 168 | 169 | [numpy std reference](https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html) 170 | 171 | 172 | - Possibility to set the value of Z Score value. 173 | 174 | You can see the Z Score set on the left side of the statusbar, if you want change it go to main mane, Edit/Set Z Score, open 175 | 176 | the window and change it, if you don't remember the Z Score legal value go to main menu on File/Z Score. 177 | 178 | 179 | ![alt tag](https://user-images.githubusercontent.com/5463566/63646648-60473c80-c716-11e9-9d5e-72b17ff18198.png) 180 | 181 | 182 | 183 | - Possibility to show error bar , show check box on the left of the statusbar and see what happends 184 | 185 | - Total refactoring of Tea Plot windows, before the system calculation it'was wrong, now we compute upper and lower limit use 186 | 187 | total error allowable funcion plus 4% as recommended by Biorad on this very interesting video. 188 | 189 | [Biological Variation Part 9](https://www.youtube.com/watch?v=b7R2tJWWrvM&list=PL8260BF796E272C8A&index=9) 190 | 191 | - Rearrangement of the main menu. 192 | 193 | 194 | **2019-05-26** 195 | 196 | Hello everybody, after having separated all qc funcions in a new module 197 | 198 | named qc.py it’s time to implement a new feature **Sigma**. 199 | 200 | You can see this new wonderful feature launch the analytical goal from 201 | 202 | File/Export/Analitycal Goals 203 | 204 | I've add even the compute of delta sistematic critical error. 205 | 206 | ```python 207 | 208 | def get_sigma(self, cvw, cvb, target, series): 209 | """Compute sigma.""" 210 | 211 | avg = self.get_mean(series) 212 | tea = self.get_tea(cvw, cvb) 213 | bias = self.get_bias(avg, target) 214 | cv = self.get_cv(series) 215 | sigma = (tea - bias)/cv 216 | 217 | return round(sigma,2) 218 | ``` 219 | 220 | tea is Total Error Allowable. 221 | 222 | 223 | **2019-05-21** 224 | 225 | Hello everybody, in this new update you will find a new type of plot, named **Total Error Plot** 226 | 227 | ![alt tag](https://user-images.githubusercontent.com/5463566/63646848-68ed4200-c719-11e9-99c2-f0bbc7a8e589.png) 228 | 229 | 230 | You can launch this plot, after selecting a test,clicking on menu File/Te voice. 231 | 232 | For more information you can see this video 233 | 234 | [Biological Variation Part 9](https://www.youtube.com/watch?v=b7R2tJWWrvM&list=PL8260BF796E272C8A&index=9) 235 | 236 | on Biorad QC youtube chanel video. 237 | 238 | By the way all videos are very interesting. 239 | 240 | Add in the main window the TE% value, it's compute on engine.py module with this two funcions. 241 | 242 | ```python 243 | 244 | def get_bias(self, avg, target): 245 | try: 246 | bias = abs(round(float((avg-target)/float(target))*100,2)) 247 | except ZeroDivisionError: 248 | bias = None 249 | return bias 250 | 251 | def get_et(self, target, avg, cv): 252 | 253 | bias = self.get_bias(avg, target) 254 | 255 | return round(bias + (1.65*cv),2) 256 | 257 | 258 | 259 | ``` 260 | 261 | Minor refactoring on bias compute. 262 | 263 | 264 | **2019-04-28** 265 | 266 | Hello everybody, I've update the main.py module of Biovarase. 267 | 268 | Now we pass a number of arbitrary args or kwargs to the main class when we launch main.py script. 269 | 270 | Notice that we pass some default attributes and the Engine class as dictionary and even sys.argv as tuple for future need: 271 | 272 | 273 | 274 | 275 | >args = [] 276 | 277 | >for i in sys.argv: 278 | > args.append(i) 279 | 280 | >kwargs={"style":"clam", "icon":"biovarase.png", "title":"Biovarase", "engine":Engine()} 281 | 282 | 283 | 284 | 285 | ```python 286 | 287 | class App(tk.Tk): 288 | """Biovarase Main Application start here""" 289 | def __init__(self, *args, **kwargs): 290 | super(App, self).__init__() 291 | 292 | self.protocol("WM_DELETE_WINDOW", self.on_exit) 293 | 294 | self.engine = kwargs['engine'] 295 | self.set_title(kwargs['title']) 296 | self.set_icon(kwargs['icon']) 297 | self.set_style(kwargs['style']) 298 | 299 | frame = Biovarase(self, *args, **kwargs) 300 | frame.on_open() 301 | frame.pack(fill=tk.BOTH, expand=1) 302 | 303 | def set_title(self, title): 304 | s = "{0} {1}".format(title, __version__) 305 | self.title(s) 306 | 307 | def set_style(self, style): 308 | self.style = ttk.Style() 309 | self.style.theme_use(style) 310 | self.style.configure('.', background=self.engine.get_rgb(240,240,237)) 311 | 312 | def set_icon(self, icon): 313 | imgicon = tk.PhotoImage(file=icon) 314 | self.call('wm', 'iconphoto', self._w, '-default', imgicon) 315 | 316 | def on_exit(self): 317 | """Close all""" 318 | if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self): 319 | self.engine.con.close() 320 | self.quit() 321 | 322 | def main(): 323 | 324 | args = [] 325 | 326 | for i in sys.argv: 327 | args.append(i) 328 | 329 | kwargs={"style":"clam", "icon":"biovarase.png", "title":"Biovarase", "engine":Engine()} 330 | 331 | app = App(*args, **kwargs) 332 | 333 | app.mainloop() 334 | 335 | if __name__ == '__main__': 336 | main() 337 | 338 | ``` 339 | 340 | 341 | **2019-03-14** 342 | 343 | Hello everybody, after having taken inspiration from this book 344 | 345 | [Python GUI programming with Tkinter](https://www.packtpub.com/application-development/python-gui-programming-tkinter) 346 | 347 | of Alan D. Moore, that I am intensely studying, I've changed the way to setting up the application. 348 | 349 | Now we inherit from the class Tk instead of Frame. 350 | 351 | ```python 352 | class App(tk.Tk): 353 | """Biovarase Main Application""" 354 | 355 | def __init__(self): 356 | super().__init__() 357 | 358 | self.engine = Engine() 359 | 360 | self.protocol("WM_DELETE_WINDOW", self.on_exit) 361 | 362 | self.set_title() 363 | self.set_icon() 364 | self.set_style() 365 | 366 | frame = Biovarase(self, engine=self.engine) 367 | frame.on_open() 368 | frame.pack(fill=tk.BOTH, expand=1) 369 | 370 | ``` 371 | 372 | that call... 373 | 374 | ```python 375 | 376 | class Biovarase(ttk.Frame): 377 | 378 | def __init__(self, parent, *args, **kwargs): 379 | super().__init__() 380 | 381 | self.parent = parent 382 | self.engine = kwargs['engine'] 383 | 384 | 385 | ``` 386 | 387 | notice that I've change 388 | 389 | ```python 390 | self.master.protocol("WM_DELETE_WINDOW",self.on_exit) 391 | 392 | ``` 393 | 394 | with 395 | 396 | ```python 397 | self.protocol("WM_DELETE_WINDOW",self.on_exit) 398 | 399 | ``` 400 | Minor refatcoring: 401 | 402 | - keep out elements spinbox from main frame 403 | - add Elements toplevel to manage element numbers to compute data 404 | - the Elements toplevel can be call from main menu, it's a file voices 405 | - the elements number are write/read on the elements file on main directory and show on status bar text 406 | 407 | 408 | The book is very cool , thank you alan, now I think that Tkinter can constructively be compare with other tools as wxPython or Qt. 409 | 410 | 411 | **2019-03-03** 412 | 413 | Changed passing arguments mechanism, when build args for update sql statements. 414 | 415 | Now we use a tuple instead of a list. 416 | 417 | before 418 | 419 | ```python 420 | #from test.py 421 | 422 | def get_values(self,): 423 | 424 | return [self.dict_samples[self.cbSamples.current()], 425 | self.dict_units[self.cbUnits.current()], 426 | self.test.get(), 427 | self.cvw.get(), 428 | self.cvb.get(), 429 | self.enable.get()] 430 | 431 | #on on_save callback.... 432 | 433 | args = self.get_values() 434 | 435 | if self.index is not None: 436 | 437 | sql = self.engine.get_update_sql('tests', 'test_id') 438 | #args is a list 439 | args.append(self.selected_test[0]) 440 | 441 | ``` 442 | 443 | now 444 | 445 | ```python 446 | 447 | 448 | def get_values(self,): 449 | 450 | return (self.dict_samples[self.cbSamples.current()], 451 | self.dict_units[self.cbUnits.current()], 452 | self.test.get(), 453 | self.cvw.get(), 454 | self.cvb.get(), 455 | self.enable.get()) 456 | 457 | args = self.get_values() 458 | 459 | #on on_save callback.... 460 | 461 | if self.index is not None: 462 | 463 | sql = self.engine.get_update_sql('tests', 'test_id') 464 | #args is a tuple 465 | args = (*args, self.selected_test[0]) 466 | ``` 467 | 468 | 469 | 470 | **2019-02-28** 471 | 472 | Changed passing arguments mechanism, init method, between all toplevel. 473 | 474 | before 475 | ```python 476 | 477 | #toplevel tests launch function from main.py 478 | def on_tests(self,): 479 | f = frames.tests.Dialog(self,self.engine) 480 | f.on_open() 481 | 482 | #tests.py init method 483 | class Dialog(tk.Toplevel): 484 | def __init__(self, parent, engine): 485 | super().__init__(name='tests') 486 | 487 | self.parent = parent 488 | self.engine = engine 489 | ``` 490 | 491 | now 492 | ```python 493 | 494 | #toplevel tests launch function from main.py 495 | def on_tests(self,): 496 | f = frames.tests.Dialog(self, engine=self.engine) 497 | f.on_open() 498 | 499 | #tests.py init method 500 | class Dialog(tk.Toplevel): 501 | def __init__(self, parent, *args, **kwargs): 502 | super().__init__(name='tests') 503 | 504 | self.parent = parent 505 | self.engine = kwargs['engine'] 506 | 507 | ``` 508 | God bless *args, **kwargs.... 509 | 510 | go here for stimuli your brain 511 | 512 | [skipperkongen](http://skipperkongen.dk/2012/04/27/python-init-multiple-heritance/) 513 | 514 | 515 | **2019-02-16** 516 | 517 | Add to every toplevel this code 518 | 519 | ```python 520 | self.attributes('-topmost', True) 521 | ``` 522 | 523 | to keep toplevel in front of main window. 524 | 525 | change reset function on data.py, set messagebox.askyesno default button on No. 526 | 527 | ```python 528 | def on_reset_database(self, evt): 529 | 530 | msg = "You are about to delete the entire database.\nAre you sure? " 531 | 532 | if messagebox.askyesno(self.engine.title, msg, default='no', parent=self) == True: 533 | 534 | self.engine.dump_db() 535 | 536 | sql = ("DELETE FROM tests", 537 | "DELETE FROM batches", 538 | "DELETE FROM results", 539 | "DELETE FROM rejections",) 540 | 541 | for statement in sql: 542 | self.engine.write(statement,()) 543 | 544 | 545 | self.parent.on_reset() 546 | self.on_cancel() 547 | else: 548 | messagebox.showinfo(self.engine.title, self.engine.abort, parent=self) 549 | ``` 550 | 551 | 552 | 553 | **2019-02-06** 554 | 555 | Migrate almost anything from tkinter to ttk. 556 | 557 | Improve the field control function in tools.py module, now we check even if the user try to use a value not 558 | 559 | present on combobox, before save a record. 560 | 561 | ```python 562 | def on_fields_control(self, container): 563 | 564 | msg = "Please fill all fields." 565 | 566 | for w in container.winfo_children(): 567 | for field in w.winfo_children(): 568 | if type(field) in(ttk.Entry,ttk.Combobox): 569 | if not field.get(): 570 | messagebox.showwarning(self.title,msg) 571 | field.focus() 572 | return 0 573 | elif type(field)==ttk.Combobox: 574 | if field.get() not in field.cget('values'): 575 | msg = "You can choice only values in the list." 576 | messagebox.showwarning(self.title,msg) 577 | field.focus() 578 | return 0 579 | ``` 580 | 581 | 582 | 583 | **2019-01-01** 584 | 585 | Hi all and happy new year. 586 | 587 | Add a new powerfull toplevel frame. 588 | 589 | In the main frame, select a test and after go to menu bar and press File/Plots. 590 | 591 | The following window will appear. 592 | 593 | ![alt tag](https://user-images.githubusercontent.com/5463566/50571419-fdc9b680-0da9-11e9-9404-3f57ece11968.png) 594 | 595 | It's cool, not? 596 | 597 | The number of graphs are created at run time, look at this code line in plots.py file. 598 | 599 | count = len(batches)*100+11 600 | 601 | count is subplot args and depend of the numbers of batch data to show. 602 | 603 | regards. 604 | 605 | 606 | 607 | 608 | **2018-12-25** 609 | 610 | Hi all and merry christmas, this is the new 4.2 version of Biovarase. 611 | 612 | I' ve use this powerfull tool, [Spyder](https://www.spyder-ide.org/), to refactoring all the project. 613 | 614 | It's very cool;) 615 | 616 | Change widgets.py to tools.py because it seems widgets it' s a reserved word in deep python tk file.... 617 | 618 | Clean, with the Spider help, very much code lines. 619 | 620 | 621 | **2018-12-09** 622 | 623 | Add rejections managements.Now you can add actions, something like "Calibation","Blanck cuvette","Substitutions" in the relative frame and save on the database rejection actions on selected results. 624 | 625 | Rewrite all westgard class, now it's better. 626 | 627 | Write a better menu bar, with some items groupig, see Export item. 628 | 629 | Improve add/update file, now we use index to understand if the frame it's open to update or ti insert an item. 630 | 631 | Some minor refactoring 632 | 633 | **2018-11-24** 634 | 635 | Change the redrew mechanism, now Biovarase doesn't rebuild the graph from scratch when you select a batch, but update only the data. 636 | 637 | I've look here [Chapman Siu](https://gist.github.com/1966bc/824372b59c03425d02d816f1f02f8685) to learn how do it. 638 | 639 | Add Quick Data Analysis function on the menu to analyze the last dataset for every test and relative bacth. 640 | 641 | Add histogram to plot frequency distribution of a dataset. 642 | 643 | Some minor refactoring on main.py code. 644 | 645 | We deserve an updating to version 2.8. ;) 646 | 647 | 648 | **2018-11-18** 649 | 650 | Change format to align text in the listbox, remember to use a font such font='TkFixedFont'. 651 | 652 | Align date and result on graph, did anyone notice this? 653 | 654 | **2018-09-23** 655 | 656 | Biovarase change is dress....and make minor refactoring. 657 | 658 | **2018-08-30** 659 | 660 | Change elements data from shelve to sqlite. 661 | Fix some minor bug on test.py 662 | 663 | **2018-02-19** 664 | 665 | Porting the project to python 3.5, Debian 9 version 666 | Fix redrew problem, now the graph dosen't disappear 667 | Heavy refactoring of the all code 668 | Developed in Python 3.5.3 on Debian Stretch, winter 2018. 669 | 670 | **2017-05-07** 671 | 672 | Put graph in the main window. 673 | Struggled to redrew graph but now it's work. 674 | Developed in Python 2.7.9 on Debian Jessie, spring 2017. 675 | TOFIX: 676 | If you make double click on a result, the graph disappear.... 677 | ... 678 | 679 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python 3](https://img.shields.io/badge/python-3%20-blue.svg)](https://www.python.org/downloads/) 2 | [![Tkinter](https://img.shields.io/badge/Tkinter%20-green.svg)](https://docs.python.org/3/library/tk.html) 3 | [![Matplotlib](https://img.shields.io/badge/Matplotlib%20-red.svg)](https://matplotlib.org/) 4 | [![license](https://img.shields.io/badge/license-GPL%203-orange.svg)](./LICENSE) 5 | ![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=for-the-badge&logo=sqlite&logoColor=white) 6 | 7 | # Biovarase 8 | 9 | ## A clinical quality control data management project 10 | 11 | ![alt tag](https://user-images.githubusercontent.com/5463566/69223427-9e1ff980-0b7b-11ea-9699-7a62aa490efa.png) 12 | 13 | Biovarase is an application to manage clinical quality control data. 14 | 15 | The purpose of Quality Control Assurance in a clinical laboratory is to allow the control of the performances of an analytical procedure showing an alarm as soon as the trial doesn't result in the degree to respect the defined analytical rules. Biovarase furthermore calculates the classical statistical parameters for the quality control assurance ,e.g. sd, cv%, avg, and even the Imp(%), Bias(%) and TEa (total allowable error) using data retrived from: Current databases on biologic variation: pros, cons and progress Scand J Clin Lab Invest 1999;59:491-500. updated with the most recent specifications made available in 2014. 16 | It uses even the famous Westgard's rules to monitor results dataset. 17 | All the data are managed by SQLite database and matplotlib. 18 | 19 | - To show levey jennings graph, in the main windows select a test and choose the relative batch. 20 | - To manage batches in the main window select a test and on menubar choice Batchs/Add batch or Update batch. 21 | - To manage results in the main window select first a batch and after a result or on menubar choice Results/Add result or Update result. 22 | - To manage rejections in the main window select a result after check Enable Rejections on statusbar, in the window that will open add or update rejection type. 23 | - To manage actions in the main window use Edit/Actions. 24 | - To manage units in the main window use Edit/Units. 25 | - To insert, update or delete a test use Edit/Tests. 26 | - To manage batchs and relative results use Edit/Data. 27 | - To set the number of elements to compute Edit/Set Elements. 28 | - To set the Z Score use Edit/Set Z Score. 29 | - To export some xls data choice an item from File/Export 30 | 31 | Biovarase requires 32 | 33 | - Python =>3.5 34 | - tkinter 35 | - matplotlib 36 | - xlwt 37 | - numpy 38 | 39 | -------------------------------------------------------------------------------- /__pycache__/calendarium.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/calendarium.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/dbms.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/dbms.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/engine.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/engine.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/exporter.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/exporter.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/launcher.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/launcher.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/qc.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/qc.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/tools.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/tools.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/westgards.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/__pycache__/westgards.cpython-310.pyc -------------------------------------------------------------------------------- /biovarase.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/biovarase.db -------------------------------------------------------------------------------- /biovarase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ This is the launcher module of Biovarase.""" 4 | import sys 5 | import profile 6 | import pstats 7 | import frames.main as main 8 | 9 | __author__ = "Mainak Chaudhuri" 10 | __copyright__ = "MWS-2023" 11 | __credits__ = ["MainakRepositor",] 12 | __license__ = "GNU GPL Version 3, 2 Jan 2023" 13 | __version__ = "4.2" 14 | __maintainer__ = "MainakRepositor" 15 | __email__ = "mainakc24365@gmail.com" 16 | __date__ = "2022-12-26" 17 | __status__ = "Production" 18 | 19 | if len(sys.argv)>1: 20 | profile.run('main.main()', 'profile_results') 21 | p = pstats.Stats('profile_results') 22 | p.sort_stats('cumulative').print_stats(10) 23 | else: 24 | main.main() 25 | 26 | -------------------------------------------------------------------------------- /calendarium.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Provides a primitive light widget to manage calendar date in tkinter projects. 3 | 4 | How import; 5 | from calendarium import Calendarium 6 | 7 | How instantiate in your frame: 8 | 9 | self.start_date = Calendarium(self,"Start Date") 10 | 11 | How pack: 12 | #f is a tkinter widget such as Frame,LabelFrame 13 | if use grid method 14 | self.start_date.get_calendarium(f, row, col) 15 | If use pack method 16 | self.start_date.get_calendarium(f,) 17 | 18 | Set today date: 19 | self.start_date.set_today() 20 | 21 | Check if a date is right formated: 22 | 23 | if self.start_date.get_date(self)==False:return 24 | 25 | Notice that in the spinbox widget we allowed only integers. 26 | Calendarium use datetime.date to set/get date. 27 | 28 | """ 29 | import sys 30 | import datetime 31 | from datetime import date 32 | import tkinter as tk 33 | from tkinter import messagebox 34 | 35 | 36 | 37 | 38 | class Calendarium(tk.Frame): 39 | def __init__(self, caller, name, *args, **kwargs): 40 | super().__init__() 41 | 42 | self.args = args 43 | self.kwargs = kwargs 44 | 45 | self.vcmd = (self.register(self.validate), '%d', '%P', '%S') 46 | 47 | self.caller = caller 48 | self.name = name 49 | self.day = tk.IntVar() 50 | self.month = tk.IntVar() 51 | self.year = tk.IntVar() 52 | 53 | def __str__(self): 54 | return "class: %s" % (self.__class__.__name__, ) 55 | 56 | 57 | def get_calendarium(self, container, row=None, col=None): 58 | 59 | 60 | w = tk.LabelFrame(container, 61 | text=self.name, 62 | borderwidth=1, 63 | padx=2, pady=2, 64 | relief=tk.GROOVE,) 65 | 66 | 67 | day_label = tk.LabelFrame(w, text="Day") 68 | 69 | d = tk.Spinbox(day_label, bg='white', fg='blue', width=2, 70 | from_=1, to=31, 71 | validate='key', 72 | validatecommand=self.vcmd, 73 | textvariable=self.day, 74 | relief=tk.GROOVE,) 75 | 76 | month_label = tk.LabelFrame(w, text="Month") 77 | m = tk.Spinbox(month_label, bg='white', fg='blue', width=2, 78 | from_=1, to=12, 79 | validate='key', 80 | validatecommand=self.vcmd, 81 | textvariable=self.month, 82 | relief=tk.GROOVE,) 83 | 84 | year_label = tk.LabelFrame(w, text="Year") 85 | y = tk.Spinbox(year_label, bg='white', fg='blue', width=4, 86 | validate='key', 87 | validatecommand=self.vcmd, 88 | from_=1900, to=3000, 89 | textvariable=self.year, 90 | relief=tk.GROOVE,) 91 | 92 | for p, i in enumerate((day_label , d, month_label, m, year_label, y)): 93 | if row is not None: 94 | i.grid(row=0, column=p, padx=5, pady=5, sticky=tk.W) 95 | else: 96 | i.pack(side=tk.LEFT, fill=tk.X, padx=2) 97 | 98 | 99 | if row is not None: 100 | w.grid(row=row, column=col, sticky=tk.W) 101 | else: 102 | w.pack() 103 | 104 | return w 105 | 106 | def set_today(self,): 107 | 108 | today = date.today() 109 | 110 | self.day.set(today.day) 111 | self.month.set(today.month) 112 | self.year.set(today.year) 113 | 114 | def get_date(self, caller): 115 | 116 | try: 117 | return datetime.date(self.year.get(), self.month.get(), self.day.get()) 118 | except ValueError: 119 | msg = "Date format error:\n%s"%str(sys.exc_info()[1]) 120 | messagebox.showerror(caller.title(), msg, parent=caller) 121 | return False 122 | 123 | 124 | def get_timestamp(self,): 125 | 126 | t = datetime.datetime.now() 127 | 128 | return datetime.datetime(self.year.get(), 129 | self.month.get(), 130 | self.day.get(), 131 | t.hour, 132 | t.minute, 133 | t.second) 134 | 135 | 136 | def validate(self, action, value_if_allowed, text,): 137 | # action=1 -> insert 138 | if action == '1': 139 | if text in '0123456789': 140 | try: 141 | int(value_if_allowed) 142 | return True 143 | except ValueError: 144 | return False 145 | else: 146 | return False 147 | else: 148 | return True 149 | -------------------------------------------------------------------------------- /dbms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ This is the database administration module of Biovarase.""" 4 | import sys 5 | import inspect 6 | import sqlite3 as lite 7 | import datetime 8 | 9 | 10 | 11 | 12 | class DBMS: 13 | def __init__(self,): 14 | 15 | self.set_connection() 16 | 17 | def set_connection(self): 18 | 19 | self.con = lite.connect("biovarase.db", 20 | detect_types=lite.PARSE_DECLTYPES|lite.PARSE_COLNAMES, 21 | isolation_level='IMMEDIATE') 22 | self.con.text_factory = lite.OptimizedUnicode 23 | 24 | 25 | def write(self, sql, args=()): 26 | 27 | try: 28 | cur = self.con.cursor() 29 | cur.execute(sql, args) 30 | self.con.commit() 31 | return cur.lastrowid 32 | 33 | except: 34 | self.con.rollback() 35 | self.on_log(self, 36 | inspect.stack()[0][3], 37 | sys.exc_info()[1], 38 | sys.exc_info()[0], 39 | sys.modules[__name__]) 40 | 41 | finally: 42 | try: 43 | cur.close() 44 | except: 45 | self.on_log(self, 46 | inspect.stack()[0][3], 47 | sys.exc_info()[1], 48 | sys.exc_info()[0], 49 | sys.modules[__name__]) 50 | 51 | 52 | def read(self, fetch, sql, args=()): 53 | 54 | try: 55 | cur = self.con.cursor() 56 | cur.execute(sql, args) 57 | 58 | if fetch == True: 59 | rs = cur.fetchall() 60 | else: 61 | rs = cur.fetchone() 62 | cur.close() 63 | return rs 64 | 65 | except: 66 | self.on_log(self, 67 | inspect.stack()[0][3], 68 | sys.exc_info()[1], 69 | sys.exc_info()[0], 70 | sys.modules[__name__]) 71 | 72 | 73 | def dump_db(self,): 74 | 75 | dt = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 76 | s = dt + ".sql" 77 | with open(s, 'w') as f: 78 | for line in self.con.iterdump(): 79 | f.write('%s\n' % line) 80 | 81 | 82 | def get_last_row_id(self, cur): 83 | 84 | return cur.lastrowid 85 | 86 | 87 | def get_fields(self, table): 88 | """return fields name of the args table ordered by field number 89 | 90 | @param name: table, 91 | @return: fields 92 | @rtype: tuple 93 | """ 94 | try: 95 | 96 | columns = [] 97 | fields = [] 98 | 99 | sql = 'SELECT * FROM %s ' % table 100 | cur = self.con.cursor() 101 | cur.execute(sql) 102 | 103 | 104 | for field in cur.description: 105 | columns.append(field[0]) 106 | cur.close() 107 | 108 | for k, v in enumerate(columns): 109 | if k > 0: 110 | fields.append(v) 111 | 112 | return tuple(fields) 113 | except: 114 | self.on_log(self, 115 | inspect.stack()[0][3], 116 | sys.exc_info()[1], 117 | sys.exc_info()[0], 118 | sys.modules[__name__]) 119 | 120 | # FIXME This function sometimes returns incorrect data when pass datetime type on timestamp field. 121 | def get_update_sql(self, table, pk): 122 | """recive a table name and his pk to format an update sql statement 123 | 124 | @param name: table, pk 125 | @return: sql formatted stringstring 126 | @rtype: string 127 | """ 128 | """""" 129 | 130 | return "UPDATE %s SET %s =? WHERE %s =?"%(table, " =?, ".join(self.get_fields(table)), pk) 131 | 132 | # FIXME This function sometimes returns incorrect data when pass datetime type on timestamp field. 133 | def get_insert_sql(self, table, n): 134 | """recive a table name and len of args, len(args), 135 | to format an insert sql statement 136 | 137 | @param name: table, n 138 | @return: sql formatted stringstring 139 | @rtype: string 140 | """ 141 | try: 142 | return "INSERT INTO %s(%s)VALUES(%s)"%(table, ",".join(self.get_fields(table)), ",".join("?"*n)) 143 | except: 144 | self.on_log(self, 145 | inspect.stack()[0][3], 146 | sys.exc_info()[1], 147 | sys.exc_info()[0], 148 | sys.modules[__name__]) 149 | 150 | 151 | # FIXME This function sometimes fails when recive datetime type on timestamp field. 152 | def get_selected(self, table, field, *args): 153 | """recive table name, pk and make a dictionary 154 | 155 | @param name: table,field,*args 156 | @return: dictionary 157 | @rtype: dictionary 158 | """ 159 | 160 | d = {} 161 | sql = 'SELECT * FROM %s WHERE %s = ? ' % (table, field) 162 | 163 | for k, v in enumerate(self.read(False, sql, args)): 164 | d[k] = v 165 | #print k,v 166 | 167 | return d 168 | 169 | 170 | def get_series(self, batch_id, limit=None, result_id=None): 171 | 172 | series = [] 173 | 174 | if result_id is not None: 175 | 176 | sql = "SELECT ROUND(result,2),enable\ 177 | FROM results\ 178 | WHERE batch_id =?\ 179 | AND result_id <=?\ 180 | ORDER BY recived DESC LIMIT ?" 181 | rs = self.read(True, sql, (batch_id, result_id, limit)) 182 | 183 | else: 184 | 185 | sql = "SELECT ROUND(result,2),enable\ 186 | FROM results\ 187 | WHERE batch_id =?\ 188 | ORDER BY recived DESC LIMIT ?" 189 | rs = self.read(True, sql, (batch_id, limit)) 190 | 191 | rs = tuple(i for i in rs if i[1] != 0) 192 | 193 | for i in reversed(rs): 194 | series.append(i[0]) 195 | 196 | return series 197 | 198 | 199 | def main(): 200 | 201 | bar = DBMS() 202 | print(bar) 203 | sql = "SELECT name FROM sqlite_master WHERE type = 'view'" 204 | rs = bar.read(True, sql) 205 | if rs: 206 | for i in enumerate(rs): 207 | print(i) 208 | 209 | input('end') 210 | 211 | if __name__ == "__main__": 212 | main() 213 | -------------------------------------------------------------------------------- /ddof: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /dimensions: -------------------------------------------------------------------------------- 1 | w,1200 2 | h,700 3 | -------------------------------------------------------------------------------- /elements: -------------------------------------------------------------------------------- 1 | 30 -------------------------------------------------------------------------------- /engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ This is the engine module of Biovarase. This class inherit from other classes.""" 4 | import sys 5 | import os 6 | import inspect 7 | import datetime 8 | from dbms import DBMS 9 | from tools import Tools 10 | from qc import QC 11 | from westgards import Westgards 12 | from exporter import Exporter 13 | from launcher import Launcher 14 | 15 | 16 | 17 | 18 | class Engine(DBMS, Tools, QC, Westgards, Exporter, Launcher,): 19 | def __init__(self,): 20 | super().__init__() 21 | 22 | self.no_selected = "Attention!\nNo record selected!" 23 | self.delete = "Delete data?" 24 | self.ask_to_save = "Save data?" 25 | self.abort = "Operation aborted!" 26 | 27 | def __str__(self): 28 | return "class: {0}\nMRO:{1}".format(self.__class__.__name__, [x.__name__ for x in Engine.__mro__]) 29 | 30 | def get_python_version(self,): 31 | return "Python version: %s" % ".".join(map(str, sys.version_info[:3])) 32 | 33 | def busy(self, caller): 34 | caller.config(cursor="watch") 35 | 36 | def not_busy(self, caller): 37 | caller.config(cursor="") 38 | 39 | def get_file(self, file): 40 | """Return full path of the directory where program resides.""" 41 | return os.path.join(os.path.dirname(__file__), file) 42 | 43 | def on_log(self, container, function, exc_value, exc_type, module): 44 | 45 | now = datetime.datetime.now() 46 | log_text = "{0}\n{1}\n{2}\n{3}\n{4}\n\n".format(now, function, exc_value, exc_type, module) 47 | log_file = open('log.txt', 'a') 48 | log_file.write(log_text) 49 | log_file.close() 50 | 51 | def get_log_file(self): 52 | path = self.get_file("log.txt") 53 | self.open_file(path) 54 | 55 | def on_debug(self, module, function, *args): 56 | 57 | now = datetime.datetime.now() 58 | s = "\n\n{0}\n{1}\n{2}\n\n".format(now, module, function) 59 | f = open('debug.txt', 'a') 60 | f.write(s) 61 | for i in args: 62 | s = "{0}\n".format(i) 63 | f.write(s) 64 | f.close() 65 | 66 | def get_elements(self): 67 | 68 | try: 69 | path = self.get_file("elements") 70 | f = open(path, "r") 71 | e = f.readline() 72 | f.close() 73 | return e 74 | except: 75 | self.on_log(self, 76 | inspect.stack()[0][3], 77 | sys.exc_info()[1], 78 | sys.exc_info()[0], 79 | sys.modules[__name__]) 80 | 81 | def set_elements(self, elements): 82 | 83 | try: 84 | with open("elements", "w") as f: 85 | f.write(str(elements)) 86 | 87 | 88 | except: 89 | self.on_log(self, 90 | inspect.stack()[0][3], 91 | sys.exc_info()[1], 92 | sys.exc_info()[0], 93 | sys.modules[__name__]) 94 | 95 | def get_dimensions(self): 96 | 97 | try: 98 | d = {} 99 | path = self.get_file("dimensions") 100 | with open(path, "r") as filestream: 101 | for line in filestream: 102 | currentline = line.split(",") 103 | d[currentline[0]] = currentline[1] 104 | 105 | return d 106 | except: 107 | self.on_log(self, 108 | inspect.stack()[0][3], 109 | sys.exc_info()[1], 110 | sys.exc_info()[0], 111 | sys.modules[__name__]) 112 | 113 | def get_ddof(self): 114 | try: 115 | path = self.get_file("ddof") 116 | f = open(path, "r") 117 | v = f.readline() 118 | f.close() 119 | return int(v) 120 | except FileNotFoundError: 121 | self.on_log(self, 122 | inspect.stack()[0][3], 123 | sys.exc_info()[1], 124 | sys.exc_info()[0], 125 | sys.modules[__name__]) 126 | 127 | def set_ddof(self, value): 128 | 129 | try: 130 | with open("ddof", "w") as f: 131 | f.write(str(value)) 132 | 133 | except: 134 | self.on_log(self, 135 | inspect.stack()[0][3], 136 | sys.exc_info()[1], 137 | sys.exc_info()[0], 138 | sys.modules[__name__]) 139 | 140 | 141 | def get_zscore(self): 142 | try: 143 | path = self.get_file("zscore") 144 | f = open(path, "r") 145 | v = f.readline() 146 | f.close() 147 | return float(v) 148 | except FileNotFoundError: 149 | self.on_log(self, 150 | inspect.stack()[0][3], 151 | sys.exc_info()[1], 152 | sys.exc_info()[0], 153 | sys.modules[__name__]) 154 | 155 | def set_zscore(self, value): 156 | try: 157 | with open("zscore", "w") as f: 158 | f.write(str(value)) 159 | except FileNotFoundError: 160 | self.on_log(self, 161 | inspect.stack()[0][3], 162 | sys.exc_info()[1], 163 | sys.exc_info()[0], 164 | sys.modules[__name__]) 165 | 166 | def get_show_error_bar(self): 167 | try: 168 | path = self.get_file("show_error_bar") 169 | f = open(path, 'r') 170 | v = f.readline() 171 | f.close() 172 | return int(v) 173 | except FileNotFoundError: 174 | self.on_log(self, 175 | inspect.stack()[0][3], 176 | sys.exc_info()[1], 177 | sys.exc_info()[0], 178 | sys.modules[__name__]) 179 | 180 | 181 | def set_show_error_bar(self, value): 182 | 183 | try: 184 | with open("show_error_bar", "w") as f: 185 | f.write(str(value)) 186 | 187 | except: 188 | self.on_log(self, 189 | inspect.stack()[0][3], 190 | sys.exc_info()[1], 191 | sys.exc_info()[0], 192 | sys.modules[__name__]) 193 | 194 | def get_icon(self): 195 | return """iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAAXNSR0IArs4c 196 | 6QAAAPBQTFRFAAAAAGbMas7///8A3/3/qeM7AFrH+v//AF/HAJn//v//ndgA 197 | AJgAhJGdipaii5ejhZKeACelwOoSZs0wACmlKC41Y8wwKS81p9s6ACykACSl 198 | LasAACmhJKcAdMgA3v3/NbcAa8QAyeoA1PYAAFzJACSk9/wAs+8UEJ8AXMYA 199 | ruYA//+bZs0yTLcA2/A6AF/JAGDW//94JSoy////IikyjNgmYGt0m9cF4f// 200 | gtUABJsA//9b//9NfN0y5PRgACaiACalX2lzQbMA//7//f8AP7IAACql2/QM 201 | Xb8A//+p6P//yOoAAGLWFKkAACWl8fqIerPNswAAAAF0Uk5TAEDm2GYAAAABY 202 | ktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AgREA0rdM 203 | kpsgAAAJBJREFUGNNVytUSwjAURdHcpKXQUsHd3d3dHf7/b4CZQHPP215zCPn 204 | OsHTdMog9U1UU1RRgE+h3Q0MBSPtK72KT/Ji2EDhS1IlBwgBSj7oAwQ4BXGqd 205 | OWO2gPxahrfTP0BQnjVH55j7J+DzspuWOZQ5QH1wKma1ZCPBL7Ao7VmuOqmkH 206 | /wSXxWiz7XHf4x84g33ag0Bx8dLigAAAABJRU5ErkJggg==""" 207 | 208 | 209 | def get_expiration_date(self, expiration_date): 210 | return (datetime.datetime.strptime(expiration_date, "%d-%m-%Y").date() - datetime.date.today()).days 211 | 212 | def get_license(self): 213 | """get license""" 214 | try: 215 | path = self.get_file("LICENSE") 216 | f = open(path, "r") 217 | v = f.read() 218 | f.close() 219 | return v 220 | except FileNotFoundError: 221 | self.on_log(inspect.stack()[0][3], 222 | sys.exc_info()[1], 223 | sys.exc_info()[0], 224 | sys.modules[__name__]) 225 | 226 | 227 | def main(): 228 | 229 | foo = Engine() 230 | print(foo) 231 | input('end') 232 | 233 | if __name__ == "__main__": 234 | main() 235 | -------------------------------------------------------------------------------- /exporter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the exporter module of Biovarase. 3 | It set data to export dataset in xls file format.""" 4 | import sys 5 | import inspect 6 | import tempfile 7 | import xlwt 8 | 9 | 10 | 11 | 12 | 13 | 14 | class Exporter: 15 | """This class is used for data export. 16 | """ 17 | 18 | def __str__(self): 19 | return "class: {0}".format(self.__class__.__name__, ) 20 | 21 | def get_counts(self, args): 22 | 23 | try: 24 | 25 | sql = "SELECT tests.test_id,\ 26 | tests.test,\ 27 | samples.sample,\ 28 | COUNT(results.batch_id)\ 29 | FROM tests\ 30 | INNER JOIN batches ON tests.test_id = batches.test_id\ 31 | INNER JOIN samples ON tests.sample_id = samples.sample_id\ 32 | INNER JOIN results ON batches.batch_id = results.batch_id\ 33 | WHERE tests.enable=1\ 34 | AND results.recived >=?\ 35 | GROUP BY batches.test_id\ 36 | ORDER BY tests.test" 37 | 38 | 39 | rs = self.read(True, sql, args) 40 | 41 | path = tempfile.mktemp (".xls") 42 | obj = xlwt.Workbook() 43 | ws = obj.add_sheet('Biovarase', cell_overwrite_ok=True) 44 | 45 | #ws.col(0).width = 200 * 20 46 | #ws.col(1).width = 300 * 20 47 | row = 0 48 | 49 | #indexing is zero based, row then column 50 | cols =('Test','Sample','Count',) 51 | for c,t in enumerate(cols): 52 | ws.write(row,c, t,) 53 | 54 | row +=1 55 | 56 | if rs: 57 | for i in rs: 58 | ws.write(row, 0, i[1]) 59 | ws.write(row, 1, i[2],) 60 | ws.write(row, 2, i[3]) 61 | row +=1 62 | 63 | obj.save(path) 64 | self.launch(path) 65 | 66 | except: 67 | self.engine.on_log(self, 68 | inspect.stack()[0][3], 69 | sys.exc_info()[1], 70 | sys.exc_info()[0], 71 | sys.modules[__name__]) 72 | 73 | 74 | def get_rejections(self, args): 75 | 76 | path = tempfile.mktemp (".xls") 77 | obj = xlwt.Workbook() 78 | ws = obj.add_sheet('Biovarase', cell_overwrite_ok=True) 79 | 80 | row = 0 81 | #indexing is zero based, row then column 82 | cols = ('Test', 'Sample', 'Batch', 'Target', 'SD', 'Result', 83 | 'Recived', 'Action', 'Description', 'Modify') 84 | 85 | for c,t in enumerate(cols): 86 | ws.write(row,c, t,self.xls_style_font(True, False, 'Arial')) 87 | 88 | row +=1 89 | 90 | sql = "SELECT * FROM rejections_to_export WHERE modified >=?" 91 | 92 | rs = self.read(True, sql, args) 93 | 94 | if rs: 95 | for i in rs: 96 | ws.write(row, 0, i[0]) 97 | ws.write(row, 1, i[1]) 98 | ws.write(row, 2, i[2]) 99 | ws.write(row, 3, i[3]) 100 | ws.write(row, 4, i[4]) 101 | ws.write(row, 5, i[5]) 102 | ws.write(row, 6, i[6]) 103 | ws.write(row, 7, i[7]) 104 | ws.write(row, 8, i[8]) 105 | ws.write(row, 9, i[9]) 106 | 107 | row +=1 108 | 109 | obj.save(path) 110 | self.launch(path) 111 | 112 | 113 | def get_quick_data_analysis(self, selected_data): 114 | 115 | path = tempfile.mktemp (".xls") 116 | obj = xlwt.Workbook() 117 | ws = obj.add_sheet('Biovarase', cell_overwrite_ok=True) 118 | 119 | ws.col(0).width = 200 * 20 120 | row = 0 121 | cols = ('Test', 'Batch', 'Target', 'Result', 'avg', 122 | 'bias', 'SD', 'sd', 'cv', 'Wstg', 'Date',) 123 | #cols = ('Test','Batch','Target','SD','Result','Wstg','Date',) 124 | 125 | for c,t in enumerate(cols): 126 | ws.write(row, c, t, self.xls_style_font(True, False, 'Arial')) 127 | 128 | row += 1 129 | 130 | sql_tests = "SELECT * FROM lst_tests WHERE enable =1" 131 | 132 | rs_tests = self.read(True, sql_tests) 133 | 134 | for test in rs_tests: 135 | 136 | sql_batches = "SELECT * FROM batches WHERE enable =1 AND test_id =?" 137 | 138 | rs_batchs = self.read(True, sql_batches, (test[0],)) 139 | 140 | if rs_batchs: 141 | 142 | for batch in rs_batchs: 143 | 144 | sql_results = "SELECT result_id, result, recived\ 145 | FROM results\ 146 | WHERE batch_id =?\ 147 | AND enable =1\ 148 | AND date(recived)=?\ 149 | ORDER BY recived DESC" 150 | 151 | rs_results = self.read(True, sql_results, (batch[0], selected_data[0])) 152 | 153 | if rs_results is not None: 154 | 155 | for i in rs_results: 156 | 157 | series = self.get_series(batch[0], int(self.get_elements()), i[0]) 158 | 159 | if len(series) > 9: 160 | rule = self.get_westgard_violation_rule(batch[4], batch[5], series, batch, test) 161 | else: 162 | rule = "No data" 163 | 164 | c = None 165 | 166 | try: 167 | if i: 168 | 169 | compute_cv = self.get_cv(series) 170 | compute_sd = self.get_sd(series) 171 | compute_avg = self.get_mean(series) 172 | target = float(batch[4]) 173 | sd = float(batch[5]) 174 | bias = self.get_bias(compute_avg, target) 175 | result = float(i[1]) 176 | date = i[2].strftime("%d-%m-%Y") 177 | 178 | 179 | 180 | if result > target: 181 | #result > 3sd 182 | if result > (target + (sd*3)): 183 | c = "red" 184 | #if result is > 2sd and < +3sd 185 | #elif (target + (sd*2) <= result <= target + (sd*3)): 186 | elif result > (target + (sd*2)) and result < (target + (sd*3)): 187 | c = "yellow" 188 | 189 | elif result < target: 190 | if result < (target - (sd*3)): 191 | c = "red" 192 | #if result is > -2sd and < -3sd 193 | #elif (target - (sd*2) <= result <= target - (sd*3)): 194 | elif result < (target - (sd*2)) and result > (target - (sd*3)): 195 | c = "yellow" 196 | 197 | ws.write(row,0,test[1]) 198 | ws.write(row,1,batch[2]) 199 | ws.write(row,2,target) 200 | ws.write(row,3,result) 201 | ws.write(row,4,compute_avg) 202 | ws.write(row,5,bias) 203 | ws.write(row,6,sd) 204 | ws.write(row,7,compute_sd) 205 | ws.write(row,8,compute_cv) 206 | ws.write(row,10,date) 207 | 208 | if rule not in('Accept','No data'): 209 | 210 | ws.write(row,9,rule,self.xls_bg_colour('blue')) 211 | else: 212 | ws.write(row,9,rule,) 213 | 214 | 215 | 216 | if c : 217 | ws.write(row,3,result,self.xls_bg_colour(c)) 218 | row +=1 219 | else: 220 | ws.write(row,3,result,) 221 | row +=1 222 | 223 | except: 224 | 225 | self.on_log(self, 226 | inspect.stack()[0][3], 227 | sys.exc_info()[1], 228 | sys.exc_info()[0], 229 | sys.modules[__name__]) 230 | 231 | obj.save(path) 232 | self.launch(path) 233 | 234 | def get_analitical_goals(self,limit,rs): 235 | 236 | path = tempfile.mktemp (".xls") 237 | obj = xlwt.Workbook() 238 | ws = obj.add_sheet('Biovarase', cell_overwrite_ok=True) 239 | ws.col(0).width = 50 * 20 240 | for f in range(4,19): 241 | ws.col(f).width = 80 * 25 242 | row = 0 243 | #indexing is zero based, row then column 244 | cols =('T','analyte','batch','expiration','target','avg', 245 | 'CVa','CVw','CVb','Imp%','Bias%','TEa%','CVt','k imp', 246 | 'k bias','TE%','Sigma','Sce','Drc%','records',) 247 | for c,t in enumerate(cols): 248 | ws.write(row,c, t, self.xls_style_font(True, False, 'Arial')) 249 | 250 | row +=1 251 | 252 | for i in rs: 253 | 254 | series = self.get_series(i[0], limit) 255 | 256 | if len(series) > 5: 257 | 258 | cva = self.get_cv(series) 259 | sd = self.get_sd(series) 260 | avg = self.get_mean(series) 261 | cvw = i[6] 262 | cvb = i[7] 263 | target = float(i[5]) 264 | 265 | ws.write(row, 0, i[1]) 266 | ws.write(row, 1, i[2], self.xls_style_font(True, False, 'Times New Roman')) 267 | ws.write(row, 2, str(i[3])) 268 | ws.write(row, 3, str(i[4])) 269 | ws.write(row, 4, target) 270 | ws.write(row, 5, avg) 271 | 272 | #if cva is > cvw*0.50 273 | if cva > self.get_imp(cvw): 274 | ws.write(row, 6, float(cva), self.xls_bg_colour("blue")) 275 | else: 276 | ws.write(row, 6, float(cva)) 277 | 278 | ws.write(row, 7, float(cvw)) 279 | ws.write(row, 8, float(cvb)) 280 | ws.write(row, 9, xlwt.Formula(self.get_formula_imp(row)),) 281 | ws.write(row, 10, xlwt.Formula(self.get_formula_bias(row,)),) 282 | ws.write(row, 11, xlwt.Formula(self.get_formula_eta(row)),) 283 | ws.write(row, 12, xlwt.Formula(self.get_formula_cvt(row)),) 284 | 285 | x = self.get_formula_k_imp(cva, cvw, row) 286 | if x is not None: 287 | if x[1] is not None: 288 | ws.write(row, 13, xlwt.Formula(x[0]), self.xls_bg_colour(x[1])) 289 | else: 290 | ws.write(row, 13, xlwt.Formula(x[0])) 291 | 292 | x = self.get_formula_k_bias(avg, target, cva, cvw, row) 293 | if x[1] is not None: 294 | ws.write(row, 14, xlwt.Formula(x[0]), self.xls_bg_colour(x[1])) 295 | else: 296 | ws.write(row, 14, xlwt.Formula(x[0])) 297 | 298 | x = self.get_tea_tes_comparision(avg, target, cvw, cvb, cva) 299 | ws.write(row,15,x[0],self.xls_bg_colour(x[1])) 300 | 301 | #compute sigma 302 | x = self.get_sigma(cvw, cvb, target, series) 303 | ws.write(row, 16, x) 304 | 305 | #compute sistematic critical error 306 | x = self.get_sce(cvw, cvb, target, series) 307 | ws.write(row,17,x[0],self.xls_bg_colour(x[1])) 308 | 309 | #compute critical difference 310 | x = self.get_formula_drc(row) 311 | ws.write(row, 18, xlwt.Formula(x)) 312 | 313 | #records 314 | ws.write(row, 19, len(series),) 315 | row +=1 316 | 317 | obj.save(path) 318 | self.launch(path) 319 | 320 | 321 | def get_formula_drc(self,row): 322 | """compute critical difference""" 323 | #=ROUND((ROUND(SQRT(POWER(G30,2)+POWER(H30,2))*2.77,2))*F30/100,2) 324 | #return "ROUND((ROUND(SQRT(POWER(G%s;2)+POWER(H%s;2))*2.77;2))*F%s/100,2)"%(row+1,row+1,row+1,) 325 | return "(ROUND(SQRT(POWER(G%s;2)+POWER(H%s;2))*2.77;2))"%(row+1,row+1,) 326 | 327 | 328 | def get_formula_imp(self, row): 329 | return "ROUND((H%s * 0.5);2)"%(row+1,) 330 | 331 | def get_formula_bias(self, row): 332 | return "ROUND(SQRT(POWER(H%s;2)+POWER(I%s;2))*0.25;2)"%(row+1,row+1,) 333 | 334 | def get_formula_eta(self, row): 335 | return "ROUND((1.65*J%s)+ K%s;2)"%(row+1, row+1,) 336 | 337 | def get_formula_cvt(self, row): 338 | return "ROUND(SQRT(POWER(G%s;2)+POWER(H%s;2));2)"%(row+1,row+1,) 339 | 340 | def get_formula_k_imp(self, cva, cvw, row): 341 | 342 | try: 343 | 344 | k = round((cva/cvw),2) 345 | 346 | if 0.25 <= k <= 0.50: 347 | c ="green" 348 | elif 0.50 <= k <= 0.75: 349 | c = "yellow" 350 | elif k > 0.75: 351 | c = "red" 352 | else: 353 | c = "green" 354 | 355 | f = "ROUND(G%s/H%s;2)"%(row+1,row+1) 356 | return f,c 357 | 358 | except (ZeroDivisionError,ValueError): 359 | return None 360 | 361 | def get_formula_k_bias(self,avg, target, cvw, cva, row): 362 | 363 | """return bias k 0.125,0.25,0.375""" 364 | 365 | k = round(self.get_bias(avg, target)/self.get_cvt(cva,cvw),2) 366 | 367 | if 0.125 <= k <= 0.25: 368 | c ="green" 369 | elif 0.25 <= k <= 0.375: 370 | c = "yellow" 371 | elif k > 0.375: 372 | c = "red" 373 | else: 374 | c = "green" 375 | 376 | f = "ROUND((((F%s-E%s)/E%s)*100)/SQRT(POWER(H%s;2)+POWER(I%s;2));2)"%(row+1,row+1, 377 | row+1,row+1, 378 | row+1,) 379 | return f,c 380 | 381 | 382 | def xls_bg_colour(self,colour): 383 | 384 | """ Colour index 385 | 8 through 63. 0 = Black, 1 = White, 2 = Red, 3 = Green, 4 = Blue, 5 = Yellow, 6 = Magenta, 386 | 7 = Cyan, 16 = Maroon, 17 = Dark Green, 18 = Dark Blue, 19 = Dark Yellow , almost brown), 387 | 20 = Dark Magenta, 21 = Teal, 22 = Light Gray, 23 = Dark Gray, """ 388 | 389 | 390 | dict_colour = {"green":3, 391 | "red":2, 392 | "white":1, 393 | "yellow":5, 394 | "gray":22, 395 | "blue":4, 396 | "magenta":6, 397 | "cyan":7,} 398 | bg_colour = xlwt.XFStyle() 399 | p = xlwt.Pattern() 400 | p.pattern = xlwt.Pattern.SOLID_PATTERN 401 | p.pattern_fore_colour = dict_colour[colour] 402 | bg_colour.pattern = p 403 | return bg_colour 404 | 405 | def xls_style_font(self,is_bold,is_underline,font_name): 406 | 407 | style = xlwt.XFStyle() 408 | # Create a font to use with the style 409 | font = xlwt.Font() 410 | font.name = font_name 411 | font.bold = is_bold 412 | font.underline = is_underline 413 | # Set the style's font to this new one you set up 414 | style.font = font 415 | return style 416 | 417 | def main(): 418 | 419 | foo = Exporter() 420 | print(foo) 421 | input('end') 422 | 423 | if __name__ == "__main__": 424 | main() 425 | -------------------------------------------------------------------------------- /frames/__init__.py: -------------------------------------------------------------------------------- 1 | #FILE: gui/__init__.py 2 | -------------------------------------------------------------------------------- /frames/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/action.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/action.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/actions.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/actions.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/analytical.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/analytical.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/analytical_goals.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/analytical_goals.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/batch.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/batch.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/counts.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/counts.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/data.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/data.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/elements.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/elements.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/export_rejections.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/export_rejections.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/license.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/license.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/main.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/main.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/plots.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/plots.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/quick_data_analysis.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/quick_data_analysis.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/rejection.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/rejection.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/rejections.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/rejections.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/result.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/result.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/set_zscore.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/set_zscore.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/tea.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/tea.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/test.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/test.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/tests.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/tests.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/unit.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/unit.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/units.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/units.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/youden.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/youden.cpython-310.pyc -------------------------------------------------------------------------------- /frames/__pycache__/zscore.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MainakRepositor/Biovarase-Machine-GUI/34b4740d7958fbb1d53a195921456906030e7a64/frames/__pycache__/zscore.cpython-310.pyc -------------------------------------------------------------------------------- /frames/action.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the action module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent, index=None): 11 | super().__init__(name="action") 12 | 13 | self.parent = parent 14 | self.index = index 15 | self.attributes("-topmost", True) 16 | self.transient(parent) 17 | self.resizable(0, 0) 18 | 19 | self.action = tk.StringVar() 20 | self.enable = tk.BooleanVar() 21 | 22 | self.init_ui() 23 | self.nametowidget(".").engine.center_me(self) 24 | 25 | def init_ui(self): 26 | 27 | w = self.nametowidget(".").engine.get_init_ui(self) 28 | 29 | r = 0 30 | c = 1 31 | ttk.Label(w, text="Action:").grid(row=r, sticky=tk.W) 32 | self.txtAction = ttk.Entry(w, textvariable=self.action) 33 | self.txtAction.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 34 | 35 | r += 1 36 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 37 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable,) 38 | chk.grid(row=r, column=c, sticky=tk.W) 39 | 40 | self.nametowidget(".").engine.get_save_cancel(self, w) 41 | 42 | def on_open(self, selected_item=None): 43 | 44 | if self.index is not None: 45 | self.selected_item = selected_item 46 | what = "Edit {0}" 47 | self.set_values() 48 | else: 49 | what = "Add {0}" 50 | self.enable.set(1) 51 | 52 | msg = what.format(self.winfo_name().title()) 53 | self.title(msg) 54 | self.txtAction.focus() 55 | 56 | def set_values(self,): 57 | 58 | self.action.set(self.selected_item[1]) 59 | self.enable.set(self.selected_item[2]) 60 | 61 | def get_values(self,): 62 | 63 | return [self.action.get(), 64 | self.enable.get(),] 65 | 66 | 67 | def on_save(self, evt=None): 68 | 69 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 70 | 71 | if messagebox.askyesno(self.nametowidget(".").title(), 72 | self.nametowidget(".").engine.ask_to_save, 73 | parent=self) == True: 74 | 75 | args = self.get_values() 76 | 77 | if self.index is not None: 78 | 79 | sql = self.nametowidget(".").engine.get_update_sql(self.parent.table, self.parent.field) 80 | 81 | args.append(self.selected_item[0]) 82 | 83 | else: 84 | 85 | sql = self.nametowidget(".").engine.get_insert_sql(self.parent.table, len(args)) 86 | 87 | last_id = self.nametowidget(".").engine.write(sql, args) 88 | self.parent.on_open() 89 | 90 | if self.index is not None: 91 | self.parent.lstItems.see(self.index) 92 | self.parent.lstItems.selection_set(self.index) 93 | else: 94 | #force focus on listbox 95 | idx = list(self.parent.dict_items.keys())[list(self.parent.dict_items.values()).index(last_id)] 96 | self.parent.lstItems.see(idx) 97 | self.parent.lstItems.selection_set(idx) 98 | 99 | self.on_cancel() 100 | 101 | 102 | def on_cancel(self, evt=None): 103 | self.destroy() 104 | -------------------------------------------------------------------------------- /frames/actions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the actions module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import messagebox 5 | import frames.action as ui 6 | 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent): 11 | super().__init__(name='actions') 12 | 13 | self.parent = parent 14 | self.attributes('-topmost', True) 15 | self.table = "actions" 16 | self.field = "action_id" 17 | self.obj = None 18 | self.init_ui() 19 | self.nametowidget(".").engine.center_me(self) 20 | 21 | def init_ui(self): 22 | 23 | f = self.nametowidget(".").engine.get_frame(self, 2) 24 | self.lstItems = self.nametowidget(".").engine.get_listbox(f,) 25 | self.lstItems.bind("<>", self.on_item_selected) 26 | self.lstItems.bind("", self.on_item_activated) 27 | f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5, pady=5) 28 | f = self.nametowidget(".").engine.get_frame(self, 2) 29 | self.nametowidget(".").engine.get_add_edit_cancel(self, f) 30 | f.pack(fill=tk.BOTH, expand=1) 31 | 32 | def on_open(self,): 33 | 34 | self.title("Correttive Actions") 35 | self.set_values() 36 | 37 | def set_values(self): 38 | 39 | self.lstItems.delete(0, tk.END) 40 | index = 0 41 | self.dict_items = {} 42 | sql = "SELECT * FROM {0}".format(self.table) 43 | rs = self.nametowidget(".").engine.read(True, sql, ()) 44 | 45 | if rs: 46 | for i in rs: 47 | self.lstItems.insert(tk.END, i[1]) 48 | if i[2] != 1: 49 | self.lstItems.itemconfig(index, {'bg':'light gray'}) 50 | self.dict_items[index] = i[0] 51 | index += 1 52 | 53 | def on_add(self, evt): 54 | 55 | self.obj = ui.UI(self) 56 | self.obj.on_open() 57 | 58 | def on_edit(self, evt): 59 | self.on_item_activated() 60 | 61 | def on_item_activated(self, evt=None): 62 | 63 | if self.lstItems.curselection(): 64 | index = self.lstItems.curselection()[0] 65 | self.obj = ui.UI(self, index) 66 | self.obj.on_open(self.selected_item,) 67 | 68 | else: 69 | messagebox.showwarning(self.nametowidget(".").title(), 70 | self.nametowidget(".").engine.no_selected, 71 | parent=self) 72 | 73 | def on_item_selected(self, evt): 74 | 75 | if self.lstItems.curselection(): 76 | index = self.lstItems.curselection()[0] 77 | pk = self.dict_items.get(index) 78 | self.selected_item = self.nametowidget(".").engine.get_selected(self.table, 79 | self.field, 80 | pk) 81 | 82 | def on_cancel(self, evt=None): 83 | 84 | if self.obj is not None: 85 | self.obj.destroy() 86 | self.destroy() 87 | -------------------------------------------------------------------------------- /frames/analytical.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the analytical module of Biovarase. 3 | It shows analyticak goals rules""" 4 | import tkinter as tk 5 | 6 | 7 | 8 | class UI(tk.Toplevel): 9 | def __init__(self, parent): 10 | super().__init__(name="analitical") 11 | 12 | self.parent = parent 13 | self.attributes("-topmost", True) 14 | self.resizable(0, 0) 15 | self.init_ui() 16 | self.nametowidget(".").engine.center_me(self) 17 | 18 | def init_ui(self): 19 | 20 | w = self.nametowidget(".").engine.get_init_ui(self) 21 | 22 | items = (("k CV:", None), ("0.25", "green"), ("0.50", "yellow"), ("0.75", "red"),) 23 | 24 | r = 0 25 | c = 0 26 | for i in items: 27 | tk.Label(w, bg=i[1], text=i[0]).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 28 | r += 1 29 | 30 | 31 | items = (("k Bias:", None), ("0.125<= k <= 0.25", "green"), 32 | ("0.25<= k <= 0.375", "yellow"), ("k > 0.375", "red"),) 33 | 34 | r = 0 35 | c = 1 36 | for i in items: 37 | tk.Label(w, bg=i[1], text=i[0]).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 38 | r += 1 39 | 40 | items = (("Eta:", None), 41 | ("ETa < 1.65 (0.25 CVi) + 0.125 (CVi2+ CVg2) ½ ", "green"), 42 | ("ETa < 1.65 (0.50 CVi) + 0.25 (CVi2 + CVg2) ½", "yellow"), ("ETa < 1.65 (0.75 CVi) + 0.375 (CVi2+ CVg2) ½", "red"),) 43 | 44 | r = 0 45 | c = 2 46 | for i in items: 47 | tk.Label(w, bg=i[1], text=i[0]).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 48 | r += 1 49 | 50 | 51 | def on_open(self,): 52 | 53 | self.title("Analytical Goals Explained") 54 | 55 | 56 | def on_cancel(self, evt=None): 57 | self.destroy() 58 | -------------------------------------------------------------------------------- /frames/analytical_goals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the analitical_goals module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name='analytical_goals') 13 | 14 | self.parent = parent 15 | self.attributes('-topmost', True) 16 | self.resizable(0, 0) 17 | self.elements = tk.IntVar() 18 | self.vcmd = self.nametowidget(".").engine.get_validate_integer(self) 19 | self.init_ui() 20 | self.nametowidget(".").engine.center_me(self) 21 | 22 | 23 | def init_ui(self): 24 | 25 | w = self.nametowidget(".").engine.get_frame(self, 8) 26 | 27 | f = tk.LabelFrame(w, text='Set elements to export', font='Helvetica 10 bold') 28 | 29 | self.txElements = ttk.Entry(f, width=8, justify=tk.CENTER, 30 | textvariable=self.elements, 31 | validate='key', 32 | validatecommand=self.vcmd) 33 | self.txElements.pack() 34 | 35 | f.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 36 | 37 | bts = [('Export', self.on_export), ('Close', self.on_cancel)] 38 | 39 | for btn in bts: 40 | self.nametowidget(".").engine.get_button(w, btn[0]).bind("", btn[1]) 41 | 42 | w.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 43 | 44 | 45 | def on_open(self): 46 | 47 | self.title("Analytical Goals") 48 | self.elements.set(self.nametowidget(".").engine.get_elements()) 49 | self.txElements.focus() 50 | 51 | def on_export(self, evt=None): 52 | 53 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 54 | 55 | sql = "SELECT batches.batch_id,\ 56 | samples.sample,\ 57 | tests.test,\ 58 | batches.batch,\ 59 | batches.expiration,\ 60 | batches.target,\ 61 | tests.cvw,\ 62 | tests.cvb\ 63 | FROM tests\ 64 | INNER JOIN samples \ 65 | ON tests.sample_id = samples.sample_id\ 66 | INNER JOIN batches \ 67 | ON tests.test_id = batches.test_id\ 68 | WHERE tests.enable = 1\ 69 | AND tests.cvw !=0\ 70 | AND tests.cvb !=0\ 71 | AND batches.target !=0\ 72 | AND batches.enable = 1\ 73 | ORDER BY tests.test,samples.sample" 74 | 75 | limit = int(self.elements.get()) 76 | rs = self.nametowidget(".").engine.read(True, sql, ()) 77 | 78 | if rs: 79 | self.nametowidget(".").engine.get_analitical_goals(limit, rs) 80 | self.on_cancel() 81 | else: 82 | msg = "No record data to compute analytical goals." 83 | messagebox.showwarning(self.nametowidget(".").title(), msg, parent=self) 84 | 85 | def on_cancel(self, evt=None): 86 | self.destroy() 87 | -------------------------------------------------------------------------------- /frames/batch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the batch module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | from calendarium import Calendarium 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent, index=None): 12 | super().__init__(name="batch") 13 | 14 | self.attributes("-topmost", True) 15 | self.parent = parent 16 | self.index = index 17 | self.transient(parent) 18 | self.resizable(0, 0) 19 | 20 | self.batch = tk.StringVar() 21 | self.target = tk.DoubleVar() 22 | self.sd = tk.DoubleVar() 23 | self.enable = tk.BooleanVar() 24 | 25 | self.vcmd = self.nametowidget(".").engine.get_validate_float(self) 26 | self.nametowidget(".").engine.center_me(self) 27 | self.init_ui() 28 | 29 | 30 | def init_ui(self): 31 | 32 | w = self.nametowidget(".").engine.get_init_ui(self) 33 | 34 | r = 0 35 | c = 1 36 | ttk.Label(w, text="Batch:").grid(row=r, sticky=tk.W) 37 | self.txtBatch = ttk.Entry(w, textvariable=self.batch) 38 | self.txtBatch.grid(row=r, column=c, padx=5, pady=5) 39 | 40 | r += 1 41 | ttk.Label(w, text="Expiration:").grid(row=r, sticky=tk.N+tk.W) 42 | self.expiration_date = Calendarium(self, "") 43 | self.expiration_date.get_calendarium(w, r, c) 44 | 45 | 46 | r += 1 47 | ttk.Label(w, text="Target:").grid(row=r, sticky=tk.W) 48 | self.txtTarget = ttk.Entry(w, 49 | width=8, 50 | justify=tk.CENTER, 51 | validate='key', 52 | validatecommand=self.vcmd, 53 | textvariable=self.target) 54 | self.txtTarget.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 55 | 56 | r += 1 57 | ttk.Label(w, text="SD:").grid(row=r, sticky=tk.W) 58 | self.txtSD = ttk.Entry(w, 59 | width=8, 60 | justify=tk.CENTER, 61 | validate='key', 62 | validatecommand=self.vcmd, 63 | textvariable=self.sd) 64 | self.txtSD.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 65 | 66 | r += 1 67 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 68 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable) 69 | chk.grid(row=r, column=c, sticky=tk.W) 70 | 71 | self.nametowidget(".").engine.get_save_cancel(self, w) 72 | 73 | 74 | def on_open(self, selected_test, selected_batch=None): 75 | 76 | self.selected_test = selected_test 77 | 78 | if self.index is not None: 79 | self.selected_batch = selected_batch 80 | msg = "Update {0} for {1}".format(self.winfo_name(), selected_test[1]) 81 | self.set_values() 82 | else: 83 | msg = "Insert {0} for {1}".format(self.winfo_name(), selected_test[1]) 84 | self.expiration_date.set_today() 85 | self.target.set('') 86 | self.sd.set('') 87 | self.enable.set(1) 88 | 89 | self.title(msg) 90 | self.txtBatch.focus() 91 | 92 | 93 | def on_save(self, evt=None): 94 | 95 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 96 | if self.expiration_date.get_date(self) == False: return 97 | if messagebox.askyesno(self.nametowidget(".").title(), 98 | self.nametowidget(".").engine.ask_to_save, 99 | parent=self) == True: 100 | 101 | args = self.get_values() 102 | 103 | if self.index is not None: 104 | 105 | sql = self.nametowidget(".").engine.get_update_sql('batches', 'batch_id') 106 | 107 | args = (*args, self.selected_batch[0]) 108 | 109 | else: 110 | 111 | sql = self.nametowidget(".").engine.get_insert_sql('batches', len(args)) 112 | 113 | last_id = self.nametowidget(".").engine.write(sql, args) 114 | self.parent.set_batches() 115 | 116 | 117 | if self.index is not None: 118 | if self.parent.winfo_name() == "data": 119 | self.parent.lstBatches.focus() 120 | self.parent.lstBatches.selection_set(self.index) 121 | self.nametowidget(".").nametowidget("biovarase").set_batches() 122 | 123 | else: 124 | self.parent.lstBatches.see(self.index) 125 | self.parent.lstBatches.selection_set(self.index) 126 | self.parent.lstBatches.event_generate("<>") 127 | else: 128 | self.parent.lstBatches.selection_set(last_id) 129 | self.parent.lstBatches.see(last_id) 130 | 131 | self.on_cancel() 132 | 133 | 134 | def get_values(self,): 135 | 136 | return (self.selected_test[0], 137 | self.batch.get(), 138 | self.expiration_date.get_date(self), 139 | self.target.get(), 140 | self.sd.get(), 141 | self.enable.get()) 142 | 143 | 144 | def set_values(self,): 145 | 146 | self.batch.set(self.selected_batch[2]) 147 | self.expiration_date.year.set(int(self.selected_batch[3][0:4])) 148 | self.expiration_date.month.set(int(self.selected_batch[3][5:7])) 149 | self.expiration_date.day.set(int(self.selected_batch[3][8:10])) 150 | self.target.set(self.selected_batch[4]) 151 | self.sd.set(self.selected_batch[5]) 152 | self.enable.set(self.selected_batch[6]) 153 | 154 | def on_cancel(self, evt=None): 155 | self.destroy() 156 | -------------------------------------------------------------------------------- /frames/counts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the export_rejections module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import messagebox 5 | from calendarium import Calendarium 6 | 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name='counts') 13 | 14 | self.parent = parent 15 | self.attributes('-topmost', True) 16 | self.resizable(0, 0) 17 | self.init_ui() 18 | self.nametowidget(".").engine.center_me(self) 19 | 20 | def init_ui(self): 21 | 22 | w = self.nametowidget(".").engine.get_init_ui(self) 23 | 24 | self.export_date = Calendarium(self, "Export From") 25 | self.export_date.get_calendarium(w, 0, 0) 26 | 27 | self.nametowidget(".").engine.get_export_cancel(self, self) 28 | 29 | def on_open(self): 30 | 31 | self.export_date.set_today() 32 | 33 | self.title("Export Counts") 34 | 35 | def on_export(self, evt=None): 36 | 37 | if self.export_date.get_date(self) == False: return 38 | if messagebox.askyesno(self.nametowidget(".").title(), "Export data?", parent=self) == True: 39 | args = (self.export_date.get_date(self),) 40 | self.nametowidget(".").engine.get_counts(args) 41 | self.on_cancel() 42 | 43 | def on_cancel(self, evt=None): 44 | self.destroy() 45 | -------------------------------------------------------------------------------- /frames/data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the data module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | import frames.batch as batch 7 | import frames.result as result 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name="data") 13 | 14 | self.parent = parent 15 | self.attributes("-topmost", True) 16 | self.minsize(800, 400) 17 | 18 | self.data = tk.StringVar() 19 | self.objs = [] 20 | self.init_ui() 21 | self.nametowidget(".").engine.center_me(self) 22 | 23 | 24 | def init_ui(self): 25 | 26 | f0 = self.nametowidget(".").engine.get_frame(self, 8) 27 | 28 | f1 = tk.Frame(f0,) 29 | 30 | w = tk.LabelFrame(f1, text='Tests') 31 | self.cbTests = ttk.Combobox(w) 32 | self.cbTests.bind("<>", self.on_selected_test) 33 | self.cbTests.pack(side=tk.TOP, fill=tk.X, expand=0) 34 | w.pack(side=tk.TOP, fill=tk.X, expand=0) 35 | 36 | w = tk.LabelFrame(f1, text='Batchs') 37 | cols = (["#0", 'id', 'w', False, 0, 0], 38 | ["#1", 'Batch', 'w', True, 50, 50], 39 | ["#2", 'Expiration', 'center', True, 50, 50], 40 | ["#3", 'Target', 'center', True, 50, 50], 41 | ["#4", 'SD', 'center', True, 50, 50],) 42 | 43 | self.lstBatches = self.nametowidget(".").engine.get_tree(w, cols) 44 | self.lstBatches.tag_configure('is_enable', background='#DFDFDF') 45 | self.lstBatches.bind("<>", self.on_selected_batch) 46 | self.lstBatches.bind("", self.on_batch_activated) 47 | 48 | w.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 49 | 50 | f1.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 51 | 52 | f2 = tk.Frame(f0,) 53 | tk.Label(f2, 54 | font="TkFixedFont", 55 | anchor=tk.W, 56 | textvariable=self.data, 57 | padx=5, 58 | pady=5).pack(side=tk.TOP, 59 | fill=tk.X, 60 | expand=0) 61 | 62 | cols = (["#0", 'id', 'w', False, 0, 0], 63 | ["#1", 'Recived', 'w', True, 50, 50], 64 | ["#2", 'Result', 'center', True, 50, 50],) 65 | 66 | self.lstResults = self.nametowidget(".").engine.get_tree(f2, cols) 67 | self.lstResults.tag_configure('is_enable', background=self.nametowidget(".").engine.get_rgb(211, 211, 211)) 68 | self.lstResults.bind("<>", self.on_result_selected) 69 | self.lstResults.bind("", self.on_result_activated) 70 | w.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 71 | f2.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 72 | 73 | 74 | w = self.nametowidget(".").engine.get_frame(f0,4) 75 | 76 | bts = [("Batch", 0, self.on_add_batch, ""), 77 | ("Result", 0, self.on_add_result, ""), 78 | ("Close", 0, self.on_cancel, "")] 79 | 80 | for btn in bts: 81 | self.nametowidget(".").engine.get_button(w, btn[0], btn[1]).bind("", btn[2]) 82 | self.bind(btn[3], btn[2]) 83 | 84 | w.pack(fill=tk.BOTH, side=tk.RIGHT) 85 | 86 | f0.pack(fill=tk.BOTH, expand=1, padx=5, pady=5) 87 | 88 | def on_open(self,): 89 | 90 | msg = "Batch: {0} Results: {1}".format(None, "0") 91 | self.data.set(msg) 92 | self.set_tests() 93 | self.title("Batches and Results Management") 94 | 95 | def set_tests(self): 96 | 97 | sql = "SELECT * FROM lst_tests" 98 | 99 | rs = self.nametowidget(".").engine.read(True, sql, ()) 100 | index = 0 101 | self.dict_tests = {} 102 | voices = [] 103 | 104 | for i in rs: 105 | self.dict_tests[index] = i[0] 106 | index += 1 107 | voices.append(i[1]) 108 | 109 | self.cbTests['values'] = voices 110 | 111 | def set_batches(self,): 112 | 113 | for i in self.lstBatches.get_children(): 114 | self.lstBatches.delete(i) 115 | 116 | for i in self.lstResults.get_children(): 117 | self.lstResults.delete(i) 118 | 119 | msg = "Batch: None Results: 0" 120 | self.data.set(msg) 121 | 122 | sql = "SELECT batch_id,\ 123 | batch,\ 124 | strftime('%d-%m-%Y', expiration),\ 125 | target,\ 126 | sd,\ 127 | enable\ 128 | FROM batches WHERE test_id = ?\ 129 | ORDER BY expiration DESC" 130 | 131 | 132 | rs = self.nametowidget(".").engine.read(True, sql, (self.selected_test[0],)) 133 | 134 | if rs: 135 | for i in rs: 136 | 137 | if i[5] != 1: 138 | tag_config = ("is_enable",) 139 | else: 140 | tag_config = ("") 141 | 142 | 143 | self.lstBatches.insert('', tk.END, iid=i[0], text=i[0], 144 | values=(i[1], i[2], i[3], i[4]), 145 | tags = tag_config) 146 | 147 | def set_results(self,): 148 | 149 | for i in self.lstResults.get_children(): 150 | self.lstResults.delete(i) 151 | 152 | sql = "SELECT result_id,\ 153 | strftime('%d-%m-%Y', recived),\ 154 | ROUND(result,2),\ 155 | enable\ 156 | FROM results\ 157 | WHERE batch_id = ?\ 158 | ORDER BY recived DESC" 159 | 160 | rs = self.nametowidget(".").engine.read(True, sql, (self.selected_batch[0],)) 161 | msg = "Batch: {0} Results: {1}".format(self.selected_batch[2], len(rs)) 162 | self.data.set(msg) 163 | 164 | if rs: 165 | for i in rs: 166 | 167 | if i[3] != 1: 168 | tag_config = ("is_enable",) 169 | else: 170 | tag_config = ("") 171 | 172 | 173 | self.lstResults.insert("", tk.END, iid=i[0], text=i[0], 174 | values=(i[1], i[2]), 175 | tags=tag_config) 176 | 177 | 178 | def on_selected_test(self, evt): 179 | 180 | if self.cbTests.current() != -1: 181 | index = self.cbTests.current() 182 | pk = self.dict_tests[index] 183 | self.selected_test = self.nametowidget(".").engine.get_selected('lst_tests', 'test_id', pk) 184 | self.set_batches() 185 | 186 | def on_selected_batch(self, evt): 187 | 188 | if self.lstBatches.focus(): 189 | pk = int(self.lstBatches.item(self.lstBatches.focus())['text']) 190 | self.selected_batch = self.nametowidget(".").engine.get_selected('batches', 'batch_id', pk) 191 | self.set_results() 192 | 193 | def on_result_selected(self, evt): 194 | 195 | if self.lstResults.focus(): 196 | pk = int(self.lstResults.item(self.lstResults.focus())['text']) 197 | self.selected_result = self.nametowidget(".").engine.get_selected('results', 'result_id', pk) 198 | 199 | 200 | def on_batch_activated(self, evt): 201 | 202 | if self.lstBatches.focus(): 203 | item_iid = self.lstBatches.selection() 204 | obj = batch.UI(self, item_iid) 205 | obj.on_open(self.selected_test, self.selected_batch) 206 | self.objs.append(obj) 207 | 208 | 209 | def on_result_activated(self, evt): 210 | 211 | if self.lstResults.focus(): 212 | item_iid = self.lstResults.selection() 213 | obj = result.UI(self, item_iid) 214 | obj.on_open(self.selected_test, self.selected_batch, self.selected_result) 215 | self.objs.append(obj) 216 | 217 | def on_add_batch(self, evt): 218 | 219 | if self.cbTests.current() != -1: 220 | obj = batch.UI(self) 221 | obj.on_open(self.selected_test) 222 | self.objs.append(obj) 223 | else: 224 | msg = "Please select a test." 225 | messagebox.showwarning(self.master.title(), msg, parent=self) 226 | 227 | 228 | def on_add_result(self, evt): 229 | 230 | if self.lstBatches.focus(): 231 | obj = result.UI(self) 232 | obj.on_open(self.selected_test, self.selected_batch) 233 | self.objs.append(obj) 234 | 235 | else: 236 | msg = "Please select a batch." 237 | messagebox.showwarning(self.master.title(), msg, parent=self) 238 | 239 | def on_cancel(self, evt=None): 240 | for obj in self.objs: 241 | obj.destroy() 242 | self.destroy() 243 | -------------------------------------------------------------------------------- /frames/elements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the elemets module of Biovarase.""" 3 | import sys 4 | import tkinter as tk 5 | from tkinter import ttk 6 | from tkinter import messagebox 7 | 8 | 9 | 10 | 11 | class UI(tk.Toplevel): 12 | def __init__(self, parent): 13 | super().__init__(name="elements") 14 | 15 | self.parent = parent 16 | self.resizable(0, 0) 17 | self.elements = tk.IntVar() 18 | self.vcmd = self.nametowidget(".").engine.get_validate_integer(self) 19 | self.init_ui() 20 | self.nametowidget(".").engine.center_me(self) 21 | 22 | def init_ui(self): 23 | 24 | w = self.nametowidget(".").engine.get_frame(self, 8) 25 | 26 | f = tk.LabelFrame(w,text="Set elements", font="Helvetica 10 bold") 27 | 28 | self.txElements = ttk.Entry(f, width=8, justify=tk.CENTER, 29 | textvariable=self.elements, 30 | validate="key", 31 | validatecommand=self.vcmd) 32 | self.txElements.pack() 33 | 34 | f.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand =1) 35 | 36 | bts = [("Save", self.on_save), 37 | ("Close", self.on_cancel)] 38 | 39 | for btn in bts: 40 | self.nametowidget(".").engine.get_button(w, btn[0]).bind("", btn[1]) 41 | 42 | self.bind("", self.on_save) 43 | self.bind("", self.on_cancel) 44 | 45 | w.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 46 | 47 | def on_open(self): 48 | 49 | self.elements.set(self.nametowidget(".").engine.get_elements()) 50 | self.txElements.focus() 51 | self.title("Set elements") 52 | 53 | def on_save(self, evt=None): 54 | 55 | try: 56 | 57 | if self.elements.get(): 58 | 59 | 60 | if messagebox.askyesno(self.nametowidget(".").title(), 61 | self.nametowidget(".").engine.ask_to_save, 62 | parent=self) == True: 63 | 64 | #notice, same name callback but different class 65 | self.nametowidget(".").engine.set_elements(self.elements.get()) 66 | self.parent.set_elements() 67 | self.on_cancel() 68 | 69 | except: 70 | msg = "Attention\n{0}\nPerhaps the text field is empty?".format(sys.exc_info()[1], sys.exc_info()[0],) 71 | 72 | messagebox.showwarning(self.master.title(), msg, parent=self) 73 | 74 | def on_cancel(self, evt=None): 75 | self.destroy() 76 | -------------------------------------------------------------------------------- /frames/export_rejections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the export_rejections module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import messagebox 5 | from calendarium import Calendarium 6 | 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name='export_rejection') 13 | 14 | self.parent = parent 15 | self.resizable(0, 0) 16 | self.transient(parent) 17 | 18 | self.init_ui() 19 | self.nametowidget(".").engine.center_me(self) 20 | 21 | def init_ui(self): 22 | 23 | w = self.nametowidget(".").engine.get_init_ui(self) 24 | 25 | r = 0 26 | self.start_date = Calendarium(self, "Start Date") 27 | self.start_date.get_calendarium(w, r) 28 | 29 | self.nametowidget(".").engine.get_export_cancel(self, w) 30 | 31 | def on_open(self): 32 | 33 | self.start_date.set_today() 34 | 35 | self.title("Export Rejections Data") 36 | 37 | def on_export(self, evt=None): 38 | 39 | if self.start_date.get_date(self) == False: return 40 | 41 | if messagebox.askyesno(self.nametowidget(".").title(), "Export data?", parent=self) == True: 42 | args = (self.start_date.get_date(self),) 43 | self.nametowidget(".").engine.get_rejections(args) 44 | self.on_cancel() 45 | 46 | def on_cancel(self, evt=None): 47 | self.destroy() 48 | -------------------------------------------------------------------------------- /frames/license.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the license module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | 6 | 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent): 11 | super().__init__(name="license") 12 | 13 | self.parent = parent 14 | #self.resizable(0, 0) 15 | self.init_ui() 16 | self.master.engine.center_me(self) 17 | 18 | def init_ui(self): 19 | 20 | w = ttk.Frame(self, padding=4) 21 | 22 | self.txLicense = self.nametowidget(".").engine.get_text_box(w,) 23 | 24 | w.pack(fill=tk.BOTH, expand=1) 25 | 26 | 27 | def on_open(self): 28 | 29 | msg = self.nametowidget(".").engine.get_license() 30 | 31 | if msg: 32 | self.txLicense.insert("1.0", msg) 33 | 34 | self.title(self.master.title()) 35 | 36 | -------------------------------------------------------------------------------- /frames/plots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the plots module of Biovarase.""" 3 | import tkinter as tk 4 | 5 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 6 | 7 | try: 8 | from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as nav_tool 9 | except: 10 | from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as nav_tool 11 | 12 | 13 | from matplotlib.figure import Figure 14 | 15 | 16 | 17 | 18 | class UI(tk.Toplevel): 19 | def __init__(self, parent): 20 | super().__init__(name="plots") 21 | 22 | self.parent = parent 23 | self.minsize(1200, 600) 24 | self.obj = None 25 | self.init_ui() 26 | self.nametowidget(".").engine.center_me(self) 27 | 28 | def init_ui(self): 29 | 30 | 31 | f0 = self.nametowidget(".").engine.get_frame(self) 32 | 33 | #create graph! 34 | #Figure: The top level container for all the plot elements. 35 | #figsize:width, height in inches, figsize=(6.4, 4.8) 36 | self.fig = Figure() 37 | #fig.suptitle(self.engine.title, fontsize=20,fontweight='bold') 38 | #self.fig.subplots_adjust(bottom=0.10, right=0.98, left=0.10, top=0.88,wspace=0.08) 39 | self.fig.subplots_adjust(hspace=0.65, left=0.125, right=0.9) 40 | self.canvas = FigureCanvasTkAgg(self.fig, f0) 41 | toolbar = nav_tool(self.canvas, f0) 42 | toolbar.update() 43 | self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 44 | 45 | f0.pack(fill=tk.BOTH, expand=1) 46 | 47 | 48 | def on_open(self, selected_test): 49 | 50 | s = "Biovarase Quality Control Plots for: {0}" 51 | 52 | title = s.format(selected_test[3]) 53 | 54 | self.fig.suptitle(title, fontsize=14) 55 | 56 | s = "Quality Control Plots %s"%selected_test[3] 57 | 58 | self.um = self.get_um(selected_test[2]) 59 | 60 | self.title(s) 61 | 62 | self.get_batches(selected_test) 63 | 64 | 65 | def get_batches(self, selected_test): 66 | 67 | batches = [] 68 | 69 | sql = "SELECT *\ 70 | FROM batches\ 71 | WHERE test_id =? AND enable =1\ 72 | ORDER BY expiration DESC" 73 | args = (selected_test[0],) 74 | rs = self.nametowidget(".").engine.read(True, sql, args) 75 | 76 | if rs: 77 | 78 | for i in rs: 79 | batches.append(i) 80 | 81 | self.set_values(batches) 82 | 83 | def set_values(self, batches): 84 | 85 | 86 | #nrows, ncols, and index 87 | 88 | count = len(batches)*100+11 89 | #print(count) 90 | 91 | sql = "SELECT * FROM lst_results WHERE batch_id = ? LIMIT ?" 92 | 93 | for batch in batches: 94 | 95 | rs = self.nametowidget(".").engine.read(True, sql, ((batch[0], int(self.nametowidget(".").engine.get_elements())))) 96 | if rs: 97 | 98 | target = batch[4] 99 | sd = batch[5] 100 | series = self.nametowidget(".").engine.get_series(batch[0], int(self.nametowidget(".").engine.get_elements())) 101 | mean = self.nametowidget(".").engine.get_mean(series) 102 | cv = self.nametowidget(".").engine.get_cv(series) 103 | x_labels = self.get_x_labels(rs) 104 | 105 | 106 | self.set_axs(count, 107 | len(rs), 108 | target, 109 | sd, 110 | series, 111 | len(series), 112 | mean, 113 | self.nametowidget(".").engine.get_sd(series), 114 | cv, 115 | x_labels[0], 116 | x_labels[1], 117 | batch) 118 | 119 | count += 1 120 | 121 | 122 | self.canvas.draw() 123 | 124 | def get_x_labels(self, rs): 125 | 126 | x_labels = [] 127 | dates = [] 128 | 129 | rs = tuple(i for i in rs if i[4] != 0) 130 | 131 | if rs: 132 | for i in reversed(rs): 133 | x_labels.append(i[2]) 134 | dates.append(i[2]) 135 | 136 | return (x_labels, dates) 137 | 138 | 139 | def set_axs(self, count, count_rs, target, sd, series, count_series, 140 | compute_average, compute_sd, compute_cv, x_labels, dates, batch): 141 | 142 | 143 | #obj.clear() 144 | obj = self.fig.add_subplot(count, facecolor=('xkcd:light grey'),) 145 | obj.grid(True) 146 | 147 | lines = ([], [], [], [], [], [], []) 148 | 149 | for i in range(len(series)+1): 150 | 151 | lines[0].append(target+(sd*3)) 152 | lines[1].append(target+(sd*2)) 153 | lines[2].append(target+sd) 154 | 155 | lines[3].append(target) 156 | 157 | lines[4].append(target-sd) 158 | lines[5].append(target-(sd*2)) 159 | lines[6].append(target-(sd*3)) 160 | 161 | #it's show time 162 | obj.set_xticks(range(0, len(series)+1)) 163 | #obj.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(21)) 164 | #obj.yaxis.set_major_formatter(FormatStrFormatter('%.2f')) 165 | obj.set_xticklabels(x_labels, rotation=70, size=6) 166 | obj.plot(series, marker="8", label='data') 167 | 168 | for x, y in enumerate(series): 169 | obj.text(x, y, str(y),) 170 | 171 | obj.plot(lines[0], color="red", label='+3 sd', linestyle='--') 172 | obj.plot(lines[1], color="yellow", label='+2 sd', linestyle='--') 173 | obj.plot(lines[2], color="green", label='+1 sd', linestyle='--') 174 | obj.plot(lines[3], label='target', linewidth=2) 175 | obj.plot(lines[4], color="green", label='-1 sd', linestyle='--') 176 | obj.plot(lines[5], color="yellow", label='-2 sd', linestyle='--') 177 | obj.plot(lines[6], color="red", label='-3 sd', linestyle='--') 178 | 179 | if self.um is not None: 180 | obj.set_ylabel(str(self.um[0])) 181 | else: 182 | obj.set_ylabel("No unit assigned yet") 183 | 184 | s = "Batch: {0} Target: {1} SD: {2} Exp: {3} avg: {4:.2f}, std: {5:.2f} cv: {6:.2f}" 185 | 186 | title = s.format(batch[2], 187 | batch[4], 188 | batch[5], 189 | batch[3], 190 | compute_average, compute_sd, compute_cv) 191 | 192 | obj.set_title(title, loc='left') 193 | 194 | bottom_text = ("from %s to %s"%(dates[0], dates[-1]), count_series, count_rs) 195 | 196 | obj.text(0.95, 0.01, '%s computed %s on %s results'%bottom_text, 197 | verticalalignment='bottom', 198 | horizontalalignment='right', 199 | transform=obj.transAxes, 200 | color='black',) 201 | 202 | 203 | def get_um(self, unit_id): 204 | 205 | sql = "SELECT unit FROM units WHERE unit_id =?" 206 | return self.nametowidget(".").engine.read(False, sql, (unit_id,)) 207 | 208 | 209 | def on_cancel(self, evt=None): 210 | self.destroy() 211 | -------------------------------------------------------------------------------- /frames/quick_data_analysis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the quick_data_analysis module of Biovarase.""" 3 | import tkinter as tk 4 | from calendarium import Calendarium 5 | 6 | 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name='quick_data_analysis') 13 | 14 | self.attributes('-topmost', True) 15 | self.transient(parent) 16 | self.resizable(0, 0) 17 | self.parent = parent 18 | self.nametowidget(".").engine.center_me(self) 19 | self.init_ui() 20 | 21 | def init_ui(self): 22 | 23 | w = self.nametowidget(".").engine.get_init_ui(self) 24 | 25 | r = 0 26 | c = 0 27 | 28 | self.analysis_date = Calendarium(self, "Set a date") 29 | self.analysis_date.get_calendarium(w, r, c) 30 | 31 | self.nametowidget(".").engine.get_export_cancel(self, self) 32 | 33 | 34 | def on_open(self): 35 | 36 | sql = "SELECT date(recived) FROM results WHERE enable= 1 ORDER BY recived DESC LIMIT 1;" 37 | 38 | rs = self.nametowidget(".").engine.read(False, sql,) 39 | 40 | msg = "Quick Data Analysis last data {0}".format(rs[0]) 41 | 42 | self.analysis_date.year.set(int(rs[0][0:4])) 43 | self.analysis_date.month.set(int(rs[0][5:7])) 44 | self.analysis_date.day.set(int(rs[0][8:10])) 45 | 46 | self.title(msg) 47 | 48 | 49 | def on_export(self, evt=None): 50 | 51 | if self.analysis_date.get_date(self) == False: return 52 | 53 | args = (self.analysis_date.get_date(self),) 54 | self.nametowidget(".").engine.get_quick_data_analysis(args) 55 | self.on_cancel() 56 | 57 | def on_cancel(self, evt=None): 58 | self.destroy() 59 | 60 | -------------------------------------------------------------------------------- /frames/rejection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the action rejection of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | from calendarium import Calendarium 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent, index=None): 12 | super().__init__(name="rejection") 13 | 14 | self.attributes('-topmost', True) 15 | self.parent = parent 16 | self.index = index 17 | self.transient(parent) 18 | self.resizable(0, 0) 19 | 20 | self.description = tk.StringVar() 21 | self.enable = tk.BooleanVar() 22 | self.init_ui() 23 | self.nametowidget(".").engine.center_me(self) 24 | 25 | def init_ui(self): 26 | 27 | w = self.nametowidget(".").engine.get_init_ui(self) 28 | 29 | r = 0 30 | c = 1 31 | tk.Label(w, text="Actions:").grid(row=r, sticky=tk.W) 32 | self.cbActions = ttk.Combobox(w,) 33 | self.cbActions.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 34 | 35 | r += 1 36 | ttk.Label(w, text="Description:").grid(row=r, sticky=tk.W) 37 | self.txDescription = ttk.Entry(w, textvariable=self.description,) 38 | self.txDescription.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 39 | 40 | r += 1 41 | ttk.Label(w, text="Modified:").grid(row=r, column=0, sticky=tk.W) 42 | self.modified_date = Calendarium(self, "") 43 | self.modified_date.get_calendarium(w, r, c) 44 | 45 | r += 1 46 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 47 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable) 48 | chk.grid(row=r, column=c, sticky=tk.W) 49 | 50 | if self.index is not None: 51 | self.nametowidget(".").engine.get_save_cancel_delete(self, w) 52 | else: 53 | self.nametowidget(".").engine.get_save_cancel(self, w) 54 | 55 | def on_open(self, selected_test, selected_batch, selected_result, selected_rejection=None): 56 | 57 | self.selected_test = selected_test 58 | self.selected_batch = selected_batch 59 | self.selected_result = selected_result 60 | 61 | self.set_actions() 62 | 63 | if self.index is not None: 64 | self.selected_rejection = selected_rejection 65 | msg = "Update {0}".format(self.winfo_name()) 66 | self.set_values() 67 | else: 68 | msg = "Add {0}".format(self.winfo_name()) 69 | self.enable.set(1) 70 | self.modified_date.set_today() 71 | 72 | 73 | self.title(msg) 74 | self.cbActions.focus() 75 | 76 | def on_save(self, evt=None): 77 | 78 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 79 | if self.modified_date.get_date(self) == False: return 80 | if messagebox.askyesno(self.nametowidget(".").title(), 81 | self.nametowidget(".").engine.ask_to_save, 82 | parent=self) == True: 83 | 84 | args = self.get_values() 85 | 86 | if self.index is not None: 87 | 88 | sql = self.nametowidget(".").engine.get_update_sql('rejections', 'rejection_id') 89 | 90 | args = (*args, self.selected_rejection[0]) 91 | 92 | else: 93 | sql = self.nametowidget(".").engine.get_insert_sql('rejections', len(args)) 94 | 95 | last_id = self.nametowidget(".").engine.write(sql, args) 96 | self.parent.on_open(self.selected_test, self.selected_batch, self.selected_result) 97 | 98 | if self.index is not None: 99 | self.parent.lstItems.see(self.index) 100 | self.parent.lstItems.selection_set(self.index) 101 | else: 102 | #force focus on listbox 103 | idx = list(self.parent.dict_items.keys())[list(self.parent.dict_items.values()).index(last_id)] 104 | self.parent.lstItems.selection_set(idx) 105 | self.parent.lstItems.see(idx) 106 | 107 | self.on_cancel() 108 | 109 | def on_delete(self, evt=None): 110 | 111 | if self.index is not None: 112 | if messagebox.askyesno(self.master.title(), self.nametowidget(".").engine.delete, parent=self) == True: 113 | 114 | sql = "DELETE FROM rejections WHERE rejection_id =?" 115 | args = (self.selected_rejection[0],) 116 | self.nametowidget(".").engine.write(sql, args) 117 | self.parent.on_open(self.selected_test, self.selected_batch, self.selected_result) 118 | 119 | if self.index is not None: 120 | self.parent.lstItems.see(self.index) 121 | self.parent.lstItems.selection_set(self.index) 122 | 123 | self.on_cancel() 124 | 125 | else: 126 | messagebox.showinfo(self.master.title(), self.engine.abort, parent=self) 127 | 128 | def set_actions(self): 129 | 130 | index = 0 131 | values = [] 132 | self.dict_actions = {} 133 | 134 | sql = "SELECT action_id, action FROM actions ORDER BY action ASC" 135 | rs = self.nametowidget(".").engine.read(True, sql, ()) 136 | 137 | for i in rs: 138 | self.dict_actions[index] = i[0] 139 | index += 1 140 | values.append(i[1]) 141 | 142 | self.cbActions['values'] = values 143 | 144 | 145 | def get_values(self,): 146 | 147 | return (self.selected_result[0], 148 | self.dict_actions[self.cbActions.current()], 149 | self.description.get(), 150 | self.modified_date.get_timestamp(), 151 | self.enable.get()) 152 | 153 | def set_values(self,): 154 | 155 | key = next(key for key, value in self.dict_actions.items() if value == self.selected_rejection[2]) 156 | self.cbActions.current(key) 157 | 158 | self.description.set(self.selected_rejection[3]) 159 | 160 | self.modified_date.year.set(int(self.selected_rejection[4].year)) 161 | self.modified_date.month.set(int(self.selected_rejection[4].month)) 162 | self.modified_date.day.set(int(self.selected_rejection[4].day)) 163 | 164 | self.enable.set(self.selected_rejection[5]) 165 | 166 | def on_cancel(self, evt=None): 167 | self.destroy() 168 | -------------------------------------------------------------------------------- /frames/rejections.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the rejections module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import messagebox 5 | import frames.rejection 6 | 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name="rejections") 13 | 14 | self.parent = parent 15 | self.attributes('-topmost', True) 16 | self.transient(parent) 17 | self.resizable(0, 0) 18 | 19 | self.test = tk.StringVar() 20 | self.batch = tk.StringVar() 21 | self.result = tk.StringVar() 22 | self.recived = tk.StringVar() 23 | self.obj = None 24 | 25 | self.init_ui() 26 | self.nametowidget(".").engine.center_me(self) 27 | 28 | def init_ui(self): 29 | 30 | f0 = self.nametowidget(".").engine.get_frame(self) 31 | 32 | w = tk.LabelFrame(f0, text='Select data',) 33 | 34 | tk.Label(w, text="Test:").pack(side=tk.TOP) 35 | tk.Label(w, 36 | font = "Verdana 12 bold", 37 | textvariable = self.test).pack(side=tk.TOP) 38 | 39 | tk.Label(w, text="Batch:").pack(side=tk.TOP) 40 | tk.Label(w, 41 | font = "Verdana 12 bold", 42 | textvariable = self.batch).pack(side=tk.TOP) 43 | 44 | tk.Label(w, text="Result:").pack(side=tk.TOP) 45 | tk.Label(w, 46 | font = "Verdana 12 bold", 47 | textvariable = self.result).pack(side=tk.TOP) 48 | 49 | tk.Label(w, text="Recived:").pack(side=tk.TOP) 50 | tk.Label(w, 51 | font = "Verdana 12 bold", 52 | textvariable = self.recived).pack(side=tk.TOP) 53 | 54 | w.pack(side=tk.LEFT, fill=tk.Y, expand=0) 55 | 56 | w = self.nametowidget(".").engine.get_frame(f0) 57 | 58 | self.lstItems = self.nametowidget(".").engine.get_listbox(w, width=40) 59 | self.lstItems.bind("<>", self.on_item_selected) 60 | self.lstItems.bind("", self.on_item_activated) 61 | 62 | w.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand =1) 63 | 64 | self.nametowidget(".").engine.get_add_edit_cancel(self,f0) 65 | 66 | f0.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 67 | 68 | def on_open(self, selected_test, selected_batch, selected_result): 69 | 70 | self.selected_test = selected_test 71 | self.selected_batch = selected_batch 72 | self.selected_result = selected_result 73 | 74 | self.set_values() 75 | 76 | self.title("Rejections") 77 | 78 | def set_values(self): 79 | 80 | self.lstItems.delete(0, tk.END) 81 | 82 | self.test.set(self.selected_test[1]) 83 | self.batch.set(self.selected_batch[2]) 84 | self.result.set(self.selected_result[2]) 85 | dt = self.selected_result[3].strftime('%Y-%m-%d') 86 | self.recived.set(dt) 87 | 88 | index = 0 89 | self.dict_items = {} 90 | args = (self.selected_result[0],) 91 | sql = "SELECT * FROM lst_rejections WHERE result_id =?" 92 | rs = self.nametowidget(".").engine.read(True, sql, args) 93 | 94 | if rs: 95 | for i in rs: 96 | s = '{:20}{:20}'.format(i[3],i[1]) 97 | self.lstItems.insert(tk.END, (s)) 98 | if i[4] != 1: 99 | self.lstItems.itemconfig(index, {'bg':'light gray'}) 100 | self.dict_items[index]=i[0] 101 | index+=1 102 | 103 | def on_add(self, evt): 104 | 105 | self.obj = frames.rejection.UI(self) 106 | self.obj.on_open(self.selected_test,self.selected_batch, self.selected_result) 107 | 108 | def on_edit(self, evt): 109 | self.on_item_activated(self) 110 | 111 | def on_item_activated(self,evt): 112 | 113 | if self.lstItems.curselection(): 114 | index = self.lstItems.curselection()[0] 115 | self.obj = frames.rejection.UI(self, index) 116 | self.obj.on_open(self.selected_test, 117 | self.selected_batch, 118 | self.selected_result, 119 | self.selected_item) 120 | 121 | else: 122 | messagebox.showwarning(self.master.title(),self.nametowidget(".").engine.no_selected, parent = self) 123 | 124 | def on_item_selected(self, evt): 125 | 126 | if self.lstItems.curselection(): 127 | index = self.lstItems.curselection()[0] 128 | pk = self.dict_items.get(index) 129 | self.selected_item = self.nametowidget(".").engine.get_selected('rejections','rejection_id', pk) 130 | 131 | 132 | def on_cancel(self, evt=None): 133 | 134 | """force closing of the childs... 135 | """ 136 | 137 | if self.obj is not None: 138 | self.obj.destroy() 139 | self.destroy() 140 | 141 | -------------------------------------------------------------------------------- /frames/result.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the result module of Biovarase.""" 3 | import sys 4 | import tkinter as tk 5 | from tkinter import ttk 6 | from tkinter import messagebox 7 | from calendarium import Calendarium 8 | 9 | 10 | 11 | 12 | class UI(tk.Toplevel): 13 | def __init__(self, parent, index=None): 14 | super().__init__(name='result') 15 | 16 | self.parent = parent 17 | self.index = index 18 | self.transient(parent) 19 | self.resizable(0, 0) 20 | 21 | self.test = tk.StringVar() 22 | self.batch = tk.StringVar() 23 | self.result = tk.DoubleVar() 24 | self.recived_time = tk.StringVar() 25 | self.enable = tk.BooleanVar() 26 | 27 | self.vcmd = self.nametowidget(".").engine.get_validate_float(self) 28 | self.set_style() 29 | self.init_ui() 30 | self.nametowidget(".").engine.center_me(self) 31 | 32 | 33 | def set_style(self): 34 | 35 | s = ttk.Style() 36 | 37 | s.configure('Data.TLabel', font=('Helvetica', 12, 'bold')) 38 | 39 | 40 | def init_ui(self): 41 | 42 | w = self.nametowidget(".").engine.get_init_ui(self) 43 | 44 | r = 0 45 | c = 1 46 | ttk.Label(w, text="Test:").grid(row=r, sticky=tk.W) 47 | lbl = ttk.Label(w, style='Data.TLabel', textvariable=self.test) 48 | lbl.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 49 | 50 | r += 1 51 | ttk.Label(w, text="Batch:").grid(row=r, sticky=tk.W) 52 | lbl = ttk.Label(w, style='Data.TLabel', textvariable=self.batch) 53 | lbl.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 54 | 55 | r += 1 56 | ttk.Label(w, text="Result:").grid(row=r, sticky=tk.W) 57 | self.txtResult = ttk.Entry(w, width=8, justify=tk.CENTER, validate='key', validatecommand=self.vcmd, textvariable=self.result) 58 | self.txtResult.grid(row=r, column=1, sticky=tk.W, padx=5, pady=5) 59 | 60 | r += 1 61 | ttk.Label(w, text="Recived:").grid(row=r, sticky=tk.N+tk.W) 62 | 63 | self.recived_date = Calendarium(self, "") 64 | self.recived_date.get_calendarium(w, r, c) 65 | 66 | r += 1 67 | lbl = ttk.Label(w, text="Time:").grid(row=r, sticky=tk.W) 68 | lbl = ttk.Label(w, style='Data.TLabel', textvariable=self.recived_time) 69 | lbl.grid(row=r, column=c, sticky=tk.W, padx=5, pady=5) 70 | 71 | r += 1 72 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 73 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable,) 74 | chk.grid(row=r, column=c, sticky=tk.W) 75 | 76 | if self.index is not None: 77 | self.nametowidget(".").engine.get_save_cancel_delete(self, w) 78 | else: 79 | self.nametowidget(".").engine.get_save_cancel(self, w) 80 | 81 | 82 | def on_open(self, selected_test, selected_batch, selected_result=None): 83 | 84 | self.selected_batch = selected_batch 85 | self.test.set(selected_test[1]) 86 | self.batch.set(self.selected_batch[2]) 87 | 88 | 89 | if self.index is not None: 90 | self.selected_result = selected_result 91 | msg = "Update {0} for {1}".format(self.winfo_name(), selected_test[1]) 92 | self.set_values() 93 | else: 94 | msg = "Insert {0} for {1}".format(self.winfo_name(), selected_test[1]) 95 | self.enable.set(1) 96 | self.result.set('') 97 | self.recived_date.set_today() 98 | 99 | self.title(msg) 100 | self.txtResult.focus() 101 | 102 | def on_save(self, evt=None): 103 | 104 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 105 | if self.recived_date.get_date(self) == False: return 106 | 107 | if messagebox.askyesno(self.nametowidget(".").title(), 108 | self.nametowidget(".").engine.ask_to_save, 109 | parent=self) == True: 110 | 111 | args = self.get_values() 112 | 113 | if self.index is not None: 114 | 115 | sql = self.nametowidget(".").engine.get_update_sql('results', 'result_id') 116 | 117 | args = (*args, self.selected_result[0]) 118 | 119 | else: 120 | sql = self.nametowidget(".").engine.get_insert_sql('results', len(args)) 121 | 122 | last_id = self.nametowidget(".").engine.write(sql, args) 123 | self.parent.set_results() 124 | 125 | if self.index is not None: 126 | self.parent.lstResults.focus() 127 | self.parent.lstResults.see(self.index) 128 | self.parent.lstResults.selection_set(self.index) 129 | else: 130 | self.parent.lstResults.see(last_id) 131 | self.parent.lstResults.selection_set(last_id) 132 | 133 | if self.parent.winfo_name() == "data": 134 | self.nametowidget(".").nametowidget("biovarase").set_results() 135 | 136 | 137 | self.on_cancel() 138 | 139 | 140 | def on_delete(self, evt=None): 141 | 142 | if self.index is not None: 143 | if messagebox.askyesno(self.nametowidget(".").title(), 144 | self.nametowidget(".").engine.delete, 145 | parent=self) == True: 146 | sql = "DELETE FROM results WHERE result_id =?" 147 | args = (self.selected_result[0],) 148 | self.nametowidget(".").engine.write(sql, args) 149 | sql = "DELETE FROM rejections WHERE result_id =?" 150 | args = (self.selected_result[0],) 151 | self.nametowidget(".").engine.write(sql, args) 152 | self.parent.set_results() 153 | 154 | if self.parent.winfo_name() == "data": 155 | self.nametowidget(".").nametowidget("biovarase").set_results() 156 | 157 | 158 | 159 | 160 | 161 | self.on_cancel() 162 | else: 163 | messagebox.showinfo(self.master.title(), 164 | self.nametowidget(".").engine.abort, 165 | parent=self) 166 | 167 | def get_values(self,): 168 | 169 | return (self.selected_batch[0], 170 | self.result.get(), 171 | self.recived_date.get_timestamp(), 172 | self.enable.get()) 173 | 174 | def set_values(self,): 175 | 176 | try: 177 | self.recived_date.year.set(int(self.selected_result[3].year)) 178 | self.recived_date.month.set(int(self.selected_result[3].month)) 179 | self.recived_date.day.set(int(self.selected_result[3].day)) 180 | 181 | t = "{0}:{1}:{0}".format(self.selected_result[3].hour, 182 | self.selected_result[3].minute, 183 | self.selected_result[3].second) 184 | 185 | self.recived_time.set(t) 186 | 187 | except: 188 | print(sys.exc_info()[0]) 189 | print(sys.exc_info()[1]) 190 | print(sys.exc_info()[2]) 191 | 192 | self.result.set(self.selected_result[2]) 193 | self.enable.set(self.selected_result[4]) 194 | 195 | def on_cancel(self, evt=None): 196 | self.destroy() 197 | 198 | -------------------------------------------------------------------------------- /frames/set_zscore.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the set set_zscore module of Biovarase.""" 3 | import sys 4 | import tkinter as tk 5 | from tkinter import ttk 6 | from tkinter import messagebox 7 | 8 | 9 | 10 | 11 | class UI(tk.Toplevel): 12 | def __init__(self, parent): 13 | super().__init__(name="set_zscore") 14 | 15 | self.parent = parent 16 | self.attributes('-topmost', True) 17 | self.transient(parent) 18 | self.resizable(0, 0) 19 | 20 | self.z_score = tk.DoubleVar() 21 | self.vcmd = self.nametowidget(".").engine.get_validate_float(self) 22 | self.nametowidget(".").engine.center_me(self) 23 | self.init_ui() 24 | 25 | 26 | def init_ui(self): 27 | 28 | f0 = self.nametowidget(".").engine.get_frame(self, 8) 29 | 30 | w = tk.LabelFrame(f0, text='Set Z Score', font='Helvetica 10 bold') 31 | 32 | self.txValue = ttk.Entry(w, width=8, 33 | justify=tk.CENTER, 34 | textvariable=self.z_score, 35 | validate='key', 36 | validatecommand=self.vcmd) 37 | self.txValue.pack() 38 | 39 | w.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 40 | 41 | bts = [('Save', self.on_save), 42 | ('Close', self.on_cancel)] 43 | 44 | for btn in bts: 45 | self.nametowidget(".").engine.get_button(f0, btn[0]).bind("", btn[1]) 46 | 47 | self.bind("", self.on_save) 48 | self.bind("", self.on_cancel) 49 | 50 | f0.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 51 | 52 | def on_open(self): 53 | 54 | self.z_score.set(self.nametowidget(".").engine.get_zscore()) 55 | self.txValue.focus() 56 | self.title("Set Z Score") 57 | 58 | def on_save(self, evt=None): 59 | 60 | try: 61 | 62 | if self.z_score.get(): 63 | 64 | if messagebox.askyesno(self.nametowidget(".").title(), 65 | self.nametowidget(".").engine.ask_to_save, 66 | parent=self) == True: 67 | 68 | self.nametowidget(".").engine.set_zscore(self.z_score.get()) 69 | self.parent.set_zscore() 70 | self.on_cancel() 71 | except: 72 | msg = "Attention\n{0}\nPerhaps the text field is empty?".format(sys.exc_info()[1], sys.exc_info()[0],) 73 | 74 | messagebox.showwarning(self.master.title(), msg, parent=self) 75 | 76 | def on_cancel(self, evt=None): 77 | self.destroy() 78 | -------------------------------------------------------------------------------- /frames/tea.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the tea module of Biovarase.""" 3 | import tkinter as tk 4 | 5 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 6 | 7 | try: 8 | from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as nav_tool 9 | except: 10 | from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as nav_tool 11 | 12 | from matplotlib.figure import Figure 13 | 14 | 15 | 16 | 17 | class UI(tk.Toplevel): 18 | def __init__(self, parent): 19 | super().__init__(name="tea") 20 | 21 | self.parent = parent 22 | self.minsize(1200, 600) 23 | self.nametowidget(".").engine.center_me(self) 24 | self.init_ui() 25 | 26 | 27 | def init_ui(self): 28 | 29 | f0 = self.nametowidget(".").engine.get_frame(self) 30 | 31 | #create graph! 32 | #Figure: The top level container for all the plot elements. 33 | self.fig = Figure() 34 | #self.fig.subplots_adjust(bottom=0.10, right=0.98, left=0.10, top=0.88,wspace=0.08) 35 | self.fig.subplots_adjust(hspace=0.65, left=0.125, right=0.9) 36 | self.canvas = FigureCanvasTkAgg(self.fig, f0) 37 | toolbar = nav_tool(self.canvas, f0) 38 | toolbar.update() 39 | self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 40 | 41 | f0.pack(fill=tk.BOTH, expand=1) 42 | 43 | 44 | def on_open(self, selected_test): 45 | 46 | self.cvw = selected_test[5] 47 | self.cvb = selected_test[6] 48 | 49 | s = "Biovarase Total Error Plots for: {0}" 50 | 51 | title = s.format(selected_test[3]) 52 | 53 | self.fig.suptitle(title, fontsize=14) 54 | 55 | s = "Total Error Plots %s"%selected_test[3] 56 | 57 | self.um = self.get_um(selected_test[2]) 58 | 59 | self.title(s) 60 | 61 | self.get_batches(selected_test) 62 | 63 | 64 | def get_batches(self, selected_test): 65 | 66 | batches = [] 67 | 68 | sql = "SELECT *\ 69 | FROM batches\ 70 | WHERE test_id =? AND enable =1\ 71 | ORDER BY expiration DESC" 72 | 73 | args = (selected_test[0],) 74 | rs = self.nametowidget(".").engine.read(True, sql, args) 75 | 76 | if rs: 77 | 78 | for i in rs: 79 | batches.append(i) 80 | 81 | self.set_values(batches) 82 | 83 | def set_values(self, batches): 84 | 85 | 86 | count = len(batches)*100+11 87 | 88 | sql = "SELECT * FROM lst_results WHERE batch_id = ? LIMIT ?" 89 | 90 | for batch in batches: 91 | 92 | rs = self.nametowidget(".").engine.read(True, sql, ((batch[0], int(self.nametowidget(".").engine.get_elements())))) 93 | 94 | if rs: 95 | target = batch[4] 96 | sd = batch[5] 97 | series = self.nametowidget(".").engine.get_series(batch[0], int(self.nametowidget(".").engine.get_elements())) 98 | mean = self.nametowidget(".").engine.get_mean(series) 99 | cv = self.nametowidget(".").engine.get_cv(series) 100 | ets = self.nametowidget(".").engine.get_te(target, mean, cv) 101 | x_labels = self.get_x_labels(rs) 102 | 103 | #compute upper and lower limits 104 | tea = self.nametowidget(".").engine.get_tea(self.cvw, self.cvb) 105 | x = self.nametowidget(".").engine.percentage(tea, target) 106 | y = self.nametowidget(".").engine.percentage(4, x) 107 | #print(x,y) 108 | upper_limit = round(target + (x+y), 2) 109 | lower_limit = round(target - (x+y), 2) 110 | 111 | 112 | self.set_axs(count, 113 | len(rs), 114 | target, 115 | sd, 116 | series, 117 | len(series), 118 | mean, 119 | self.nametowidget(".").engine.get_sd(series), 120 | cv, 121 | x_labels[0], 122 | x_labels[1], 123 | batch, 124 | tea, 125 | upper_limit, 126 | lower_limit, 127 | ets) 128 | count += 1 129 | 130 | 131 | self.canvas.draw() 132 | 133 | def get_x_labels(self, rs): 134 | 135 | x_labels = [] 136 | dates = [] 137 | 138 | rs = tuple(i for i in rs if i[4] != 0) 139 | 140 | if rs: 141 | for i in reversed(rs): 142 | x_labels.append(i[2]) 143 | dates.append(i[2]) 144 | 145 | return (x_labels, dates) 146 | 147 | 148 | def set_axs(self, count, count_rs, target, sd, series, count_series, 149 | compute_average, compute_sd, compute_cv, x_labels, dates, batch, 150 | tea, upper_limit, lower_limit, ets): 151 | 152 | 153 | #obj.clear() 154 | obj = self.fig.add_subplot(count, facecolor=('xkcd:light grey'),) 155 | obj.grid(True) 156 | 157 | lines = ([], [], [],) 158 | 159 | for i in range(len(series)+1): 160 | 161 | lines[0].append(upper_limit) 162 | lines[1].append(target) 163 | lines[2].append(lower_limit) 164 | 165 | 166 | #it's show time 167 | obj.set_xticks(range(0, len(series)+1)) 168 | obj.set_xticklabels(x_labels, rotation=70, size=6) 169 | obj.plot(series, marker="8", label='data') 170 | 171 | for x, y in enumerate(series): 172 | obj.text(x, y, str(y),) 173 | 174 | obj.plot(lines[0], color="violet", label='Tea +4', linestyle='--') 175 | obj.plot(lines[1], label='target', linewidth=2) 176 | obj.plot(lines[2], color="violet", label='Tea -4', linestyle='--') 177 | 178 | if self.um is not None: 179 | obj.set_ylabel(str(self.um[0])) 180 | else: 181 | obj.set_ylabel("No unit assigned yet") 182 | 183 | s = "Batch: {0} Target: {1} Upper: {2} Lower: {3} ETa%: {4:.2f} ET%: {5:.2f} Z Score: {6:.2f}" 184 | 185 | title = s.format(batch[2], 186 | batch[4], 187 | upper_limit, 188 | lower_limit, 189 | tea, 190 | ets, 191 | self.nametowidget(".").engine.get_zscore()) 192 | 193 | obj.set_title(title, loc='left') 194 | 195 | bottom_text = ("from %s to %s"%(dates[0], dates[-1]), count_series, count_rs) 196 | 197 | obj.text(0.95, 0.01, '%s computed %s on %s results'%bottom_text, 198 | verticalalignment='bottom', 199 | horizontalalignment='right', 200 | transform=obj.transAxes, 201 | color='black',) 202 | 203 | 204 | def get_um(self, unit_id): 205 | 206 | sql = "SELECT unit FROM units WHERE unit_id =?" 207 | return self.nametowidget(".").engine.read(False, sql, (unit_id,)) 208 | 209 | 210 | def on_cancel(self, evt=None): 211 | self.destroy() 212 | -------------------------------------------------------------------------------- /frames/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the test module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent, index=None): 11 | super().__init__(name="test") 12 | 13 | self.parent = parent 14 | self.index = index 15 | self.attributes("-topmost", True) 16 | self.transient(parent) 17 | self.resizable(0, 0) 18 | 19 | self.test = tk.StringVar() 20 | self.cvw = tk.DoubleVar() 21 | self.cvb = tk.DoubleVar() 22 | self.enable = tk.BooleanVar() 23 | self.vcmd = self.nametowidget(".").engine.get_validate_float(self) 24 | 25 | self.init_ui() 26 | self.nametowidget(".").engine.center_me(self) 27 | 28 | def init_ui(self): 29 | 30 | w = self.nametowidget(".").engine.get_init_ui(self) 31 | 32 | r = 0 33 | ttk.Label(w, text="Samples:").grid(row=r, sticky=tk.W) 34 | self.cbSamples = ttk.Combobox(w) 35 | self.cbSamples.grid(row=0, column=1, sticky=tk.W) 36 | 37 | r += 1 38 | ttk.Label(w, text="Units:").grid(row=r, sticky=tk.W) 39 | self.cbUnits = ttk.Combobox(w) 40 | self.cbUnits.grid(row=r, column=1, sticky=tk.W) 41 | 42 | r += 1 43 | ttk.Label(w, text="Test:").grid(row=r, sticky=tk.W) 44 | self.txTest = ttk.Entry(w, textvariable=self.test) 45 | self.txTest.grid(row=r, column=1, sticky=tk.W, padx=5, pady=5) 46 | 47 | r += 1 48 | ttk.Label(w, text="Cvw:").grid(row=r, sticky=tk.W) 49 | self.txtCVW = ttk.Entry(w, 50 | width=8, 51 | justify=tk.CENTER, 52 | validate='key', 53 | validatecommand=self.vcmd, 54 | textvariable=self.cvw) 55 | self.txtCVW.grid(row=r, column=1, sticky=tk.W, padx=5, pady=5) 56 | 57 | r += 1 58 | ttk.Label(w, text="Cvb:").grid(row=r, sticky=tk.W) 59 | self.txtCVB = ttk.Entry(w, 60 | width=8, 61 | justify=tk.CENTER, 62 | validate='key', 63 | validatecommand=self.vcmd, 64 | textvariable=self.cvb) 65 | self.txtCVB.grid(row=r, column=1, sticky=tk.W, padx=5, pady=5) 66 | 67 | r += 1 68 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 69 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable) 70 | chk.grid(row=r, column=1, sticky=tk.W) 71 | 72 | self.nametowidget(".").engine.get_save_cancel(self, w) 73 | 74 | 75 | def on_open(self, selected_item=None): 76 | 77 | self.set_samples() 78 | self.set_units() 79 | 80 | if self.index is not None: 81 | self.selected_item = selected_item 82 | what = "Edit {0}" 83 | self.set_values() 84 | else: 85 | what = "Add {0}" 86 | self.enable.set(1) 87 | 88 | msg = what.format(self.winfo_name().title()) 89 | self.title(msg) 90 | self.txTest.focus() 91 | 92 | def set_samples(self): 93 | 94 | index = 0 95 | values = [] 96 | self.dict_samples = {} 97 | 98 | sql = "SELECT sample_id, description FROM samples ORDER BY description ASC" 99 | rs = self.nametowidget(".").engine.read(True, sql, ()) 100 | 101 | for i in rs: 102 | self.dict_samples[index] = i[0] 103 | index += 1 104 | values.append(i[1]) 105 | 106 | self.cbSamples['values'] = values 107 | 108 | 109 | def set_units(self): 110 | 111 | index = 0 112 | values = [] 113 | self.dict_units = {} 114 | 115 | sql = "SELECT unit_id, unit FROM units ORDER BY unit ASC" 116 | rs = self.nametowidget(".").engine.read(True, sql, ()) 117 | 118 | for i in rs: 119 | self.dict_units[index] = i[0] 120 | index += 1 121 | values.append(i[1]) 122 | 123 | self.cbUnits['values'] = values 124 | 125 | def get_values(self,): 126 | 127 | return [self.dict_samples[self.cbSamples.current()], 128 | self.dict_units[self.cbUnits.current()], 129 | self.test.get(), 130 | self.cvw.get(), 131 | self.cvb.get(), 132 | self.enable.get(),] 133 | 134 | def set_values(self,): 135 | 136 | key = next(key for key, 137 | value in self.dict_samples.items() 138 | if value == self.selected_item[1]) 139 | self.cbSamples.current(key) 140 | 141 | key = next(key for key, 142 | value in self.dict_units.items() 143 | if value == self.selected_item[2]) 144 | self.cbUnits.current(key) 145 | 146 | self.test.set(self.selected_item[3]) 147 | self.cvw.set(self.selected_item[4]) 148 | self.cvb.set(self.selected_item[5]) 149 | self.enable.set(self.selected_item[6]) 150 | 151 | def on_save(self, evt=None): 152 | 153 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 154 | 155 | if messagebox.askyesno(self.nametowidget(".").title(), 156 | self.nametowidget(".").engine.ask_to_save, 157 | parent=self) == True: 158 | 159 | args = self.get_values() 160 | 161 | if self.index is not None: 162 | 163 | sql = self.nametowidget(".").engine.get_update_sql(self.parent.table, self.parent.field) 164 | 165 | args.append(self.selected_item[0]) 166 | 167 | else: 168 | 169 | sql = self.nametowidget(".").engine.get_insert_sql(self.parent.table, len(args)) 170 | 171 | last_id = self.nametowidget(".").engine.write(sql, args) 172 | self.parent.on_open() 173 | 174 | if self.index is not None: 175 | self.parent.lstItems.see(self.index) 176 | self.parent.lstItems.selection_set(self.index) 177 | else: 178 | #force focus on listbox 179 | idx = list(self.parent.dict_items.keys())[list(self.parent.dict_items.values()).index(last_id)] 180 | self.parent.lstItems.selection_set(idx) 181 | self.parent.lstItems.see(idx) 182 | 183 | self.on_cancel() 184 | 185 | 186 | def on_cancel(self, evt=None): 187 | self.destroy() 188 | 189 | -------------------------------------------------------------------------------- /frames/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the tests module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | import frames.test as ui 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent): 11 | super().__init__(name="tests") 12 | 13 | self.parent = parent 14 | self.attributes('-topmost', True) 15 | self.protocol("WM_DELETE_WINDOW", self.on_cancel) 16 | self.table = "tests" 17 | self.field = "test_id" 18 | self.obj = None 19 | self.init_ui() 20 | self.nametowidget(".").engine.center_me(self) 21 | 22 | def init_ui(self): 23 | 24 | f0 = self.nametowidget(".").engine.get_frame(self, 8) 25 | f1 = ttk.Frame(f0,) 26 | self.lstItems = self.nametowidget(".").engine.get_listbox(f1,) 27 | self.lstItems.bind("<>", self.on_item_selected) 28 | self.lstItems.bind("", self.on_item_activated) 29 | f1.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 30 | self.nametowidget(".").engine.get_add_edit_cancel(self, f0) 31 | f0.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 32 | 33 | def on_open(self,): 34 | self.title("Tests") 35 | self.set_values() 36 | 37 | def set_values(self): 38 | 39 | self.lstItems.delete(0, tk.END) 40 | index = 0 41 | self.dict_items = {} 42 | 43 | sql = "SELECT tests.test_id,tests.test||' '||samples.sample, tests.enable\ 44 | FROM tests\ 45 | INNER JOIN samples ON tests.sample_id = samples.sample_id\ 46 | ORDER BY tests.test" 47 | 48 | rs = self.nametowidget(".").engine.read(True, sql, ()) 49 | 50 | if rs: 51 | for i in rs: 52 | s = "{:}".format(i[1]) 53 | self.lstItems.insert(tk.END, s) 54 | if i[2] != 1: 55 | self.lstItems.itemconfig(index, {'bg':'light gray'}) 56 | self.dict_items[index] = i[0] 57 | index += 1 58 | 59 | 60 | def on_add(self, evt): 61 | 62 | self.obj = ui.UI(self) 63 | self.obj.on_open() 64 | 65 | 66 | def on_edit(self, evt): 67 | self.on_item_activated() 68 | 69 | 70 | def on_item_activated(self, evt=None): 71 | 72 | if self.lstItems.curselection(): 73 | index = self.lstItems.curselection()[0] 74 | self.obj = ui.UI(self, index) 75 | self.obj.on_open(self.selected_item,) 76 | 77 | else: 78 | messagebox.showwarning(self.master.title(), 79 | self.nametowidget(".").engine.no_selected, 80 | parent=self) 81 | 82 | 83 | def on_item_selected(self, evt): 84 | 85 | if self.lstItems.curselection(): 86 | index = self.lstItems.curselection()[0] 87 | pk = self.dict_items.get(index) 88 | self.selected_item = self.nametowidget(".").engine.get_selected(self.table, self.field, pk) 89 | 90 | 91 | def on_cancel(self, evt=None): 92 | 93 | if self.obj is not None: 94 | self.obj.destroy() 95 | self.destroy() 96 | -------------------------------------------------------------------------------- /frames/unit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the unit module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | 7 | 8 | 9 | class UI(tk.Toplevel): 10 | def __init__(self, parent, index=None): 11 | super().__init__(name="unit") 12 | 13 | self.parent = parent 14 | self.index = index 15 | self.attributes("-topmost", True) 16 | self.transient(parent) 17 | self.resizable(0, 0) 18 | 19 | self.unit = tk.StringVar() 20 | self.enable = tk.BooleanVar() 21 | 22 | self.init_ui() 23 | self.nametowidget(".").engine.center_me(self) 24 | 25 | def init_ui(self): 26 | 27 | w = self.nametowidget(".").engine.get_init_ui(self) 28 | 29 | r = 0 30 | c = 1 31 | 32 | ttk.Label(w, text="Unit:").grid(row=r, sticky=tk.W) 33 | self.txtUnit = ttk.Entry(w, textvariable=self.unit) 34 | self.txtUnit.grid(row=r, column=c, padx=5, pady=5) 35 | 36 | r += 1 37 | ttk.Label(w, text="Enable:").grid(row=r, sticky=tk.W) 38 | chk = ttk.Checkbutton(w, onvalue=1, offvalue=0, variable=self.enable,) 39 | chk.grid(row=r, column=c, sticky=tk.W) 40 | 41 | self.nametowidget(".").engine.get_save_cancel(self, w) 42 | 43 | def on_open(self, selected_item=None): 44 | 45 | if self.index is not None: 46 | self.selected_item = selected_item 47 | what = "Edit {0}" 48 | self.set_values() 49 | else: 50 | what = "Add {0}" 51 | self.enable.set(1) 52 | 53 | msg = what.format(self.winfo_name().title()) 54 | self.title(msg) 55 | self.txtUnit.focus() 56 | 57 | def set_values(self,): 58 | 59 | self.unit.set(self.selected_item[1]) 60 | self.enable.set(self.selected_item[2]) 61 | 62 | def get_values(self,): 63 | 64 | return [self.unit.get(), 65 | self.enable.get(),] 66 | 67 | def on_save(self, evt=None): 68 | 69 | if self.nametowidget(".").engine.on_fields_control(self) == False: return 70 | 71 | if messagebox.askyesno(self.nametowidget(".").title(), 72 | self.nametowidget(".").engine.ask_to_save, 73 | parent=self) == True: 74 | 75 | args = self.get_values() 76 | 77 | if self.index is not None: 78 | 79 | sql = self.nametowidget(".").engine.get_update_sql(self.parent.table, self.parent.field) 80 | 81 | args.append(self.selected_item[0]) 82 | 83 | else: 84 | 85 | sql = self.nametowidget(".").engine.get_insert_sql(self.parent.table, len(args)) 86 | 87 | last_id = self.nametowidget(".").engine.write(sql, args) 88 | self.parent.on_open() 89 | 90 | if self.index is not None: 91 | self.parent.lstItems.see(self.index) 92 | self.parent.lstItems.selection_set(self.index) 93 | else: 94 | #force focus on listbox 95 | idx = list(self.parent.dict_items.keys())[list(self.parent.dict_items.values()).index(last_id)] 96 | self.parent.lstItems.selection_set(idx) 97 | self.parent.lstItems.see(idx) 98 | 99 | self.on_cancel() 100 | 101 | def on_cancel(self, evt=None): 102 | self.destroy() 103 | -------------------------------------------------------------------------------- /frames/units.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the units module of Biovarase.""" 3 | import tkinter as tk 4 | from tkinter import ttk 5 | from tkinter import messagebox 6 | import frames.unit as ui 7 | 8 | 9 | 10 | class UI(tk.Toplevel): 11 | def __init__(self, parent): 12 | super().__init__(name='units') 13 | 14 | self.parent = parent 15 | self.attributes('-topmost', True) 16 | self.protocol("WM_DELETE_WINDOW", self.on_cancel) 17 | self.table = "units" 18 | self.field = "unit_id" 19 | self.obj = None 20 | self.init_ui() 21 | self.nametowidget(".").engine.center_me(self) 22 | 23 | def init_ui(self): 24 | 25 | f0 = self.nametowidget(".").engine.get_frame(self, 8) 26 | f1 = ttk.Frame(f0,) 27 | self.lstItems = self.nametowidget(".").engine.get_listbox(f1,) 28 | self.lstItems.bind("<>", self.on_item_selected) 29 | self.lstItems.bind("", self.on_item_activated) 30 | f1.pack(side=tk.LEFT, fill=tk.BOTH, padx=5, pady=5, expand=1) 31 | self.nametowidget(".").engine.get_add_edit_cancel(self, f0) 32 | f0.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 33 | 34 | def on_open(self,): 35 | self.title('Units') 36 | self.set_values() 37 | 38 | def set_values(self): 39 | 40 | self.lstItems.delete(0, tk.END) 41 | index = 0 42 | self.dict_items = {} 43 | sql = "SELECT * FROM {0}".format(self.table) 44 | rs = self.nametowidget(".").engine.read(True, sql, ()) 45 | 46 | if rs: 47 | for i in rs: 48 | self.lstItems.insert(tk.END, i[1]) 49 | if i[2] != 1: 50 | self.lstItems.itemconfig(index, {'bg':'light gray'}) 51 | self.dict_items[index] = i[0] 52 | index += 1 53 | 54 | def on_add(self, evt): 55 | 56 | self.obj = ui.UI(self) 57 | self.obj.on_open() 58 | 59 | 60 | def on_edit(self, evt): 61 | self.on_item_activated() 62 | 63 | 64 | def on_item_activated(self, evt=None): 65 | 66 | if self.lstItems.curselection(): 67 | index = self.lstItems.curselection()[0] 68 | self.obj = ui.UI(self, index) 69 | self.obj.on_open(self.selected_item,) 70 | 71 | else: 72 | messagebox.showwarning(self.master.title(), 73 | self.nametowidget(".").engine.no_selected, 74 | parent=self) 75 | 76 | 77 | def on_item_selected(self, evt): 78 | 79 | if self.lstItems.curselection(): 80 | index = self.lstItems.curselection()[0] 81 | pk = self.dict_items.get(index) 82 | self.selected_item = self.nametowidget(".").engine.get_selected(self.table, self.field, pk) 83 | 84 | 85 | def on_cancel(self, evt=None): 86 | 87 | if self.obj is not None: 88 | self.obj.destroy() 89 | self.destroy() 90 | -------------------------------------------------------------------------------- /frames/youden.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the youden module of Biovarase.""" 3 | 4 | import tkinter as tk 5 | 6 | from matplotlib.figure import Figure 7 | 8 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 9 | 10 | try: 11 | from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as nav_tool 12 | except: 13 | from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as nav_tool 14 | 15 | 16 | 17 | 18 | class UI(tk.Toplevel): 19 | def __init__(self, parent): 20 | super().__init__(name="youden") 21 | 22 | self.parent = parent 23 | self.obj = None 24 | self.batches = [] 25 | self.nametowidget(".").engine.center_me(self) 26 | self.init_ui() 27 | 28 | 29 | def init_ui(self): 30 | 31 | 32 | f0 = self.nametowidget(".").engine.get_frame(self) 33 | 34 | #create graph! 35 | #Figure: The top level container for all the plot elements. 36 | self.fig = Figure() 37 | #self.fig.subplots_adjust(bottom=0.10, right=0.98, left=0.10, top=0.88,wspace=0.08) 38 | self.fig.subplots_adjust(hspace=0.65, left=0.125, right=0.9) 39 | self.canvas = FigureCanvasTkAgg(self.fig, f0) 40 | toolbar = nav_tool(self.canvas, f0) 41 | toolbar.update() 42 | self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) 43 | 44 | f0.pack(fill=tk.BOTH, expand=1) 45 | 46 | 47 | def on_open(self, selected_test, batches, data): 48 | 49 | s = "Biovarase Youden Plot" 50 | 51 | title = s.format(selected_test[3]) 52 | 53 | self.fig.suptitle(title, fontsize=14) 54 | 55 | s = "Youden Plot %s"%selected_test[3] 56 | 57 | self.um = self.get_um(selected_test[2]) 58 | 59 | self.title(s) 60 | 61 | self.batches = batches 62 | 63 | self.set_axs(data) 64 | 65 | self.canvas.draw() 66 | 67 | 68 | def set_axs(self, data): 69 | 70 | first_sample_target = self.batches[0][4] 71 | second_sample_target = self.batches[1][4] 72 | 73 | first_sample_sd = self.batches[0][5] 74 | second_sample_sd = self.batches[1][5] 75 | 76 | x = data[0] 77 | y = data[1] 78 | 79 | z = list(zip(x, y)) 80 | 81 | first_sample_low_limit = first_sample_target - (first_sample_sd*5) 82 | first_sample_high_limit = first_sample_target + (first_sample_sd*5) 83 | 84 | second_sample_low_limit = second_sample_target - (second_sample_sd*5) 85 | second_sample_high_limit = second_sample_target + (second_sample_sd*5) 86 | 87 | obj = self.fig.add_subplot(111, facecolor=('xkcd:light grey'),) 88 | 89 | obj.grid(True) 90 | 91 | obj.set_xlim(first_sample_low_limit, first_sample_high_limit) 92 | 93 | obj.set_ylim(second_sample_low_limit, second_sample_high_limit) 94 | 95 | obj.axvline(x=first_sample_target, linewidth=2, color='orange') 96 | 97 | obj.axvline(x=first_sample_target+(first_sample_sd), ymin=0.4, ymax=0.6, linestyle='--', color='green') 98 | obj.axvline(x=first_sample_target-(first_sample_sd), ymin=0.4, ymax=0.6, linestyle='--', color='green') 99 | obj.axvline(x=first_sample_target+(first_sample_sd*2), ymin=0.3, ymax=0.7, linestyle='--', color='yellow') 100 | obj.axvline(x=first_sample_target-(first_sample_sd*2), ymin=0.3, ymax=0.7, linestyle='--', color='yellow') 101 | obj.axvline(x=first_sample_target+(first_sample_sd*3), ymin=0.2, ymax=0.8, linestyle='--', color='red') 102 | obj.axvline(x=first_sample_target-(first_sample_sd*3), ymin=0.2, ymax=0.8, linestyle='--', color='red') 103 | 104 | obj.axhline(y=second_sample_target, linewidth=2, color='orange') 105 | obj.axhline(y=second_sample_target+(second_sample_sd), xmin=0.4, xmax=0.6, linestyle='--', color='green') 106 | obj.axhline(y=second_sample_target-(second_sample_sd), xmin=0.4, xmax=0.6, linestyle='--', color='green') 107 | obj.axhline(y=second_sample_target+(second_sample_sd*2), xmin=0.3, xmax=0.7, linestyle='--', color='yellow') 108 | obj.axhline(y=second_sample_target-(second_sample_sd*2), xmin=0.3, xmax=0.7, linestyle='--', color='yellow') 109 | obj.axhline(y=second_sample_target+(second_sample_sd*3), xmin=0.2, xmax=0.8, linestyle='--', color='red') 110 | obj.axhline(y=second_sample_target-(second_sample_sd*3), xmin=0.2, xmax=0.8, linestyle='--', color='red') 111 | 112 | obj.scatter(x, y, marker='8', s=80) 113 | 114 | for i, txt in enumerate(z): 115 | obj.annotate(txt, (x[i], y[i]), size=8,) 116 | 117 | 118 | if self.um is not None: 119 | obj.set_ylabel(str(self.um[0])) 120 | obj.set_xlabel(str(self.um[0])) 121 | else: 122 | obj.set_ylabel("No unit assigned yet") 123 | obj.set_xlabel("No unit assigned yet") 124 | 125 | 126 | s = "Batch: {0} Target: {1:.2f} sd: {2:.2f} Batch: {3} Target: {4:.2f} sd: {5:.2f}" 127 | 128 | title = s.format(self.batches[0][2], 129 | first_sample_target, 130 | first_sample_sd, 131 | self.batches[1][2], 132 | second_sample_target, 133 | second_sample_sd) 134 | 135 | obj.set_title(title, loc='left') 136 | 137 | 138 | 139 | def get_um(self, unit_id): 140 | 141 | sql = "SELECT unit FROM units WHERE unit_id =?" 142 | return self.nametowidget(".").engine.read(False, sql, (unit_id,)) 143 | 144 | 145 | def on_cancel(self, evt=None): 146 | self.destroy() 147 | -------------------------------------------------------------------------------- /frames/zscore.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the zscore module of Biovarase.""" 3 | import tkinter as tk 4 | 5 | 6 | 7 | class UI(tk.Toplevel): 8 | def __init__(self, parent, index=None): 9 | super().__init__(name="zscore") 10 | 11 | self.parent = parent 12 | self.attributes("-topmost", True) 13 | self.transient(parent) 14 | self.resizable(0, 0) 15 | self.nametowidget(".").engine.center_me(self) 16 | self.init_ui() 17 | 18 | 19 | def init_ui(self): 20 | 21 | w = self.nametowidget(".").engine.get_init_ui(self) 22 | 23 | items = ("Z-Score", "2.33", "2.05", "0.75", "1.88", "1.75", "1.65") 24 | 25 | r = 0 26 | c = 0 27 | for i in items: 28 | tk.Label(w, text=i).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 29 | r += 1 30 | 31 | items = ("Probability", "p>0.01", "p>0.02", "p>0.03", "p>0.04", "p>0.05") 32 | 33 | r = 0 34 | c = 1 35 | for i in items: 36 | tk.Label(w, text=i).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 37 | r += 1 38 | 39 | items = ("Probability","99%", "98%", "97%", "96%", "95%") 40 | r = 0 41 | c = 2 42 | for i in items: 43 | tk.Label(w, text=i).grid(row=r, column=c, sticky=tk.W, padx=10, pady=10) 44 | r += 1 45 | 46 | 47 | def on_open(self,): 48 | 49 | self.title("Probability") 50 | 51 | 52 | def on_cancel(self, evt=None): 53 | self.destroy() 54 | -------------------------------------------------------------------------------- /launcher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the westgard launcher of Biovarase. 3 | It provides to perform the file opening.""" 4 | import os 5 | import subprocess 6 | import threading 7 | 8 | 9 | 10 | class Launcher: 11 | """This class is used for data export. 12 | """ 13 | 14 | def __str__(self): 15 | return "class: %s" % (self.__class__.__name__, ) 16 | 17 | 18 | def launch(self, path): 19 | 20 | threading.Thread(target=self.open_file(path), daemon=True).start() 21 | 22 | def open_file(self, path): 23 | 24 | if os.path.exists(path): 25 | if os.name == 'posix': 26 | subprocess.call(["xdg-open", path]) 27 | else: 28 | os.startfile(path) 29 | 30 | def main(): 31 | 32 | foo = Launcher() 33 | print(foo) 34 | input('end') 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /qc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ This is the qc module of Biovarase.""" 4 | 5 | import numpy as np 6 | import math 7 | import sys 8 | import inspect 9 | 10 | 11 | 12 | 13 | class QC: 14 | 15 | def __str__(self): 16 | return "class: %s" % (self.__class__.__name__, ) 17 | 18 | def get_sd(self, values): 19 | """Standard deviation is a statistic that quantifies how 20 | close numerical values are in relation to each other. 21 | The term precision is often used interchangeably with 22 | standard deviation. 23 | The repeatability of a test may be 24 | consistent (low standard deviation, low imprecision) 25 | or inconsistent (high standard deviation, high 26 | imprecision).""" 27 | 28 | a = np.array(values) 29 | 30 | return round(np.std(a, 31 | ddof=self.get_ddof(), 32 | dtype=np.float64), 2) 33 | 34 | 35 | def get_cv(self, values): 36 | """The Coefficient of Variation [CV] is the ratio of the 37 | standard deviation to the mean and is expressed 38 | as a percentage.""" 39 | 40 | sd = self.get_sd(values) 41 | mean = self.get_mean(values) 42 | 43 | return round((sd/mean)*100, 2) 44 | 45 | 46 | def get_mean(self, values): 47 | """To calculate a mean for a specific level of control, 48 | first, add all the values collected for that control. 49 | Then divide the sum of these values by the total 50 | number of values.""" 51 | a = np.array(values) 52 | return round(np.mean(a, 53 | dtype=np.float64), 2) 54 | 55 | 56 | def get_range(self, values): 57 | """Range is a measure of dispersion which is the difference between 58 | the largest and the smallest observed value of a quantitative 59 | characteristic in a given sample.""" 60 | 61 | return round(np.ptp(values), 2) 62 | 63 | 64 | def get_bias(self, avg, target): 65 | """Bias is the systematic, signed deviation of the test results 66 | from the accepted reference value.""" 67 | 68 | try: 69 | bias = abs(round(float((avg-target)/float(target))*100, 2)) 70 | except (ZeroDivisionError, ValueError, RuntimeWarning): 71 | bias = 0 72 | self.on_log(self, 73 | inspect.stack()[0][3], 74 | sys.exc_info()[1], 75 | sys.exc_info()[0], 76 | sys.modules[__name__]) 77 | return bias 78 | 79 | 80 | def get_cvt(self, cvw, cva): 81 | 82 | return round(math.sqrt(cva**2 + cvw**2), 2) 83 | 84 | 85 | def get_allowable_bias(self, cvw, cvb): 86 | """The specification for analytical bias.""" 87 | 88 | return abs(round(math.sqrt((math.pow(cvw, 2) + math.pow(cvb, 2)))*0.25, 2)) 89 | 90 | 91 | def get_te(self, target, avg, cv): 92 | """Compute Totale Error as Te = |Bias| + 1.65 * cv.""" 93 | 94 | bias = self.get_bias(avg, target) 95 | 96 | return round(bias + (self.get_zscore()*cv), 2) 97 | 98 | 99 | def get_tea(self, cvw, cvb): 100 | """Compute Totale Error Allowable.""" 101 | 102 | return round((self.get_zscore() * self.get_imp(cvw)) + self.get_allowable_bias(cvw, cvb), 2) 103 | 104 | 105 | def get_sigma(self, cvw, cvb, target, series): 106 | """Compute sigma.""" 107 | 108 | avg = self.get_mean(series) 109 | tea = self.get_tea(cvw, cvb) 110 | bias = self.get_bias(avg, target) 111 | cv = self.get_cv(series) 112 | sigma = (tea - bias)/cv 113 | 114 | return round(sigma, 2) 115 | 116 | 117 | def get_sce(self, cvw, cvb, target, series): 118 | """compute delta sistematic critical error.""" 119 | 120 | sigma = self.get_sigma(cvw, cvb, target, series) 121 | sce = round(sigma-1.65, 2) 122 | 123 | if sce > 3: 124 | c = "green" 125 | r = "sce >3" 126 | elif sce > 2 and sce <= 3: 127 | c = "yellow" 128 | r = "2> sce <3" 129 | elif sce < 2: 130 | c = "red" 131 | r = "sce <2" 132 | else: 133 | c = None 134 | return sce, c 135 | 136 | 137 | def get_tea_tes_comparision(self, avg, target, cvw, cvb, cva): 138 | 139 | """Confronting total error allowable vs total error instrumental 140 | 141 | x = Tea allowable, theoretical 142 | y = Tes instrumental, computed""" 143 | 144 | try: 145 | TEa = round(self.get_allowable_bias(cvw, cvb) + (self.get_zscore() * self.get_imp(cvw)), 2) 146 | Tes = self.get_te(target, avg, cva) 147 | 148 | if Tes < TEa: 149 | c = "green" 150 | r = "<" 151 | elif Tes == TEa: 152 | c = "yellow" 153 | r = "=" 154 | elif Tes > TEa: 155 | c = "red" 156 | r = ">" 157 | 158 | return Tes, c 159 | except ZeroDivisionError: 160 | return None 161 | 162 | 163 | def get_imp(self, cvw): 164 | """The Cotlove/Harris concept defines the maximum allowable imprecision, 165 | CVA,as the maximum imprecision, that when added to the within-subject 166 | biological variation, CVw, will maximimally increase the total CV by 12%, 167 | which is achieved when CVa <= 0.5*CVw""" 168 | 169 | return round(cvw*0.5, 2) 170 | 171 | 172 | def percentage(self, percent, whole): 173 | return (percent * whole) / 100.0 174 | 175 | 176 | def main(): 177 | 178 | foo = QC() 179 | print(foo) 180 | series = (4.0, 4.1, 4.0, 4.2, 4.1, 4.1, 4.2) 181 | mean = foo.get_mean(series) 182 | print("mean: {0}".format(mean)) 183 | input('end') 184 | 185 | if __name__ == "__main__": 186 | main() 187 | -------------------------------------------------------------------------------- /show_error_bar: -------------------------------------------------------------------------------- 1 | 0 -------------------------------------------------------------------------------- /test/init: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test_qc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | 5 | class UBT(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.series = (4.0, 4.1, 4.0, 4.2, 4.1, 4.1, 4.2) 9 | 10 | # Returns True or False. 11 | def test(self): 12 | self.assertTrue(True) 13 | 14 | def test_mean(self): 15 | x = self.get_mean(self.series) 16 | self.assertEqual(x, 4.1) 17 | 18 | def test_range(self): 19 | x = self.get_range(self.series) 20 | self.assertEqual(x, 0.2) 21 | 22 | def test_sd(self): 23 | x = self.get_sd(self.series) 24 | self.assertEqual(x, 0.08) 25 | 26 | def test_cv(self): 27 | x = self.get_cv(self.series) 28 | self.assertEqual(x, 1.95) 29 | 30 | def test_bias(self): 31 | x = self.get_bias(self.series,4) 32 | self.assertEqual(x, 2.5) 33 | 34 | def get_mean(self, values): 35 | """To calculate a mean for a specific level of control, 36 | first, add all the values collected for that control. 37 | Then divide the sum of these values by the total 38 | number of values.""" 39 | a = np.array(values) 40 | return round(np.mean(a, 41 | dtype = np.float64),2) 42 | 43 | def get_sd(self, values, ddof=0): 44 | """Standard deviation is a statistic that quantifies how 45 | close numerical values are in relation to each other. 46 | The term precision is often used interchangeably with 47 | standard deviation. 48 | The repeatability of a test may be 49 | consistent (low standard deviation, low imprecision) 50 | or inconsistent (high standard deviation, high 51 | imprecision).""" 52 | 53 | a = np.array(values) 54 | 55 | return round(np.std(a, 56 | ddof = ddof, 57 | dtype = np.float64),2) 58 | 59 | def get_cv(self, values, ddof=0): 60 | """The Coefficient of Variation [CV] is the ratio of the 61 | standard deviation to the mean and is expressed 62 | as a percentage.""" 63 | 64 | sd = self.get_sd(values, ddof) 65 | mean = self.get_mean(values) 66 | return round((sd/mean)*100,2) 67 | 68 | 69 | def get_range(self, values): 70 | """Range is a measure of dispersion which is the difference between 71 | the largest and the smallest observed value of a quantitative 72 | characteristic in a given sample.""" 73 | a = np.array(values) 74 | return round(np.ptp(a),2) 75 | 76 | def get_bias(self, values, target): 77 | """Bias is the systematic, signed deviation of the test results 78 | from the accepted reference value.""" 79 | 80 | return abs(round(float((self.get_mean(values)-target)/float(target))*100,2)) 81 | 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ This is the tools module of Biovarase. 4 | It helps to draw the windwos.""" 5 | import datetime 6 | import tkinter as tk 7 | from tkinter import ttk 8 | from tkinter import messagebox 9 | from tkinter import font 10 | from tkinter.scrolledtext import ScrolledText 11 | 12 | 13 | class Tools: 14 | 15 | def __str__(self): 16 | return "class: %s" % (self.__class__.__name__, ) 17 | 18 | 19 | def get_rgb(self, r, g, b): 20 | """translates an rgb tuple of int to a tkinter friendly color code""" 21 | return "#%02x%02x%02x" % (r, g, b) 22 | 23 | def center_me(self, container): 24 | 25 | """center window on the screen""" 26 | x = (container.winfo_screenwidth() - container.winfo_reqwidth()) / 2 27 | y = (container.winfo_screenheight() - container.winfo_reqheight()) / 2 28 | container.geometry("+%d+%d" % (x, y)) 29 | 30 | 31 | def cols_configure(self, w): 32 | 33 | w.columnconfigure(0, weight=0) 34 | w.columnconfigure(1, weight=0) 35 | w.columnconfigure(2, weight=1) 36 | 37 | w.rowconfigure(0, weight=0) 38 | w.rowconfigure(1, weight=0) 39 | w.rowconfigure(2, weight=1) 40 | 41 | def get_init_ui(self, container): 42 | """All insert,update modules have this same configuration on init_ui. 43 | A Frame, a columnconfigure and a grid method. 44 | So, why rewrite every time?""" 45 | w = self.get_frame(container) 46 | self.cols_configure(w) 47 | w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E, ipadx=4, ipady=4) 48 | 49 | return w 50 | 51 | def get_frame(self, container, padding=None): 52 | return ttk.Frame(container, padding=padding) 53 | 54 | 55 | def get_label_frame(self, container, text=None, ): 56 | return ttk.LabelFrame(container, text=text,) 57 | 58 | def get_button(self, container, text, underline=0, row=None, col=None): 59 | 60 | w = ttk.Button(container, text=text, underline=underline) 61 | 62 | if row is not None: 63 | w.grid(row=row, column=col, sticky=tk.N+tk.W+tk.E, padx=5, pady=5) 64 | else: 65 | w.pack(fill=tk.X, padx=5, pady=5) 66 | 67 | return w 68 | 69 | 70 | def get_label(self, container, text, textvariable=None, anchor=None, args=()): 71 | 72 | w = ttk.Label(container, 73 | text=text, 74 | textvariable=textvariable, 75 | anchor=anchor) 76 | 77 | if args: 78 | w.grid(row=args[0], column=args[1], sticky=args[2]) 79 | else: 80 | w.pack(fill=tk.X, padx=5, pady=5) 81 | 82 | return w 83 | 84 | def get_spin_box(self, container, text, frm, to, width, var=None, callback=None): 85 | 86 | w = self.get_label_frame(container, text=text,) 87 | 88 | tk.Spinbox(w, 89 | bg='white', 90 | from_=frm, 91 | to=to, 92 | justify=tk.CENTER, 93 | width=width, 94 | wrap=False, 95 | insertwidth=1, 96 | textvariable=var).pack(anchor=tk.CENTER) 97 | return w 98 | 99 | 100 | 101 | def set_font(self, family, size, weight=None): 102 | 103 | if weight is not None: 104 | weight = weight 105 | else: 106 | weight = tk.NORMAL 107 | 108 | return font.Font(family=family, size=size, weight=weight) 109 | 110 | def get_listbox(self, container, height=None, width=None): 111 | 112 | sb = ttk.Scrollbar(container, orient=tk.VERTICAL) 113 | 114 | w = tk.Listbox(container, 115 | relief=tk.GROOVE, 116 | selectmode=tk.EXTENDED, 117 | exportselection=0, 118 | height=height, 119 | width=width, 120 | background='white', 121 | font='TkFixedFont', 122 | yscrollcommand=sb.set,) 123 | 124 | sb.config(command=w.yview) 125 | 126 | w.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 127 | sb.pack(fill=tk.Y, expand=1) 128 | 129 | return w 130 | 131 | def get_text_box(self, container, height=None, width=None, row=None, col=None): 132 | 133 | w = ScrolledText(container, 134 | bg='white', 135 | relief=tk.GROOVE, 136 | height=height, 137 | width=width, 138 | font='TkFixedFont',) 139 | 140 | if row is not None: 141 | #print(row,col) 142 | w.grid(row=row, column=1, sticky=tk.W) 143 | else: 144 | w.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 145 | 146 | return w 147 | 148 | def get_save_cancel(self, caller, container): 149 | 150 | caller.btnSave = self.get_button(container, "Save",0, 0, 2) 151 | caller.btnSave.bind("", caller.on_save) 152 | caller.btnSave.bind("", caller.on_save) 153 | caller.btCancel = self.get_button(container, "Close",0, 1, 2) 154 | caller.btCancel.bind("", caller.on_cancel) 155 | 156 | caller.bind("", caller.on_save) 157 | caller.bind("", caller.on_cancel) 158 | 159 | 160 | def get_export_cancel(self, caller, container): 161 | 162 | w = self.get_frame(container, 5) 163 | 164 | caller.btnExport = self.get_button(w, "Export",0, 0, 1,) 165 | caller.btnExport.bind("", caller.on_export) 166 | caller.btnExport.bind("", caller.on_export) 167 | 168 | caller.btCancel = self.get_button(w, "Close",0, 1, 1) 169 | caller.btCancel.bind("", caller.on_cancel) 170 | 171 | caller.bind("", caller.on_export) 172 | caller.bind("", caller.on_cancel) 173 | 174 | w.grid(row=0, column=2, sticky=tk.N+tk.E, padx=5, pady=5) 175 | 176 | 177 | def get_save_cancel_delete(self, caller, container): 178 | 179 | caller.btnSave = self.get_button(container, "Save",0, 0, 2) 180 | caller.btnSave.bind("", caller.on_save) 181 | caller.btnSave.bind("", caller.on_save) 182 | 183 | caller.btDelete = self.get_button(container, "Delete",0, 1, 2) 184 | caller.btDelete.bind("", caller.on_delete) 185 | 186 | caller.btCancel = self.get_button(container, "Close",0, 2, 2) 187 | caller.btCancel.bind("", caller.on_cancel) 188 | 189 | caller.bind("", caller.on_save) 190 | caller.bind("", caller.on_delete) 191 | caller.bind("", caller.on_cancel) 192 | 193 | 194 | def get_add_edit_cancel(self, caller, container): 195 | 196 | caller.btnAdd = self.get_button(container, "Add") 197 | caller.btnAdd.bind("", caller.on_add) 198 | caller.btnAdd.bind("", caller.on_add) 199 | caller.btnEdit = self.get_button(container, "Edit") 200 | caller.btnEdit.bind("", caller.on_edit) 201 | caller.btCancel = self.get_button(container, "Close") 202 | caller.btCancel.bind("", caller.on_cancel) 203 | 204 | caller.bind("", caller.on_add) 205 | caller.bind("", caller.on_edit) 206 | caller.bind("", caller.on_cancel) 207 | 208 | 209 | def on_fields_control(self, container): 210 | 211 | msg = "Please fill all fields." 212 | 213 | for w in container.winfo_children(): 214 | for field in w.winfo_children(): 215 | if type(field) in(ttk.Entry, ttk.Combobox): 216 | #print(type(field),) 217 | #for i in field.keys(): 218 | # print (i) 219 | if not field.get(): 220 | messagebox.showwarning(self.title, msg, parent=container) 221 | field.focus() 222 | return 0 223 | elif type(field) == ttk.Combobox: 224 | if field.get() not in field.cget('values'): 225 | msg = "You can choice only values in the list." 226 | messagebox.showwarning(self.title, msg, parent=container) 227 | field.focus() 228 | return 0 229 | 230 | def get_tree(self, container, cols, size=None, show=None): 231 | 232 | 233 | #this is a patch because with tkinter version with Tk 8.6.9 the color assignment with tags dosen't work 234 | #https://bugs.python.org/issue36468 235 | style = ttk.Style() 236 | style.map('Treeview', foreground=self.fixed_map('foreground'), background=self.fixed_map('background')) 237 | 238 | 239 | ttk.Style().configure("Treeview.Heading", background=self.get_rgb(240, 240, 237)) 240 | ttk.Style().configure("Treeview.Heading", font=('Helvetica', 10)) 241 | 242 | headers = [] 243 | 244 | for col in cols: 245 | headers.append(col[1]) 246 | del headers[0] 247 | 248 | if show is not None: 249 | w = ttk.Treeview(container, show=show) 250 | 251 | else: 252 | w = ttk.Treeview(container,) 253 | 254 | 255 | w['columns'] = headers 256 | w.tag_configure('is_enable', background='light gray') 257 | 258 | for col in cols: 259 | w.heading(col[0], text=col[1], anchor=col[2],) 260 | w.column(col[0], anchor=col[2], stretch=col[3], minwidth=col[4], width=col[5]) 261 | 262 | sb = ttk.Scrollbar(container) 263 | sb.configure(command=w.yview) 264 | w.configure(yscrollcommand=sb.set) 265 | 266 | w.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 267 | sb.pack(fill=tk.Y, expand=1) 268 | 269 | return w 270 | 271 | def fixed_map(self, option): 272 | 273 | style = ttk.Style() 274 | # Fix for setting text colour for Tkinter 8.6.9 275 | # From: https://core.tcl.tk/tk/info/509cafafae 276 | # 277 | # Returns the style map for 'option' with any styles starting with 278 | # ('!disabled', '!selected', ...) filtered out. 279 | 280 | # style.map() returns an empty list for missing options, so this 281 | # should be future-safe. 282 | return [elm for elm in style.map('Treeview', query_opt=option) if 283 | elm[:2] != ('!disabled', '!selected')] 284 | 285 | 286 | def get_validate_integer(self, caller): 287 | return (caller.register(self.validate_integer), '%d', '%P', '%S') 288 | 289 | def get_validate_float(self, caller): 290 | return (caller.register(self.validate_float), '%d', '%P', '%S') 291 | 292 | 293 | def validate_integer(self, action, value_if_allowed, text,): 294 | # action=1 -> insert 295 | if action == '1': 296 | if text in '0123456789': 297 | try: 298 | int(value_if_allowed) 299 | return True 300 | except ValueError: 301 | return False 302 | else: 303 | return False 304 | else: 305 | return True 306 | 307 | def validate_float(self, action, value_if_allowed, text,): 308 | # action=1 -> insert 309 | if action == '1': 310 | if text in '0123456789.': 311 | try: 312 | float(value_if_allowed) 313 | return True 314 | except ValueError: 315 | return False 316 | else: 317 | return False 318 | else: 319 | return True 320 | 321 | def main(): 322 | 323 | foo = Tools() 324 | print(foo) 325 | input('end') 326 | 327 | if __name__ == "__main__": 328 | main() 329 | -------------------------------------------------------------------------------- /westgards.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ This is the westgard module of Biovarase. 3 | It provides to perform the westgard's rules calculations.""" 4 | 5 | 6 | 7 | class Westgards: 8 | 9 | def get_westgard_violation_rule(self, target, sd, series, selected_batch=None, selected_test=None): 10 | """This function recive target, sd and a value series 11 | to compute westgard violtetion rule. 12 | 13 | @param name: target and sd of the selected batch, series are 14 | a list of reversed results of the relative batch 15 | @return: westgard rule 16 | @rtype: string 17 | """ 18 | 19 | self.target = target 20 | self.sd = sd 21 | self.series = series 22 | self.selected_batch = selected_batch 23 | self.selected_test = selected_test 24 | 25 | self.get_standard_deviations(target, sd) 26 | 27 | if self.get_rule_12S(): 28 | if self.get_rule_13S(): 29 | return "1:3S" 30 | elif self.get_rule_22S(): 31 | return "2:2S" 32 | elif self.get_rule_R4S(): 33 | return "R:4S" 34 | elif self.get_rule_41S(): 35 | return "4:1S" 36 | elif self.get_rule_10X(): 37 | return "10:X" 38 | else: 39 | return "1:2S" 40 | else: 41 | if self.get_rule_41S(): 42 | return "4:1S" 43 | else: 44 | if self.get_rule_10X(): 45 | return "10:x" 46 | else: 47 | return "Accept" 48 | 49 | 50 | def get_standard_deviations(self, target, sd): 51 | 52 | self.sd1 = target + sd 53 | self.sd2 = target + (2*sd) 54 | self.sd3 = target + (3*sd) 55 | self.sd_1 = target - sd 56 | self.sd_2 = target - (2*sd) 57 | self.sd_3 = target - (3*sd) 58 | self.sd4 = 4*sd 59 | 60 | 61 | def get_rule_12S(self,): 62 | """Control data start here. 63 | 1:2s 64 | Check if the control limits are set as the mean plus/minus 2s 65 | +/- > 2sd 66 | If false the value is in control, otherwise we continue the evaluation. 67 | Refers to the control rule that is commonly used with a Levey-Jennings chart. 68 | 69 | @param name: 70 | @return: westgard rule 71 | @rtype: string 72 | """ 73 | #print ("Westgard rule 1:2s tested") 74 | #print(self.series[-1]) 75 | 76 | return self.series[-1] > self.sd2 or self.series[-1] < self.sd_2 77 | 78 | def get_rule_13S(self): 79 | """1:3s 80 | Check if the control limits are set as the mean plus 3s and the mean minus 3s. 81 | A run is rejected when a single control measurement exceeds the mean plus 3s 82 | or the mean minus 3s 83 | control limit. 84 | +/- > 3sd""" 85 | 86 | #print ("Westgard rule 1:3s tested") 87 | 88 | return self.series[-1] > self.sd3 or self.series[-1] < self.sd_3 89 | 90 | 91 | def get_rule_22S(self): 92 | """2:2s: 93 | check if 2 consecutive control measurements exceed 94 | the same mean plus 2s or the same mean minus 2s control limit. """ 95 | 96 | #print ("Westgard rule 2:2s tested") 97 | 98 | last_two_values = self.series[-2:] 99 | 100 | 101 | x = (all(i >= self.sd2 for i in last_two_values)) 102 | y = (all(i <= self.sd_2 for i in last_two_values)) 103 | 104 | if x or y: 105 | return True 106 | else: 107 | return False 108 | 109 | def get_rule_R4S(self): 110 | """R:4s 111 | Check if 1 control measurement in a group exceeds the mean plus 2s and another 112 | exceeds the mean minus 2s. 113 | This rule should only be interpreted within-run, not between-run. """ 114 | 115 | #print ("Westgard rule R:4s tested") 116 | 117 | last_two_values = self.series[-2:] 118 | 119 | a = min(last_two_values) 120 | b = max(last_two_values) 121 | value = b - a 122 | 123 | if value >= (self.sd4): 124 | return True 125 | else: 126 | return False 127 | 128 | def get_rule_41S(self): 129 | """4:1s 130 | Check if 4 consecutive control measurements 131 | exceed the same mean plus 1s or the same mean minus 1s control limit. """ 132 | 133 | #print ("Westgard rule 4:1s tested") 134 | 135 | 136 | last_four_values = self.series[-4:] 137 | 138 | x = (all(i > self.sd1 for i in last_four_values)) 139 | y = (all(i < self.sd_1 for i in last_four_values)) 140 | 141 | if x or y: 142 | return True 143 | else: 144 | return False 145 | 146 | def get_rule_10X(self): 147 | """10:x 148 | Check when 10 consecutive control measurements fall on one side of the mean.""" 149 | #print ("Westgard rule 10:x tested") 150 | 151 | last_ten_values = self.series[-10:] 152 | 153 | x = (all(i > self.target for i in last_ten_values)) 154 | y = (all(i < self.target for i in last_ten_values)) 155 | 156 | if x or y: 157 | return True 158 | else: 159 | return False 160 | 161 | 162 | def main(): 163 | 164 | foo = Westgards() 165 | print(foo) 166 | 167 | #(target, sd, series) 168 | target = 100 169 | sd = 10 170 | series = (100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 121) 171 | rule = foo.get_westgard_violation_rule(target, sd, series) 172 | series = (100, 100, 110, 110, 110, 110, 111, 111, 111, 111, 111) 173 | rule = foo.get_westgard_violation_rule(target, sd, series) 174 | print(rule) 175 | 176 | input('end') 177 | 178 | if __name__ == "__main__": 179 | main() 180 | 181 | 182 | -------------------------------------------------------------------------------- /zscore: -------------------------------------------------------------------------------- 1 | 1.65 --------------------------------------------------------------------------------