#! /usr/bin/python3
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+Start the application
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
-# This file is part of DDStorm.
+This file is part of DDStorm.
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
-import sys, time, subprocess
-from PyQt4 import QtGui, QtCore
-from _symptoms import Symptoms
-from _differentials import Differentials
-from ddstorm import DDStorm
-from conf import Conf
-from _extras import *
-from const import *
-
-conf=False
-
-class Content(QtGui.QWidget):
- change=QtCore.pyqtSignal()
- def __init__(self):
- super(Content, self).__init__()
- self.dd=DDStorm(True,conf)
- if(not self.dd.compiler.clean):
- ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log")
- if(ret==1):
- x_logfile()
- self.initUI()
- def initUI(self):
- global conf
-
- grid=QtGui.QGridLayout()
- self.setLayout(grid)
-
- self.symp=Symptoms(self.dd.symptoms())
- self.symp.setFrameShape(QtGui.QFrame.StyledPanel)
- self.symp.changed.connect(self.update)
-
- self.diff=Differentials()
- self.diff.setFrameShape(QtGui.QFrame.StyledPanel)
-
- grid.addWidget(self.symp, 0, 0)
- grid.addWidget(self.diff, 0, 1)
- grid.setColumnStretch(0, 1)
- grid.setColumnStretch(1, 1)
- QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
- def update(self, data):
- self.diff.update(self.dd.dd(data))
- self.change.emit()
-
-class Window(QtGui.QMainWindow):
- def __init__(self):
- super(Window, self).__init__()
- self.initUI()
- def initUI(self):
- global conf
- self.con=Content()
- self.sett=SettingsDialog(conf)
- if(conf.get("status_message")=="on"):
- self.con.change.connect(self.showStatus)
-
- menu=self.menuBar()
- menuFile=menu.addMenu("&File")
- menuFile.addAction("&Save").triggered.connect(self.savefile)
- menuFile.addAction("E&xit").triggered.connect(self.close)
- menuEdit=menu.addMenu("&Edit")
- menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem)
- menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms)
- rmAction=QtGui.QAction("&Remove", self)
- rmAction.setShortcut("Delete")
- rmAction.triggered.connect(self.con.symp.remove)
- menuEdit.addAction(rmAction)
- menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll)
- menuTool=menu.addMenu("&Tools")
- menuTool.addAction("&Library").triggered.connect(x_lib)
- menuTool.addAction("&Settings").triggered.connect(self.settings)
- menuTool.addAction("&View Log").triggered.connect(x_logfile)
- menuHelp=menu.addMenu("&Help")
- menuHelp.addAction("&Help").triggered.connect(x_help)
- menuHelp.addAction("&About").triggered.connect(self.about)
-
- self.setCentralWidget(self.con)
- self.status=self.statusBar()
- self.setGeometry(200, 200, 600, 400)
- self.setWindowTitle("D/D Storm")
- self.setWindowIcon(QtGui.QIcon("icons/icon.png"))
- self.showMaximized()
- self.con.symp.new.setFocus()
- def showStatus(self):
- if(self.con.symp.getList() and self.con.diff.getList()):
- self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).")
- else:
- self.status.showMessage("")
- def savefile(self):
- x_save(self, self.con.symp.getList(), self.con.diff.getList())
- def settings(self):
- self.sett.exec_()
- def about(self):
- QtGui.QMessageBox.about(self, "About", "<h1>DDStorm</h1>\nBrainstorm Medicine")
-
-def main():
- app=QtGui.QApplication(sys.argv)
-
- global conf
- conf=Conf()
- if(conf.get("clean_log")=="yes"):
- open(LOG_FILE, "w").close()
- if(conf.get("splash_screen")=="yes"):
- ss=True
- else:
- ss=False
- if(ss):
- splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png"))
- splash.show()
- time.sleep(0.1)
- app.processEvents()
- splash.showMessage("Loading...")
-
- w=Window()
- if(ss):
- splash.finish(w)
-
- sys.exit(app.exec_())
+import gui
if(__name__=="__main__"):
- main()
+ gui.main()
+++ /dev/null
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
-
-from PyQt4 import QtGui
-
-class Differentials(QtGui.QFrame):
- data=[]
- def __init__(self):
- super(Differentials, self).__init__()
- self.initUI()
- def initUI(self):
- self.label=QtGui.QLabel("Differential Diagnosis")
- self.label.setStyleSheet("font-size:18px")
- self.listWidget=QtGui.QListWidget(self)
- self.listWidget.setStyleSheet("font-size:14px")
- self.listWidget.setSelectionMode(0)
- box=QtGui.QVBoxLayout()
- box.addWidget(self.label)
- box.addWidget(self.listWidget)
- self.setLayout(box)
-
- def update(self, data):
- self.data=data
- self.listWidget.clear()
- if(self.data):
- for d in self.data:
- QtGui.QListWidgetItem(d, self.listWidget)
-
- def getList(self):
- return self.data
+++ /dev/null
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
-
-import subprocess, os
-from PyQt4 import QtGui, QtCore
-from const import *
-
-def x_settings():
- subprocess.Popen(["xdg-open", CONF_FILE])
-
-def x_lib():
- if(os.path.isfile(CONF_FILE)):
- with open(CONF_FILE) as conf:
- for line in conf:
- if(line.startswith("library_path=")):
- library_path=line[13:-1]
- if(os.path.isdir(library_path)):
- subprocess.Popen(["xdg-open", library_path])
-
-def x_save(w, symp, diff):
- fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')")
- if(not fname.endswith(".html")):
- fname=fname+".html"
- with open(fname, "w") as f:
- print("<!DOCTYPE html><html><head><title>Differential Diagnosis</title></head>", file=f)
- print("<body><h1>Differential Diagnosis</h1>", file=f)
- print("<table style='width:100%'>", file=f)
- print("<tr><th>Symptoms</th><th>Diffrential Diagnosis</th></tr>", file=f)
- print("<tr><td style='vertical-align:text-top'><ol>", file=f)
- for s in symp:
- print("<li>"+s+"</li>", file=f)
- print("</ol></td><td style='vertical-align:text-top'><ol>", file=f)
- for d in diff:
- print("<li>"+d+"</li>", file=f)
- print("</ol></td></tr></table></body></html>", file=f)
-
-def x_help():
- if(os.path.isfile(HELP_FILE)):
- subprocess.Popen(["xdg-open", HELP_FILE])
- else:
- subprocess.Popen(["xdg-open", "http://www.agnibho.com"])
-
-def x_logfile():
- subprocess.Popen(["xdg-open", LOG_FILE])
-
-class SettingsDialog(QtGui.QDialog):
- def __init__(self, conf):
- super(SettingsDialog, self).__init__()
- self.setWindowTitle("Settings")
- self.conf=conf
- self.initUI()
- def initUI(self):
- self.lpLabel=QtGui.QLabel("Libary Path:")
- self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path"))
- self.lpBrowse=QtGui.QPushButton("Browse")
- self.lpBrowse.clicked.connect(self.lpUpdate)
- self.mpLabel=QtGui.QLabel("Module Path:")
- self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path"))
- self.mpBrowse=QtGui.QPushButton("Browse")
- self.mpBrowse.clicked.connect(self.mpUpdate)
- self.splash=QtGui.QCheckBox("Show Splash Screen")
- if(self.conf.get("splash_screen")=="yes"):
- self.splash.setChecked(True)
- self.clean=QtGui.QCheckBox("Clean Log on Exit")
- if(self.conf.get("clean_log")=="yes"):
- self.clean.setChecked(True)
- self.status=QtGui.QCheckBox("Show Status Message")
- if(self.conf.get("status_message")=="on"):
- self.status.setChecked(True)
- self.ok=QtGui.QPushButton("Ok")
- self.ok.clicked.connect(self.save)
- self.cancel=QtGui.QPushButton("Cancel")
- self.cancel.clicked.connect(self.close)
- self.default=QtGui.QPushButton("Default")
- self.default.clicked.connect(self.reset)
-
- ctrl=QtGui.QHBoxLayout()
- ctrl.addWidget(self.ok)
- ctrl.addWidget(self.cancel)
- ctrl.addWidget(self.default)
- layout=QtGui.QGridLayout(self)
- layout.addWidget(self.lpLabel, 0, 0)
- layout.addWidget(self.lpEdit, 0, 1)
- layout.addWidget(self.lpBrowse, 0, 2)
- layout.addWidget(self.mpLabel, 1, 0)
- layout.addWidget(self.mpEdit, 1, 1)
- layout.addWidget(self.mpBrowse, 1, 2)
- layout.addWidget(self.splash, 2, 0)
- layout.addWidget(self.clean, 3, 0)
- layout.addWidget(self.status, 4, 0)
- layout.addLayout(ctrl, 5, 1)
-
- self.cancel.setFocus()
-
- def lpUpdate(self):
- self.lpEdit.setText(self.getFolder())
- def cpUpdate(self):
- self.cpEdit.setText(self.getFolder())
- def mpUpdate(self):
- self.mpEdit.setText(self.getFolder())
-
- def getFolder(self):
- dn=QtGui.QFileDialog.getExistingDirectory()
- if(dn.startswith(QtCore.QDir.currentPath())):
- dn="."+dn[len(QtCore.QDir.currentPath()):]+"/"
- else:
- dn=dn+"/"
- return dn
-
- def save(self):
- self.conf.set("library_path", self.lpEdit.text())
- self.conf.set("class_path", self.cpEdit.text())
- self.conf.set("module_path", self.mpEdit.text())
- if(self.splash.isChecked()):
- self.conf.set("splash_screen", "yes")
- else:
- self.conf.set("splash_screen", "no")
- if(self.clean.isChecked()):
- self.conf.set("clean_log", "yes")
- else:
- self.conf.set("clean_log", "no")
- if(self.status.isChecked()):
- self.conf.set("status_message", "on")
- else:
- self.conf.set("status_message", "off")
- QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm")
- self.close()
- self.conf.write()
-
- def reset(self):
- self.conf.default()
- self.lpEdit.setText(self.conf.get("library_path"))
- self.cpEdit.setText(self.conf.get("class_path"))
- self.mpEdit.setText(self.conf.get("module_path"))
- if(self.conf.get("splash_screen")=="yes"):
- self.splash.setChecked(True)
- else:
- self.splash.setChecked(False)
- if(self.conf.get("clean_log")=="yes"):
- self.clean.setChecked(True)
- else:
- self.clean.setChecked(False)
- if(self.conf.get("status_message")=="on"):
- self.status.setChecked(True)
- else:
- self.status.setChecked(False)
+++ /dev/null
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
-
-from PyQt4 import QtGui, QtCore
-
-class Symptoms(QtGui.QFrame):
- sympList=[]
- changed=QtCore.pyqtSignal(list)
- def __init__(self, auto):
- super(Symptoms, self).__init__()
- self.auto=auto
- self.initUI()
-
- def initUI(self):
- self.label=QtGui.QLabel("Symptoms")
- self.label.setStyleSheet("font-size:18px")
-
- self.listWidget=QtGui.QListWidget(self)
- self.listWidget.setStyleSheet("font-size:14px")
- self.listWidget.setSelectionMode(4)
-
- self.rm=QtGui.QPushButton("Remove")
- self.rm.clicked.connect(self.remove)
-
- self.cl=QtGui.QPushButton("Clear All")
- self.cl.clicked.connect(self.removeAll)
-
- self.browse=QtGui.QPushButton("Browse Symptoms")
- self.browse.clicked.connect(self.browseSymptoms)
-
- self.add=QtGui.QPushButton("Add")
- self.add.clicked.connect(self.addItem)
-
- self.new=QtGui.QLineEdit(self)
- self.new.returnPressed.connect(self.addItem)
- self.completer=QtGui.QCompleter(self.auto)
- self.completer.setCaseSensitivity(0)
- self.completer.setCompletionMode(2)
- self.new.setCompleter(self.completer)
-
- self.browser=SymptomBrowser(self.auto)
- self.browser.added.connect(self.addItem)
-
- hboxt=QtGui.QHBoxLayout()
- hboxt.addWidget(self.new)
- hboxt.addWidget(self.add)
- hboxb=QtGui.QHBoxLayout()
- hboxb.addWidget(self.rm)
- hboxb.addWidget(self.cl)
- hboxb.addWidget(self.browse)
- vbox=QtGui.QVBoxLayout()
- vbox.addWidget(self.label)
- vbox.addLayout(hboxt)
- vbox.addWidget(self.listWidget)
- vbox.addLayout(hboxb)
- self.setLayout(vbox)
-
- def addItem(self, text=""):
- if(not text):
- text=self.new.text()
- if(len(text)>0):
- if(text in self.sympList):
- QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.")
- elif(text in self.auto):
- QtGui.QListWidgetItem(text, self.listWidget)
- self.sympList.append(text)
- self.new.clear()
- self.changed.emit(list(self.sympList))
- else:
- QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.")
-
- def remove(self, all=False):
- if(len(self.listWidget.selectedItems())>0):
- for item in self.listWidget.selectedItems():
- self.sympList.remove(item.text())
- self.listWidget.takeItem(self.listWidget.row(item))
- self.changed.emit(list(self.sympList))
-
- def removeAll(self):
- self.listWidget.clear()
- del self.sympList[:]
- self.changed.emit(list(self.sympList))
-
- def browseSymptoms(self):
- self.browser.exec_()
-
- def getList(self):
- return self.sympList
-
-class SymptomBrowser(QtGui.QDialog):
- added=QtCore.pyqtSignal(str)
- def __init__(self, items):
- super(SymptomBrowser, self).__init__()
- self.setWindowTitle("Choose Symptom")
- self.items=items
- self.initUI()
-
- def initUI(self):
- self.search=QtGui.QLineEdit()
- self.search.textChanged.connect(self.refresh)
- self.listItems=QtGui.QListWidget()
- self.listItems.activated.connect(self.sendUp)
-
- layout=QtGui.QVBoxLayout(self)
- layout.addWidget(self.search)
- layout.addWidget(self.listItems)
-
- self.listItems.addItems(self.items)
- self.search.setFocus()
-
- def refresh(self):
- term=self.search.text()
- buff=[]
- for i in self.items:
- if(term.lower() in i.lower()):
- buff.append(i)
- self.listItems.clear()
- self.listItems.addItems(buff)
-
- def sendUp(self):
- self.added.emit(self.listItems.currentItem().text())
- self.close()
#! /usr/bin/python3
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+''' This module handles the aliases of the symptoms. '''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
-# This file is part of DDStorm.
+This file is part of DDStorm.
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
-import sys, os
+import sys
+import os
from fnmatch import fnmatch
+
from conf import Conf
from const import *
class Alias:
+ ''' Provides the class to handle symptom aliases '''
+
def __init__(self, conf=False):
+ '''
+ Initiates the alias object
+ Accepts a Conf object as parameter
+ '''
self.data={}
if(conf):
self.conf=conf
self.compile()
def compile(self):
+ ''' Compile the plaintext index files to a program usable format '''
+ # Loop over the alias files
for path, subdirs, files in os.walk(self.conf.get("alias_path")):
for name in files:
if(fnmatch(name, "*.txt")):
+ # Open the *.txt files
with open(self.conf.get("alias_path")+name, "r") as f:
for line in f:
+ # Ignore lines starting with #
line=line.rstrip().split("#")[0]
if(len(line)==0):
pass
else:
terms=[]
+ # Split words separated by ; and add to the terms
for i in line.split(";"):
if(i.strip()):
terms.append(i.strip())
+ # If alias present, add terms to the data
if(len(terms)==2):
self.data[terms[-1]]=terms[0]
elif(len(terms)>2):
self.data[i]=t
def get(self, term):
+ '''
+ Return the alias of the queried symptom
+
+ Parameter:
+ term - Queried string
+
+ Return value:
+ String containing the alias of the term
+ '''
if(term in self.data):
return self.data[term]
else:
return term
def main():
+ ''' Print the alias of the command line argument '''
a=Alias()
if(len(sys.argv)>1):
print(a.get(sys.argv[1]))
self._conf=conf
else:
self._conf=Conf()
+ self.clean=True
def compile(self):
''' Compile the text files to DDStorm modules. '''
self.source=set()
self.custom=set()
self.alias=Alias(self._conf)
- self.clean=True
+
+ # Loop over library files and add *.txt files to source
for path, subdirs, files in os.walk(self._conf.get("library_path")):
for name in files:
if(fnmatch(name, "*.txt")):
self.source.add(os.path.join(path, name))
+
+ # Loop over custom files and add *.txt files to custom
for path, subdirs, files in os.walk(self._conf.get("custom_path")):
for name in files:
if(fnmatch(name, "*.txt")):
self.custom.add(os.path.join(path, name))
+
+ # Create module directory if not already present and delete all module files
if(not os.path.isdir(self._conf.get("module_path"))):
os.makedirs(self._conf.get("module_path"))
for f in os.listdir(self._conf.get("module_path")):
if(fnmatch(f, "*.module")):
os.unlink(self._conf.get("module_path")+f)
+
+ # Create a regex for calculating priority from filename
self.priorityRegex=re.compile("(?<=\.)\d+$")
+
+ # First sort files by priority then compile them to module
for src in self._sortPriority(self.source):
self._makeModule(src)
for src in self._sortPriority(self.custom):
self._makeModule(src)
def _sortPriority(self, files):
+ ''' Sort data files based on their priority settings. '''
ls=[]
+ # Loop over the files
for addr in files:
+ # Format the file name
name=os.path.splitext(os.path.basename(addr))[0].lower().replace("_"," ").replace("-", " ")
+ # Search for priority tag on file name
m=re.search(self.priorityRegex, name)
+ # Add to ls as (symptom name, priority number, file name) with default priority of 100
if(m):
ls.append((name.replace("."+m.group(), ""), int(m.group()), addr))
else:
ls.append((name, 100, addr))
+ # Sort the file list, first by the symptom name, then by the priority number
ls.sort(reverse=True)
if(ls):
return(list(zip(*ls))[2])
return ls
def _makeModule(self, src):
+ ''' Create application usable modules from data files. '''
+ # Format the file name
module=os.path.splitext(os.path.basename(src))[0].lower().replace("_"," ").replace("-", " ")
+ # Remove the priority tag from file name
m=re.search(self.priorityRegex, module)
if(m):
module=module.replace("."+m.group(), "")
+ # Create the module file name
modFile=self._conf.get("module_path")+module+".module"
modFlag=False
+ # Loop over both files, the source data file and the target module file
with open(src, "r") as sf, open(modFile, "a") as tf:
+ # Ignore lines starting with ! or #, + and - has special meaning, write other lines to module. Log the errors.
for line in sf:
line=line.strip().split("#")[0]
if(len(line)==0):
else:
self.clean=False
logging.warning("Syntax error in file '"+src+"': "+line)
+ # Deal with special lines
if(modFlag):
modFlag=False
with open(src, "r") as f:
for line in f:
line=line.strip().split("#")[0]
if(line[1:].replace(" ","").replace("-","").replace("_","").replace("'","").isalnum()):
+ # If line starts with + add it to the module file
if(line.startswith("+")):
with open(modFile, "r") as fn:
text=fn.read()
with open(modFile, "w") as fn:
print(self.alias.get(line[1:]).capitalize()+"\n"+text, file=fn)
+ # If line starts with - remove corresponding item from the module file
elif(line.startswith("-")):
with open(modFile, "r") as fn:
text=fn.read()
with open(modFile, "w") as fn:
print(text, file=fn)
+ def is_clean(self):
+ '''Report if compilation ended successfully'''
+ return self.clean
+
def main():
+ ''' Compile the data files into formatted module files '''
c=Compile().compile()
if(__name__=="__main__"):
import os
import logging
-from const import * # Import constants
+from const import *
logging.basicConfig(filename=LOG_FILE)
function calls.
'''
- _conf={} #Initiates configuration dictionary
+ _conf={}
def __init__(self, filename=CONF_FILE):
'''
def read(self):
''' Read the configuration file and collect the values '''
- if(os.path.isfile(self.filename)): # If file is actually present
+ # If file is actually present
+ if(os.path.isfile(self.filename)):
try:
- with open(self.filename) as f: # Open file
+ with open(self.filename) as f:
for line in f:
- line="".join(line.split()) # Removes any stray whitespaces
- if(line.startswith("#")): # Ignores comments
+ # Removes any stray whitespaces
+ line="".join(line.split())
+ # Ignores comments starting with #
+ if(line.startswith("#")):
pass
- elif(line.startswith("library_path=")): # Library files path
+ # Library path
+ elif(line.startswith("library_path=")):
self._conf["library_path"]=line[13:]
if(os.path.isdir(self._conf["library_path"])):
if(not self._conf["library_path"].endswith("/")):
self._conf["library_path"]+="/"
- elif(line.startswith("custom_path=")): # Custom files path
+ # Custom path
+ elif(line.startswith("custom_path=")):
self._conf["custom_path"]=line[12:]
if(os.path.isdir(self._conf["custom_path"])):
if(not self._conf["custom_path"].endswith("/")):
self._conf["custom_path"]+="/"
- elif(line.startswith("index_path=")): # Index files path
+ # Index path
+ elif(line.startswith("index_path=")):
self._conf["index_path"]=line[11:]
if(os.path.isdir(self._conf["index_path"])):
if(not self._conf["index_path"].endswith("/")):
self._conf["index_path"]+="/"
- elif(line.startswith("alias_path=")): # Alias files path
+ # Alias path
+ elif(line.startswith("alias_path=")):
self._conf["alias_path"]=line[11:]
if(os.path.isdir(self._conf["alias_path"])):
if(not self._conf["alias_path"].endswith("/")):
self._conf["alias_path"]+="/"
- elif(line.startswith("module_path=")): # Path to save compiled modules
+ # Module path
+ elif(line.startswith("module_path=")):
self._conf["module_path"]=line[12:]
if(os.path.isdir(self._conf["module_path"])):
if(not self._conf["module_path"].endswith("/")):
self._conf["module_path"]+="/"
- elif(line.startswith("splash_screen=")): # Whether to show a splash screen
+ # Splash screen
+ elif(line.startswith("splash_screen=")):
self._conf["splash_screen"]=line[14:]
- elif(line.startswith("clean_log=")): # Whether to clean logs before exit
+ # Clean log
+ elif(line.startswith("clean_log=")):
self._conf["clean_log"]=line[10:]
- elif(line.startswith("status_message=")): # Whether to show status messages
+ # Status message
+ elif(line.startswith("status_message=")):
self._conf["status_message"]=line[15:]
+ # Unknown option
else:
- logging.warning("Unrecognized configuration: "+line) # Log a warning if unrecognized option found
- except: # Go with default if file could not be read and log an error
+ logging.warning("Unrecognized configuration: "+line)
+ except:
logging.error("Configuration file "+self.filename+" could not be read. Using default configurations.")
def get(self, key=False):
#! /usr/bin/python3
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+DDStorm is a python application for finding differential diagnosis for
+a given list of symptoms.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
-# This file is part of DDStorm.
+This file is part of DDStorm.
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import os
-import sys, os
from compile import Compile
from conf import Conf
from index import Index
class DDStorm:
+ ''' Provides the class for finding differential diagnosis. '''
conf=False
+
def __init__(self, comp=False, conf=False):
+ '''
+ Initiate the diagnosis finder.
+
+ Parameters:
+ comp - Recompiles the data files if set to True
+ conf - Supply a Conf object
+ '''
if(conf):
self.conf=conf
else:
self.conf=Conf()
+ self.compiler=Compile(conf)
if(comp):
- self.compiler=Compile(conf).compile()
+ self.compiler.compile()
self.index=Index(conf)
def dd(self, symptoms):
+ '''
+ Find the differential diagnosis list.
+
+ Parameter:
+ symptom - list of strings containing symptoms
+
+ Return value:
+ List of strings containing the differential diagnosis
+ '''
+
+ # Return empty list if symptom list is empty
if(not symptoms):
return
+
+ # Find DD of first symptom and discard it
diff1=self._getDiff(symptoms.pop(0))
+
+ # Loop through the rest of the list
for s in symptoms:
+
+ # Find DD of the current item in the list
diff2=self._getDiff(s)
+
+ # List for temporary holding the DDs
temp=[]
+
+ # Make both lists the same length by appending empty strings to the end
if(len(diff1)>len(diff2)):
diff2+=[""]*(len(diff1)-len(diff2))
elif(len(diff2)>len(diff1)):
diff1+=[""]*(len(diff2)-len(diff1))
+
+ # Loop over both lists
for (s1, s2) in zip(diff1, diff2):
+
+ # Add s1 to temp if s1 or any of its upstream ancestor is common to both list
if((s1 not in temp) and (len(s1)>0)):
if(s1 in diff2):
temp.append(s1)
for i in us:
if(i in diff2):
temp.append(i)
+
+ # Add s2 to temp if s2 or any of its upstream ancestor is common to both list
if((s2 not in temp) and (len(s2)>0)):
if(s2 in diff1):
temp.append(s2)
for i in us:
if(i in diff1):
temp.append(i)
+
+ # Copy temp to first list
diff1=list(temp)
+
return diff1
def _getDiff(self, symptom):
+ ''' Return differential diagnosis for a single symptom '''
diff=[]
symptom=symptom.lower().replace("_"," ").replace("-", " ")
if(os.path.isfile(self.conf.get("module_path")+symptom+".module")):
return diff
def symptoms(self):
+ '''
+ Return a full list of available symptoms
+
+ Return value:
+ List of string containing symptoms
+ '''
symp=[]
for n in os.listdir(self.conf.get("module_path")):
symp.append(os.path.splitext(os.path.basename(n))[0].capitalize())
return symp
def main():
+ '''
+ Find differential diagnosis in command line mode.
+ Accepts symptoms as command line arguments. Prints a list of
+ avialable symptoms if called without any argument.
+
+ Command line arguments:
+ A list of symptoms separated by space
+ '''
s=DDStorm()
if(len(sys.argv)>1):
for d in s.dd(sys.argv[1:]):
--- /dev/null
+'''
+This module provides some extra functionalities to the main window.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import subprocess
+import os
+from PyQt4 import QtGui, QtCore
+from const import *
+
+def x_settings():
+ ''' Open the configuration file '''
+ subprocess.Popen(["xdg-open", CONF_FILE])
+
+def x_lib():
+ ''' Open the library path '''
+ if(os.path.isfile(CONF_FILE)):
+ with open(CONF_FILE) as conf:
+ for line in conf:
+ if(line.startswith("library_path=")):
+ library_path=line[13:-1]
+ if(os.path.isdir(library_path)):
+ subprocess.Popen(["xdg-open", library_path])
+
+def x_save(w, symp, diff):
+ ''' Save data to a file '''
+ fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')")
+ if(not fname.endswith(".html")):
+ fname=fname+".html"
+ with open(fname, "w") as f:
+ print("<!DOCTYPE html><html><head><title>Differential Diagnosis</title></head>", file=f)
+ print("<body><h1>Differential Diagnosis</h1>", file=f)
+ print("<table style='width:100%'>", file=f)
+ print("<tr><th>Symptoms</th><th>Diffrential Diagnosis</th></tr>", file=f)
+ print("<tr><td style='vertical-align:text-top'><ol>", file=f)
+ for s in symp:
+ print("<li>"+s+"</li>", file=f)
+ print("</ol></td><td style='vertical-align:text-top'><ol>", file=f)
+ for d in diff:
+ print("<li>"+d+"</li>", file=f)
+ print("</ol></td></tr></table></body></html>", file=f)
+
+def x_help():
+ ''' Show help '''
+ if(os.path.isfile(HELP_FILE)):
+ subprocess.Popen(["xdg-open", HELP_FILE])
+ else:
+ subprocess.Popen(["xdg-open", "http://www.agnibho.com"])
+
+def x_logfile():
+ ''' Open log file '''
+ subprocess.Popen(["xdg-open", LOG_FILE])
+
+
+class SettingsDialog(QtGui.QDialog):
+ '''
+ Provides a dialog box to configure application settings with a
+ graphical user interface.
+ '''
+
+ def __init__(self, conf):
+ ''' Initiate the dialog '''
+ super(SettingsDialog, self).__init__()
+ self.setWindowTitle("Settings")
+ self.conf=conf
+ self.initUI()
+
+ def initUI(self):
+ ''' Initiate the user interface '''
+ self.lpLabel=QtGui.QLabel("Libary Path:")
+ self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path"))
+ self.lpBrowse=QtGui.QPushButton("Browse")
+ self.lpBrowse.clicked.connect(self.lpUpdate)
+ self.cpLabel=QtGui.QLabel("Custom Path:")
+ self.cpEdit=QtGui.QLineEdit(self.conf.get("custom_path"))
+ self.cpBrowse=QtGui.QPushButton("Browse")
+ self.cpBrowse.clicked.connect(self.cpUpdate)
+ self.mpLabel=QtGui.QLabel("Module Path:")
+ self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path"))
+ self.mpBrowse=QtGui.QPushButton("Browse")
+ self.mpBrowse.clicked.connect(self.mpUpdate)
+ self.splash=QtGui.QCheckBox("Show Splash Screen")
+ if(self.conf.get("splash_screen")=="yes"):
+ self.splash.setChecked(True)
+ self.clean=QtGui.QCheckBox("Clean Log on Exit")
+ if(self.conf.get("clean_log")=="yes"):
+ self.clean.setChecked(True)
+ self.status=QtGui.QCheckBox("Show Status Message")
+ if(self.conf.get("status_message")=="on"):
+ self.status.setChecked(True)
+ self.ok=QtGui.QPushButton("Ok")
+ self.ok.clicked.connect(self.save)
+ self.cancel=QtGui.QPushButton("Cancel")
+ self.cancel.clicked.connect(self.close)
+ self.default=QtGui.QPushButton("Default")
+ self.default.clicked.connect(self.reset)
+
+ ctrl=QtGui.QHBoxLayout()
+ ctrl.addWidget(self.ok)
+ ctrl.addWidget(self.cancel)
+ ctrl.addWidget(self.default)
+ layout=QtGui.QGridLayout(self)
+ layout.addWidget(self.lpLabel, 0, 0)
+ layout.addWidget(self.lpEdit, 0, 1)
+ layout.addWidget(self.lpBrowse, 0, 2)
+ layout.addWidget(self.cpLabel, 1, 0)
+ layout.addWidget(self.cpEdit, 1, 1)
+ layout.addWidget(self.cpBrowse, 1, 2)
+ layout.addWidget(self.mpLabel, 2, 0)
+ layout.addWidget(self.mpEdit, 2, 1)
+ layout.addWidget(self.mpBrowse, 2, 2)
+ layout.addWidget(self.splash, 3, 0)
+ layout.addWidget(self.clean, 4, 0)
+ layout.addWidget(self.status, 5, 0)
+ layout.addLayout(ctrl, 6, 1)
+
+ self.cancel.setFocus()
+
+ def lpUpdate(self):
+ ''' Updates the library path '''
+ self.lpEdit.setText(self.getFolder())
+
+ def cpUpdate(self):
+ ''' Updates the custom path '''
+ self.cpEdit.setText(self.getFolder())
+
+ def mpUpdate(self):
+ ''' Updates the module path '''
+ self.mpEdit.setText(self.getFolder())
+
+ def getFolder(self):
+ ''' Returns the selected directory '''
+ dn=QtGui.QFileDialog.getExistingDirectory()
+ if(dn.startswith(QtCore.QDir.currentPath())):
+ dn="."+dn[len(QtCore.QDir.currentPath()):]+"/"
+ else:
+ dn=dn+"/"
+ return dn
+
+ def save(self):
+ ''' Saves the configuration to disk '''
+ self.conf.set("library_path", self.lpEdit.text())
+ self.conf.set("custom_path", self.cpEdit.text())
+ self.conf.set("module_path", self.mpEdit.text())
+ if(self.splash.isChecked()):
+ self.conf.set("splash_screen", "yes")
+ else:
+ self.conf.set("splash_screen", "no")
+ if(self.clean.isChecked()):
+ self.conf.set("clean_log", "yes")
+ else:
+ self.conf.set("clean_log", "no")
+ if(self.status.isChecked()):
+ self.conf.set("status_message", "on")
+ else:
+ self.conf.set("status_message", "off")
+ QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm")
+ self.close()
+ self.conf.write()
+
+ def reset(self):
+ ''' Resests the settings to the default factory value '''
+ self.conf.default()
+ self.lpEdit.setText(self.conf.get("library_path"))
+ self.cpEdit.setText(self.conf.get("class_path"))
+ self.mpEdit.setText(self.conf.get("module_path"))
+ if(self.conf.get("splash_screen")=="yes"):
+ self.splash.setChecked(True)
+ else:
+ self.splash.setChecked(False)
+ if(self.conf.get("clean_log")=="yes"):
+ self.clean.setChecked(True)
+ else:
+ self.clean.setChecked(False)
+ if(self.conf.get("status_message")=="on"):
+ self.status.setChecked(True)
+ else:
+ self.status.setChecked(False)
--- /dev/null
+#! /usr/bin/python3
+
+'''
+This module provides the main graphical user interface for DDStorm
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import time
+import subprocess
+
+from PyQt4 import QtGui, QtCore
+
+from panes import Symptoms, Differentials
+from ddstorm import DDStorm
+from conf import Conf
+from extras import *
+from const import *
+
+conf=False
+
+class Content(QtGui.QWidget):
+ '''
+ Provides the main content widget. Contains the sysmptoms and
+ the diagnosis panes. Also creates the DDStorm object and performs
+ the main operation.
+ '''
+
+ # Signal to detect when
+ change=QtCore.pyqtSignal()
+
+ def __init__(self):
+ ''' Initiate the content widget '''
+ super(Content, self).__init__()
+
+ # Create DDStorm object with the global configuration
+ self.dd=DDStorm(True, conf)
+
+ # Show warning if any error happened during data compilation
+ if(not self.dd.compiler.is_clean()):
+ ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log")
+ if(ret==1):
+ x_logfile()
+
+ self.initUI()
+
+ def initUI(self):
+ ''' Create the user interface of the widget '''
+
+ global conf
+
+ grid=QtGui.QGridLayout()
+ self.setLayout(grid)
+
+ self.symp=Symptoms(self.dd.symptoms())
+ self.symp.setFrameShape(QtGui.QFrame.StyledPanel)
+ self.symp.changed.connect(self.update)
+
+ self.diff=Differentials()
+ self.diff.setFrameShape(QtGui.QFrame.StyledPanel)
+
+ grid.addWidget(self.symp, 0, 0)
+ grid.addWidget(self.diff, 0, 1)
+ grid.setColumnStretch(0, 1)
+ grid.setColumnStretch(1, 1)
+ QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
+
+ def update(self, data):
+ ''' Update the inteface with refreshed information '''
+ self.diff.update(self.dd.dd(data))
+ self.change.emit()
+
+
+class Window(QtGui.QMainWindow):
+ '''
+ Provides main application window. Acts as a container for the
+ content widget. Also contains the menubar and the status bar.
+ '''
+
+ def __init__(self):
+ ''' Initiate the main window '''
+ super(Window, self).__init__()
+ self.initUI()
+
+ def initUI(self):
+ ''' Create the user interface '''
+ global conf
+ self.con=Content()
+ self.sett=SettingsDialog(conf)
+ if(conf.get("status_message")=="on"):
+ self.con.change.connect(self.showStatus)
+
+ menu=self.menuBar()
+ menuFile=menu.addMenu("&File")
+ menuFile.addAction("&Save").triggered.connect(self.savefile)
+ menuFile.addAction("E&xit").triggered.connect(self.close)
+ menuEdit=menu.addMenu("&Edit")
+ menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem)
+ menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms)
+ rmAction=QtGui.QAction("&Remove", self)
+ rmAction.setShortcut("Delete")
+ rmAction.triggered.connect(self.con.symp.remove)
+ menuEdit.addAction(rmAction)
+ menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll)
+ menuTool=menu.addMenu("&Tools")
+ menuTool.addAction("&Library").triggered.connect(x_lib)
+ menuTool.addAction("&Settings").triggered.connect(self.settings)
+ menuTool.addAction("&View Log").triggered.connect(x_logfile)
+ menuHelp=menu.addMenu("&Help")
+ menuHelp.addAction("&Help").triggered.connect(x_help)
+ menuHelp.addAction("&About").triggered.connect(self.about)
+
+ self.setCentralWidget(self.con)
+ self.status=self.statusBar()
+ self.setGeometry(200, 200, 600, 400)
+ self.setWindowTitle("D/D Storm")
+ self.setWindowIcon(QtGui.QIcon("icons/icon.png"))
+ self.showMaximized()
+ self.con.symp.new.setFocus()
+
+ def showStatus(self):
+ ''' Show status message '''
+ if(self.con.symp.getList() and self.con.diff.getList()):
+ self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).")
+ else:
+ self.status.showMessage("")
+
+ def savefile(self):
+ ''' Save data to a file '''
+ x_save(self, self.con.symp.getList(), self.con.diff.getList())
+
+ def settings(self):
+ ''' Open the settings dialog '''
+ self.sett.exec_()
+
+ def about(self):
+ ''' Show information about this application '''
+ QtGui.QMessageBox.about(self, "About", "<h1>DDStorm</h1>\nBrainstorm Medicine")
+
+
+def main():
+ ''' Start the main application interface '''
+ app=QtGui.QApplication(sys.argv)
+
+ # Initiate the global configuration
+ global conf
+ conf=Conf()
+
+ # Clean the log file
+ if(conf.get("clean_log")=="yes"):
+ open(LOG_FILE, "w").close()
+
+ # Show splash-screen
+ if(conf.get("splash_screen")=="yes"):
+ ss=True
+ else:
+ ss=False
+ if(ss):
+ splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png"))
+ splash.show()
+ time.sleep(0.1)
+ app.processEvents()
+ splash.showMessage("Loading...")
+
+ # Create main window
+ w=Window()
+ if(ss):
+ splash.finish(w)
+
+ # Start application
+ sys.exit(app.exec_())
+
+if(__name__=="__main__"):
+ main()
#! /usr/bin/python3
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+This module handles the upstream ancestors of symptoms.
-# This file is part of DDStorm.
+The symptoms can be classified in different levels, with the more
+generalized symtoms at upstream and the more specialized symptoms at
+the downstream.
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+For example:
+'Acute peritonitis' is a special type of 'peritonitis'
+Hence 'peritionitis' will be at upstream and 'acute peritonitis' will be
+at downstream.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
+This file is part of DDStorm.
-# You should have received a copy of the GNU General Public License
-# along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
-import sys, os
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import os
from fnmatch import fnmatch
+
from conf import Conf
from const import *
class Index:
+ ''' Provides an index of the upstream ancestors of symptoms '''
def __init__(self, conf=False):
+ '''
+ Initiate the index object. Accepts a Conf object as an
+ optional parameter.
+ '''
if(conf):
self.conf=conf
else:
self.compile()
def compile(self):
+ '''
+ Compile the text index files to a application usable format.
+ '''
+ # Loop over all files under index_path
for path, subdirs, files in os.walk(self.conf.get("index_path")):
for name in files:
+ # Only files with *.txt suffix
if(fnmatch(name, "*.txt")):
with open(self.conf.get("index_path")+name, "r") as f:
+ # Two dimensional list buffer to hold the index
buff=[]
buff.append([])
buff.append([])
+ # Loop over all lines as separate entries
for line in f:
+ # Ignore commnents starting with #
line=line.rstrip().split("#")[0]
if(len(line)==0):
pass
else:
+ # Find number of leading whitespaces
ws=len(line)-len(line.lstrip())
+ # Format the entry
line=line.lstrip().capitalize()
+
+ # No leading whitespace means a top level entry i.e. no upstream ancestor
if(ws==0):
+ # Empty the buffer and add the entry
del buff[0][:]
buff[0].append(line.lstrip())
+ # Reset the whitespace index
del buff[1][:]
buff[1].append(0)
+ # If leading whitespace > indexed whitespace, the entry is a subcategory of previous entry
elif(ws>buff[1][-1]):
+ # Append entry to buffer as new item
buff[0].append(line.lstrip())
+ # Record leading whitespace to buffer
buff[1].append(ws)
+ # Copy buffer to data list
self.data[buff[0][-1]]=list(reversed(buff[0]))
+ # If leading whitespace == indexed whitespace, the entry is at the same level with the previous entry
elif(ws==buff[1][-1]):
+ # Append entry to last item of buffer
buff[0][-1]=line.lstrip()
buff[1][-1]=ws
+ # Copy buffer to data list
self.data[buff[0][-1]]=list(reversed(buff[0]))
+ # If leading whitespace < indexed whitespace, the entry is at an upper category than the previous entry
elif(ws<buff[1][-1]):
+ # Loop over the buffer in reverse
for i in reversed(buff[1]):
+ # Discard all lower category items
if(i>=ws):
buff[0].pop()
buff[1].pop()
+ #Append the entry to buffer
else:
buff[0].append(line.lstrip())
buff[1].append(ws)
break
+ # Copy buffer to data list
self.data[buff[0][-1]]=list(reversed(buff[0]))
def upstream(self, name):
+ '''
+ Return the upstream list of a symptom
+
+ Parameter:
+ name - the name of the symptom as string
+
+ Return value:
+ List of strings containing the upstream items
+ '''
if(len(name)>0):
if name in self.data:
return self.data[name]
else:
return []
+ def names(self):
+ ''' Return all indexed symptoms name '''
+ return list(self.data.keys())
+
def main():
+ '''
+ Prints upstream items of a symptom provided as command line
+ argument.
+ If no argument provided, returns a list of all indexed symptoms.
+ '''
i=Index()
if(len(sys.argv)>1):
print(i.upstream(sys.argv[1]))
+ else:
+ print(i.names())
if(__name__=="__main__"):
main()
--- /dev/null
+'''
+This module provides the application panes containing essential
+functionalities.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from PyQt4 import QtGui, QtCore
+
+class Symptoms(QtGui.QFrame):
+ ''' Provides the widget for symptoms input '''
+
+ # List to hold the symptoms
+ sympList=[]
+
+ # Signal to notify any change in input
+ changed=QtCore.pyqtSignal(list)
+
+ def __init__(self, auto):
+ '''
+ Initiate the symptom input pane. Takes a list of string as
+ argument. The list is used as the auto-complete list during
+ user input.
+
+ Parameter:
+ auto - A list of string containing all available symptoms for
+ autocomplete.
+ '''
+ super(Symptoms, self).__init__()
+ self.auto=auto
+ self.initUI()
+
+ def initUI(self):
+ ''' Initiate the user interface '''
+ self.label=QtGui.QLabel("Symptoms")
+ self.label.setStyleSheet("font-size:18px")
+
+ self.listWidget=QtGui.QListWidget(self)
+ self.listWidget.setStyleSheet("font-size:14px")
+ self.listWidget.setSelectionMode(4)
+
+ self.rm=QtGui.QPushButton("Remove")
+ self.rm.clicked.connect(self.remove)
+
+ self.cl=QtGui.QPushButton("Clear All")
+ self.cl.clicked.connect(self.removeAll)
+
+ self.browse=QtGui.QPushButton("Browse Symptoms")
+ self.browse.clicked.connect(self.browseSymptoms)
+
+ self.add=QtGui.QPushButton("Add")
+ self.add.clicked.connect(self.addItem)
+
+ self.new=QtGui.QLineEdit(self)
+ self.new.returnPressed.connect(self.addItem)
+ self.completer=QtGui.QCompleter(self.auto)
+ self.completer.setCaseSensitivity(0)
+ self.completer.setCompletionMode(2)
+ self.new.setCompleter(self.completer)
+
+ self.browser=SymptomBrowser(self.auto)
+ self.browser.added.connect(self.addItem)
+
+ hboxt=QtGui.QHBoxLayout()
+ hboxt.addWidget(self.new)
+ hboxt.addWidget(self.add)
+ hboxb=QtGui.QHBoxLayout()
+ hboxb.addWidget(self.rm)
+ hboxb.addWidget(self.cl)
+ hboxb.addWidget(self.browse)
+ vbox=QtGui.QVBoxLayout()
+ vbox.addWidget(self.label)
+ vbox.addLayout(hboxt)
+ vbox.addWidget(self.listWidget)
+ vbox.addLayout(hboxb)
+ self.setLayout(vbox)
+
+ def addItem(self, text=""):
+ ''' Add a new symptom '''
+ if(not text):
+ text=self.new.text()
+ if(len(text)>0):
+ if(text in self.sympList):
+ QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.")
+ elif(text in self.auto):
+ QtGui.QListWidgetItem(text, self.listWidget)
+ self.sympList.append(text)
+ self.new.clear()
+ self.changed.emit(list(self.sympList))
+ else:
+ QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.")
+
+ def remove(self, all=False):
+ ''' Remove selected symptoms '''
+ if(len(self.listWidget.selectedItems())>0):
+ for item in self.listWidget.selectedItems():
+ self.sympList.remove(item.text())
+ self.listWidget.takeItem(self.listWidget.row(item))
+ self.changed.emit(list(self.sympList))
+
+ def removeAll(self):
+ ''' Clear all symptoms '''
+ self.listWidget.clear()
+ del self.sympList[:]
+ self.changed.emit(list(self.sympList))
+
+ def browseSymptoms(self):
+ ''' Open the symptom browser '''
+ self.browser.exec_()
+
+ def getList(self):
+ ''' Return a list of all symptoms '''
+ return self.sympList
+
+
+class SymptomBrowser(QtGui.QDialog):
+ '''
+ Provides a dialog with a list of symptoms for alternative user
+ input.
+ '''
+ added=QtCore.pyqtSignal(str)
+
+ def __init__(self, items):
+ ''' Initiate the input dialog '''
+ super(SymptomBrowser, self).__init__()
+ self.setWindowTitle("Choose Symptom")
+ self.items=items
+ self.initUI()
+
+ def initUI(self):
+ ''' Initiate the dialog interface '''
+ self.search=QtGui.QLineEdit()
+ self.search.textChanged.connect(self.refresh)
+ self.listItems=QtGui.QListWidget()
+ self.listItems.activated.connect(self.sendUp)
+
+ layout=QtGui.QVBoxLayout(self)
+ layout.addWidget(self.search)
+ layout.addWidget(self.listItems)
+
+ self.listItems.addItems(self.items)
+ self.search.setFocus()
+
+ def refresh(self):
+ ''' Refresh the symptom list based on search term '''
+ term=self.search.text()
+ buff=[]
+ for i in self.items:
+ if(term.lower() in i.lower()):
+ buff.append(i)
+ self.listItems.clear()
+ self.listItems.addItems(buff)
+
+ def sendUp(self):
+ ''' Emit signal and close when a symptom is selected '''
+ self.added.emit(self.listItems.currentItem().text())
+ self.close()
+
+
+class Differentials(QtGui.QFrame):
+ ''' Provides the widget for differential diagnosis output '''
+ data=[]
+
+ def __init__(self):
+ ''' Initiate the diagnosis output pane '''
+ super(Differentials, self).__init__()
+ self.initUI()
+
+ def initUI(self):
+ ''' Initiate the user interface '''
+ self.label=QtGui.QLabel("Differential Diagnosis")
+ self.label.setStyleSheet("font-size:18px")
+ self.listWidget=QtGui.QListWidget(self)
+ self.listWidget.setStyleSheet("font-size:14px")
+ self.listWidget.setSelectionMode(0)
+ box=QtGui.QVBoxLayout()
+ box.addWidget(self.label)
+ box.addWidget(self.listWidget)
+ self.setLayout(box)
+
+ def update(self, data):
+ '''
+ Update the outut pane with updated diagnosis list.
+
+ Parameter:
+ data - List of strings containing new list of differential
+ diagnosis
+ '''
+ self.data=data
+ self.listWidget.clear()
+ if(self.data):
+ for d in self.data:
+ QtGui.QListWidgetItem(d, self.listWidget)
+
+ def getList(self):
+ ''' Return a list of current differential diagnosis '''
+ return self.data