Mercurial > hgweb > health
changeset 3215:3c8aa357f08d
Task #15167: Update existing patients and studies on sync; Reformat code
| author | Chris Zimmerman <chris@teffalump.com> |
|---|---|
| date | Sun, 17 Feb 2019 23:33:05 -0800 |
| parents | 37dd03f759f8 |
| children | e964f822c8fd |
| files | tryton/health_orthanc/__init__.py tryton/health_orthanc/health_orthanc.py tryton/health_orthanc/wizard/wizard.py |
| diffstat | 3 files changed, 272 insertions(+), 154 deletions(-) [+] |
line wrap: on
line diff
--- a/tryton/health_orthanc/__init__.py +++ b/tryton/health_orthanc/__init__.py @@ -23,6 +23,7 @@ from .health_orthanc import * from .wizard import * + def register(): Pool.register( AddOrthancInit, @@ -31,7 +32,7 @@ OrthancStudy, OrthancPatient, Orthanc, - module='health_orthanc', type_='model') - Pool.register( - FullSyncOrthanc, - module='health_orthanc', type_='wizard') + module="health_orthanc", + type_="model", + ) + Pool.register(FullSyncOrthanc, module="health_orthanc", type_="wizard")
--- a/tryton/health_orthanc/health_orthanc.py +++ b/tryton/health_orthanc/health_orthanc.py @@ -22,44 +22,59 @@ from trytond.pool import Pool from trytond.transaction import Transaction from orthanc_rest_client import Orthanc as RestClient -import pendulum from requests.auth import HTTPBasicAuth as auth from requests.exceptions import HTTPError, ConnectionError from datetime import datetime import logging +import pendulum -__all__=['OrthancServerConfig', 'OrthancPatient', 'OrthancStudy', 'Orthanc'] +__all__ = ["OrthancServerConfig", "OrthancPatient", "OrthancStudy", "Orthanc"] logger = logging.getLogger(__name__) + class OrthancServerConfig(ModelSQL, ModelView): - '''Orthanc server details''' + """Orthanc server details""" - __name__ = 'gnuhealth.orthanc.config' - _rec_name = 'label' + __name__ = "gnuhealth.orthanc.config" + _rec_name = "label" - label = fields.Char('Label', required=True, help="Label for server (eg., remote1)") - domain = fields.Char('URL', required=True, help="The full URL of the Orthanc server") - user = fields.Char('Username', required=True, help="Username for Orthanc REST server") - password = fields.Char('Password', required=True, help="Password for Orthanc REST server") - last = fields.BigInteger('Last Index', readonly=True, help="Index of last change") - sync_time = fields.DateTime('Sync Time', readonly=True, help="Time of last server sync") - validated = fields.Boolean('Validated', help="The server details have been successfully checked") - since_sync = fields.Function(fields.TimeDelta('Since last sync', help="Time since last sync"), 'get_since_sync') - since_sync_readable = fields.Function(fields.Char('Since last sync', help="Time since last sync"), 'get_since_sync_readable') - patients = fields.One2Many('gnuhealth.orthanc.patient', 'server', 'Patients') - studies = fields.One2Many('gnuhealth.orthanc.study', 'server', 'Studies') + label = fields.Char("Label", required=True, help="Label for server (eg., remote1)") + domain = fields.Char( + "URL", required=True, help="The full URL of the Orthanc server" + ) + user = fields.Char( + "Username", required=True, help="Username for Orthanc REST server" + ) + password = fields.Char( + "Password", required=True, help="Password for Orthanc REST server" + ) + last = fields.BigInteger("Last Index", readonly=True, help="Index of last change") + sync_time = fields.DateTime( + "Sync Time", readonly=True, help="Time of last server sync" + ) + validated = fields.Boolean( + "Validated", help="Whether the server details have been successfully checked" + ) + since_sync = fields.Function( + fields.TimeDelta("Since last sync", help="Time since last sync"), + "get_since_sync", + ) + since_sync_readable = fields.Function( + fields.Char("Since last sync", help="Time since last sync"), + "get_since_sync_readable", + ) + patients = fields.One2Many("gnuhealth.orthanc.patient", "server", "Patients") + studies = fields.One2Many("gnuhealth.orthanc.study", "server", "Studies") @classmethod def __setup__(cls): - super(OrthancServerConfig, cls).__setup__() - t = cls.__table__() - cls._sql_constraints = [ - ('label_unique', Unique(t, t.label), 'The label must be unique.'), - ] - cls._buttons.update({ - 'do_sync': {}, - }) + super(OrthancServerConfig, cls).__setup__() + t = cls.__table__() + cls._sql_constraints = [ + ("label_unique", Unique(t, t.label), "The label must be unique.") + ] + cls._buttons.update({"do_sync": {}}) @classmethod @ModelView.button @@ -71,54 +86,71 @@ """Sync from changes endpoint""" pool = Pool() - patient = pool.get('gnuhealth.orthanc.patient') - study = pool.get('gnuhealth.orthanc.study') + patient = pool.get("gnuhealth.orthanc.patient") + study = pool.get("gnuhealth.orthanc.study") if not servers: - servers = cls.search([('domain', '!=', None), - ('validated', '=', True)]) + servers = cls.search([("domain", "!=", None), ("validated", "=", True)]) - logger.info('Starting sync') + logger.info("Starting sync") for server in servers: - if not server.validated: continue - logger.info('Getting new changes for <{}>'.format(server.label)) + if not server.validated: + continue + logger.info("Getting new changes for <{}>".format(server.label)) orthanc = RestClient(server.domain, auth=auth(server.user, server.password)) curr = server.last - new_studies = [] - update_studies = [] - new_patients = [] - update_patients = [] + new_patients = set() + update_patients = set() + new_studies = set() + update_studies = set() + while True: try: changes = orthanc.get_changes(since=curr) except: server.validated = False - logger.info('Invalid details for <{}>'.format(server.label)) + logger.info("Invalid details for <{}>".format(server.label)) break - for change in changes['Changes']: - type_ = change['ChangeType'] - if type_ == 'NewStudy': - new_studies.append(orthanc.get_study(change['ID'])) - elif type_ == 'StableStudy': - update_studies.append(orthanc.get_study(change['ID'])) - elif type_ == 'NewPatient': - new_patients.append(orthanc.get_patient(change['ID'])) - elif type_ == 'StablePatient': - update_patients.append(orthanc.get_patient(change['ID'])) + for change in changes["Changes"]: + type_ = change["ChangeType"] + if type_ == "NewStudy": + new_studies.add(change["ID"]) + elif type_ == "StableStudy": + update_studies.add(change["ID"]) + elif type_ == "NewPatient": + new_patients.add(change["ID"]) + elif type_ == "StablePatient": + update_patients.add(change["ID"]) else: pass - curr = changes['Last'] - if changes['Done'] == True: - logger.info('<{}> at newest change'.format(server.label)) + curr = changes["Last"] + if changes["Done"] == True: + logger.info("<{}> at newest change".format(server.label)) break - patient.create_patients(new_patients, server) - study.create_studies(new_studies, server) + + update_patients -= new_patients + update_studies -= new_studies + patient.create_patients( + [orthanc.get_patient(p) for p in new_patients], server + ) + patient.update_patients( + [orthanc.get_patient(p) for p in update_patients], server + ) + study.create_studies([orthanc.get_study(s) for s in new_studies], server) + study.update_studies([orthanc.get_study(s) for s in update_studies], server) server.last = curr server.sync_time = datetime.now() - logger.info('{} sync complete: {} new patients, {} new studies'.format(server.label, len(new_patients), len(new_studies))) + logger.info( + "<{}> sync complete: {} new patients, {} update patients, {} new studies, {} updated studies".format( + server.label, + len(new_patients), + len(update_patients), + len(new_studies), + len(update_studies), + ) + ) cls.save(servers) - @staticmethod def quick_check(domain, user, password): """Validate the server details""" @@ -131,7 +163,7 @@ else: return True - @fields.depends('domain', 'user', 'password') + @fields.depends("domain", "user", "password") def on_change_with_validated(self): return self.quick_check(self.domain, self.user, self.password) @@ -143,20 +175,25 @@ d = pendulum.now() - pendulum.instance(self.sync_time) return d.in_words(Transaction().language) except: - return '' + return "" + class OrthancPatient(ModelSQL, ModelView): - '''Orthanc patient information''' + """Orthanc patient information""" - __name__ = 'gnuhealth.orthanc.patient' + __name__ = "gnuhealth.orthanc.patient" - patient = fields.Many2One('gnuhealth.patient', 'Patient', help="Local linked patient") - name = fields.Char('PatientName', readonly=True) - bd = fields.Date('Birthdate', readonly=True) - ident = fields.Char('PatientID', readonly=True) - uuid = fields.Char('PatientUUID', readonly=True, required=True) - studies = fields.One2Many('gnuhealth.orthanc.study', 'patient', 'Studies', readonly=True) - server = fields.Many2One('gnuhealth.orthanc.config', 'Server', readonly=True) + patient = fields.Many2One( + "gnuhealth.patient", "Patient", help="Local linked patient" + ) + name = fields.Char("PatientName", readonly=True) + bd = fields.Date("Birthdate", readonly=True) + ident = fields.Char("PatientID", readonly=True) + uuid = fields.Char("PatientUUID", readonly=True, required=True) + studies = fields.One2Many( + "gnuhealth.orthanc.study", "patient", "Studies", readonly=True + ) + server = fields.Many2One("gnuhealth.orthanc.config", "Server", readonly=True) @staticmethod def get_info_from_dicom(patients): @@ -165,56 +202,80 @@ data = [] for patient in patients: try: - bd = datetime.strptime(patient['MainDicomTags']['PatientBirthDate'], '%Y%m%d').date() + bd = datetime.strptime( + patient["MainDicomTags"]["PatientBirthDate"], "%Y%m%d" + ).date() except: bd = None - data.append({ - 'name': patient.get('MainDicomTags').get('PatientName'), - 'bd': bd, - 'ident': patient.get('MainDicomTags').get('PatientID'), - 'uuid': patient.get('ID'), - }) + data.append( + { + "name": patient.get("MainDicomTags").get("PatientName"), + "bd": bd, + "ident": patient.get("MainDicomTags").get("PatientID"), + "uuid": patient.get("ID"), + } + ) return data @classmethod def update_patients(cls, patients, server): - pass + """Update patients""" + + entries = cls.get_info_from_dicom(patients) + updates = [] + for entry in entries: + try: + patient = cls.search( + [("uuid", "=", entry["ident"]), ("server", "=", server)], limit=1 + )[0] + patient.name = entry["name"] + patient.bd = entry["bd"] + patient.ident = entry["ident"] # TODO Look for matching record + updates.append(patient) + logger.info("Updating {}".format(entry["ident"])) + except: + continue + logger.warning("Unable to update patient {}".format(entry["ident"])) + cls.save(updates) @classmethod def create_patients(cls, patients, server): """Create patients""" pool = Pool() - Patient = pool.get('gnuhealth.patient') + Patient = pool.get("gnuhealth.patient") entries = cls.get_info_from_dicom(patients) for entry in entries: try: - g_patient = Patient.search([('puid', '=', entry['ident'])], limit=1)[0] - logger.info('Matching PUID found for {}'.format(entry['uuid'])) + g_patient = Patient.search([("puid", "=", entry["ident"])], limit=1)[0] + logger.info("Matching PUID found for {}".format(entry["uuid"])) except: g_patient = None - entry['server'] = server - entry['patient'] = g_patient + entry["server"] = server + entry["patient"] = g_patient cls.create(entries) + class OrthancStudy(ModelSQL, ModelView): - '''Orthanc study''' + """Orthanc study""" - __name__ = 'gnuhealth.orthanc.study' + __name__ = "gnuhealth.orthanc.study" - patient = fields.Many2One('gnuhealth.orthanc.patient', 'Patient', readonly=True) - uuid = fields.Char('UUID', readonly=True, required=True) - description = fields.Char('Description', readonly=True) - date = fields.Date('Date', readonly=True) - ident = fields.Char('ID', readonly=True) - institution = fields.Char('Institution', readonly=True, help="Imaging center where study was undertaken") - ref_phys = fields.Char('Referring Physician', readonly=True) - req_phys = fields.Char('Requesting Physician', readonly=True) - server = fields.Many2One('gnuhealth.orthanc.config', 'Server', readonly=True) + patient = fields.Many2One("gnuhealth.orthanc.patient", "Patient", readonly=True) + uuid = fields.Char("UUID", readonly=True, required=True) + description = fields.Char("Description", readonly=True) + date = fields.Date("Date", readonly=True) + ident = fields.Char("ID", readonly=True) + institution = fields.Char( + "Institution", readonly=True, help="Imaging center where study was undertaken" + ) + ref_phys = fields.Char("Referring Physician", readonly=True) + req_phys = fields.Char("Requesting Physician", readonly=True) + server = fields.Many2One("gnuhealth.orthanc.config", "Server", readonly=True) def get_rec_name(self, name): - return ': '.join((self.ident or self.uuid, self.description or '')) + return ": ".join((self.ident or self.uuid, self.description or "")) @staticmethod def get_info_from_dicom(studies): @@ -224,52 +285,85 @@ for study in studies: try: - date = datetime.strptime(study['MainDicomTags']['StudyDate'], '%Y%m%d').date() + date = datetime.strptime( + study["MainDicomTags"]["StudyDate"], "%Y%m%d" + ).date() except: date = None try: - description = study['MainDicomTags']['RequestedProcedureDescription'] + description = study["MainDicomTags"]["RequestedProcedureDescription"] except: description = None - data.append({ - 'parent_patient': study['ParentPatient'], - 'uuid': study['ID'], - 'description': description, - 'date': date, - 'ident': study.get('MainDicomTags').get('StudyID'), - 'institution': study.get('MainDicomTags').get('InstitutionName'), - 'ref_phys': study.get('MainDicomTags').get('ReferringPhysicianName'), - 'req_phys': study.get('MainDicomTags').get('RequestingPhysician'), - }) + data.append( + { + "parent_patient": study["ParentPatient"], + "uuid": study["ID"], + "description": description, + "date": date, + "ident": study.get("MainDicomTags").get("StudyID"), + "institution": study.get("MainDicomTags").get("InstitutionName"), + "ref_phys": study.get("MainDicomTags").get( + "ReferringPhysicianName" + ), + "req_phys": study.get("MainDicomTags").get("RequestingPhysician"), + } + ) return data @classmethod def update_studies(cls, studies, server): - pass + """Update studies""" + + entries = cls.get_info_from_dicom(studies) + updates = [] + for entry in entries: + try: + study = cls.search( + [("uuid", "=", entry["ident"]), ("server", "=", server)], limit=1 + )[0] + study.description = entry["description"] + study.date = entry["date"] + study.ident = entry["ident"] + study.institution = entry["institution"] + study.ref_phys = entry["ref_phys"] + study.req_phys = entry["req_phys"] + updates.append(study) + logger.info("Updating {}".format(entry["ident"])) + except: + continue + logger.warning("Unable to update study {}".format(entry["ident"])) + cls.save(updates) @classmethod def create_studies(cls, studies, server): """Create studies""" pool = Pool() - Patient = pool.get('gnuhealth.orthanc.patient') + Patient = pool.get("gnuhealth.orthanc.patient") entries = cls.get_info_from_dicom(studies) for entry in entries: try: - patient = Patient.search([('uuid', '=', entry['parent_patient']), - ('server', '=', server)], limit=1)[0] + patient = Patient.search( + [("uuid", "=", entry["parent_patient"]), ("server", "=", server)], + limit=1, + )[0] except: patient = None - logger.warning('No parent patient found for study {}'.format(entry['ID'])) - entry.pop('parent_patient') - entry['server'] = server - entry['patient'] = patient + logger.warning( + "No parent patient found for study {}".format(entry["ID"]) + ) + entry.pop("parent_patient") # remove non-model entry + entry["server"] = server + entry["patient"] = patient cls.create(entries) + class Orthanc(ModelSQL, ModelView): - '''Add Orthanc patient(s) to the main patient data''' + """Add Orthanc patient(s) to the main patient data""" - __name__ = 'gnuhealth.patient' + __name__ = "gnuhealth.patient" - orthanc_patients = fields.One2Many('gnuhealth.orthanc.patient', 'patient', 'Orthanc patients') + orthanc_patients = fields.One2Many( + "gnuhealth.orthanc.patient", "patient", "Orthanc patients" + )
--- a/tryton/health_orthanc/wizard/wizard.py +++ b/tryton/health_orthanc/wizard/wizard.py @@ -18,7 +18,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## -import logging from datetime import datetime from trytond.model import ModelView, fields from trytond.wizard import Wizard, StateTransition, StateView, Button @@ -27,55 +26,75 @@ from requests.auth import HTTPBasicAuth as auth from requests.exceptions import HTTPError, ConnectionError from orthanc_rest_client import Orthanc as RestClient +import logging logger = logging.getLogger(__name__) -__all__ = ['AddOrthancInit', 'FullSyncOrthanc', 'AddOrthancResult'] +__all__ = ["AddOrthancInit", "FullSyncOrthanc", "AddOrthancResult"] + class AddOrthancInit(ModelView): """Init Full Orthanc Sync""" - __name__ = 'gnuhealth.orthanc.add.init' - label = fields.Char('Label', required=True, help="The label of the Orthanc server. Must be unique") - domain = fields.Char('URL', required=True, help="The full URL of the Orthanc server") - user = fields.Char('Username', required=True, help="Username for Orthanc REST server") - password = fields.Char('Password', required=True, help="Password for Orthanc REST server") + __name__ = "gnuhealth.orthanc.add.init" + + label = fields.Char( + "Label", required=True, help="The label of the Orthanc server. Must be unique" + ) + domain = fields.Char( + "URL", required=True, help="The full URL of the Orthanc server" + ) + user = fields.Char( + "Username", required=True, help="Username for Orthanc REST server" + ) + password = fields.Char( + "Password", required=True, help="Password for Orthanc REST server" + ) + class AddOrthancResult(ModelView): """Display Result""" - __name__ = 'gnuhealth.orthanc.add.result' - result = fields.Text('Result', help="Information") + __name__ = "gnuhealth.orthanc.add.result" + + result = fields.Text("Result", help="Information") + class FullSyncOrthanc(Wizard): - 'Full sync new orthanc server' - __name__ = 'gnuhealth.orthanc.wizard.full_sync' + "Full sync new orthanc server" + __name__ = "gnuhealth.orthanc.wizard.full_sync" - start = StateView('gnuhealth.orthanc.add.init', 'health_orthanc.view_orthanc_add_init', - [ - Button('Cancel', 'end', 'tryton-cancel'), - Button('Begin', 'first_sync', 'tryton-ok', default=True), - ]) + start = StateView( + "gnuhealth.orthanc.add.init", + "health_orthanc.view_orthanc_add_init", + [ + Button("Cancel", "end", "tryton-cancel"), + Button("Begin", "first_sync", "tryton-ok", default=True), + ], + ) first_sync = StateTransition() - result = StateView('gnuhealth.orthanc.add.result', 'health_orthanc.view_orthanc_add_result', - [ - Button('Close', 'end', 'tryton-close'), - ]) + result = StateView( + "gnuhealth.orthanc.add.result", + "health_orthanc.view_orthanc_add_result", + [Button("Close", "end", "tryton-close")], + ) def transition_first_sync(self): """Import and create all current patients and studies on remote DICOM server """ pool = Pool() - Patient = pool.get('gnuhealth.orthanc.patient') - Study = pool.get('gnuhealth.orthanc.study') - Config = pool.get('gnuhealth.orthanc.config') + Patient = pool.get("gnuhealth.orthanc.patient") + Study = pool.get("gnuhealth.orthanc.study") + Config = pool.get("gnuhealth.orthanc.config") - orthanc = RestClient(self.start.domain, auth=auth(self.start.user, self.start.password)) + orthanc = RestClient( + self.start.domain, auth=auth(self.start.user, self.start.password) + ) try: - patients = [p for p in orthanc.get_patients(params={'expand': ''})] - studies = [s for s in orthanc.get_studies(params={'expand': ''})] + patients = [p for p in orthanc.get_patients(params={"expand": ""})] + studies = [s for s in orthanc.get_studies(params={"expand": ""})] except HTTPError as err: if err.response.status_code == 401: self.result.result = "Invalid credentials provided" @@ -85,24 +104,28 @@ self.result.result = "Invalid domain provided" else: new_server = { - 'label': self.start.label, - 'domain': self.start.domain, - 'user': self.start.user, - 'password': self.start.password - } + "label": self.start.label, + "domain": self.start.domain, + "user": self.start.user, + "password": self.start.password, + } server, = Config.create([new_server]) Patient.create_patients(patients, server) Study.create_studies(studies, server) - server.last = orthanc.get_changes(last=True).get('Last') + server.last = orthanc.get_changes(last=True).get("Last") server.sync_time = datetime.now() server.validated = True - logger.info('<{}> sync complete: {} new patients, {} new studies'.format(server.label, len(patients), len(studies))) + logger.info( + "<{}> sync complete: {} new patients, {} new studies".format( + server.label, len(patients), len(studies) + ) + ) Config.save([server]) - self.result.result = "Successfully added and synced <{}>".format(server.label) + self.result.result = "Successfully added and synced <{}>".format( + server.label + ) finally: - return 'result' + return "result" def default_result(self, fields): - return { - 'result': self.result.result - } + return {"result": self.result.result}
