Package: pyconquest
PyConquestIntroductionPython code to partly mimic the functionality of the Conquest Pacs system (http://www.natura-ingenium.nl/dicom.html).
No (source) code of Conquest was used to write this python code. Program was optimised to be partly compatible (files and database), so queries etc. can
be re-used.Description:Class is used to organise and index sets of dicom files. The dicom files are stored in
a directory (default name : data), with the files of each patient stored in a
subdirectory with the patientid as the name. The class will index all files in the
directory and store information (tag information of the dicom files)
in a sqlite database (default name : conquest.db).
Which tags are stored in the database can be defined by the dicom.sql file, this
file is optional, a standard set is used when not present.Standard file/directory layout is :conquest.db                       database file
data                              data_directory
    1                             subdirectory with name of PatientID
      file1_of_pat1.dcm           files of patient1
      file2_of_pat1.dcm
      ..
      ..
    2                             subdirectory with name of PatientID
      file1_of_pat2               files of patient2
      file2_of_pat2
      ..
      ..
[dicom.sql]                       optional definition of the databaseImplemented features :indexer properties : read a dicom tree and build a sqlite database from this which conforms to the Conquest standard.use the conquest style dicom.sql file to define the columnsadd some special columns,with for instance the roinames in a RTSTRUCT fileprovide low level procedures to write and read to the database and helper files to create queriesprovide basic SCU and SCP functionalitybasic printing of data propertiesWhat it is not and when you should use Conquestno attempt made to mimic full dicom functionality (this is no PACS)lua scriptingimage visualisationExamplesNote : use the argumentloglevel='INFO'when creating the instance to get logging output(Re)create database and rebuild databasefrom pyconquest import pyconquest

c=pyconquest(loglevel='INFO')
c.create_standard_dicom_tables()
nr_files=c.rebuild_database_from_dicom()
# only rebuild a single MRN/patient
c.rebuild_database_from_dicom(mrn='1234567')
# build for every MRN, even if the MRN already exists in Database
c.rebuild_database_from_dicom(compute_only_missing=False)Basic database summaryfrom pyconquest import pyconquest

c=pyconquest()
c.dicom_series_summary()Extracting extra data from the dicomfilesadds data concerning images to the dicomseries tablefrom pyconquest import pyconquest

c=pyconquest()
c.analyse_images()Saving database information to a csv filefrom pyconquest import pyconquest

c=pyconquest()

# by table(s) or views, specification of filenames is optional
c.dump_data_to_csv(table='dicomstudies')
c.dump_data_to_csv(table=['dicomseries','dicompatients'],
                   filename_dict={'dicomseries': 'dicomseries_filename', 'dicompatients': 'dicompatients_filename'} )
# by query
c.dump_data_to_csv(query='select * from dicomstudies',filename='query_filename.csv')More advanced querying and extraction from the database:from pyconquest import pyconquest

c=pyconquest()

# extract python list type of selected seriesuids 
query='select * from dicomseries where modality='CT'
list_of_seriesuid=c.execute_db_query(query,return_list_from_col='SeriesInst')
for seriesuid in list_of_seriesuid:
  print(seriesuid)Using non standard names and directoriesfrom pyconquest import pyconquest

c=pyconquest(
    data_directory='data2',
    sql_inifile_name='dicom2.sql',
    database_filename='test.db',
    connect_and_read_sql=True)
    
nr_files=c.rebuild_database_from_dicom()   
c.dicom_series_summary()Adding and deleting files to the databaseAdding a single file specified by name (optionally remove original)from pyconquest import pyconquest

c=pyconquest()
c.store_dicom_file('1.2.840.113704.1.111....931a31.dcm')
c.store_dicom_file('1.2.840.113704.1.111....931a31.dcm', remove_after_store=True)Enter all dicom files in a directory into the databasefrom pyconquest import pyconquest

c=pyconquest()
c.store_dicom_files_from_directory('input_directory_name')
c.store_dicom_files_from_directory('input_directory_name', remove_after_store=True)

# using below, the filename is made equal to the sopinstanceuid :
c.store_dicom_files_from_directory('input_directory_name', sopinstance_as_filename=True)Deleting series or list of series from the database (optionally 'real' deletion of the file)c.delete_series(seriesuid='2.16.840.1.11....152.20190524082318.537836')
c.delete_series(seriesuid=['2.16..7836','1.2.666...55'])
#or by query
c.delete_series(query='select * from dicomseries where modality="CT"')

# Really pyhsically remove the files (otherwise only the DB entries are removed)
c.delete_series('2.16.840.1.11....152.20190524082318.537836', delete_files=True)Sending and receiving files via dicom connectivitySend dicom files via dicom protocol, by patientid, seriesuid or queryfrom pyconquest import pyconquest

c=pyconquest()
c.connect_db()
c.send_dicom(addres='127.0.0.1', port=5678, patientid='1234567', ae_title=b'destinationAE',  sending_ae_title=b'MY_AE_TITLE')
c.send_dicom(seriesuid='2.16.840.1.113669.2.931128.880152.20190524082318.537836')

RTPLAN_query="select seriesinst from dicomseries where modality=\'RTPLAN\'"
c.send_dicom(query=RTPLAN_query)Start receiver (received files are stored in data directory and database is updated )from pyconquest import pyconquest

c=pyconquest(data_directory='data2')
c.start_dicom_listener(port=5678)
# Below does not update the database which saves time
c.start_dicom_listener(port=104, write_to_database=False)Copying files of a series on the disk to another directoryby seriesuid or list of seriesuidsfrom pyconquest import pyconquest

c.copy_dicom_files_to_dest(seriesuid='1.2...8024597.12',destination='out', UseSubDirectories=True)
c.copy_dicom_files_to_dest(seriesuid=['1.2.84..97.12','2.16.840....33'],destination='data2')Or by query (query should return seriesinst)from pyconquest import pyconquest

c=pyconquest()
CTquery="select seriesinst from dicomseries where modality=\'CT\'"
c.copy_dicom_files_to_dest(query=CTquery, destination='CTdata',UseSubDirectories=True)Changing tags of dicom filesSimple functionality to change a single tag of a dicom file. For
each change the file is opened and saved, so not suitable for many
changes per dicom file.from pyconquest import pyconquest

c=pyconquest()
# single tag of single file
c.change_tag('test.dcm', tag='PatientBirthDate', new_value='19000101')

# all tags from a list of filenames
c.change_all_tags(filename=['test.dcm', 'test.dcm'], new_value='19000102')

# using a helper function, change by series, study or patientid
lst = c.get_list_of_filenames(seriesuid='2.16.840.1.113669.2.931128.880152.20190524082317.937233'))
c.change_all_tags(filename=lst, tag='PatientBirthDate', new_value='19000101')Deleting and changing ROI names in a RTSTRUCT fileIt is possible to change or delete rois in a RTSTRUCT file using either of 3 modes:delete : delete the specific namesleave : leave only the given names and delete all otherschange : change the given names to alternativesthere are flags to actualy write the file or not.
using delete_points you can indicate whether deletion of points is allowedyou can useget_list_of_filenames()to apply to multiple files.from pyconquest import pyconquest

c=pyconquest()
c.modify_rtstruct(filename='tst.dcm',mode='change',roiname=['Lung_R','Lung_L'],newname=['LR','LL'],write_file=True)
c.modify_rtstruct(filename='tst.dcm',mode='delete',roiname=['Lung_R','Lung_L'],write_file=True)
c.modify_rtstruct(filename='tst.dcm',mode='leave',roiname=['Lung_R','Lung_L'],write_file=True)
# standard mode is to NOT delete points, you can overrule
# and filename can be a list of filenames
c.modify_rtstruct(filename=['tst.dcm','tst2.dcm'],mode='leave',roiname=['Lung_R','Lung_L'],write_file=True,delete_points=True)Definition of the database structureThe database can be defined using various waysIf a file dicom.sql exist that file is usedIf an alternative file is specified on creation of the instance, that file is useduse sql_infilename='filename.sql' when creatingIf no file is found, a hardcoded definition is takenYou can add columns to this definition as illustrated below:from pyconquest import pyconquest

c=pyconquest(sql_inifile_name='dicom.sql',loglevel='INFO')
c.add_column_to_database(tablename='DICOMpatients',column_definition=['0x0020', '0x000d', 'StudyInst'])
c.create_standard_dicom_tables()the .sql files are compatible with the original Conquest file formatTo install and upload to PyPi:python setup.py sdist
twine upload dist/*CHANGELOGversion 0.1.3Added functionalitychange_tag()to change tags of DICOM filesAdded functionalitymodify_rtstruct()to change and delete roi's from filesversion 0.1.2Added methodanalyse_imagesto analyse CT/MR/PET images and add data to DicomSeries table. Data added is number of slices, min/max slicepositions and a check for slice consistency is done.Added postprocessing routinedatabase_postprocessing()of the database to enrich the database with entries that can only be added when the database is
complete. For instance the Referenced seriesuid in DICOMseries table for RTDOSE. Routine is automatically
executed at the end ofrebuild_database_from_dicom()Added option calledsopinstance_as_filenametostore_dicom_files_from_directory()andstore_dicom_file()to change the actual filename in the database from the original (default)
to  sopinstanceUID.dcmversion 0.1.1 (skipped to 0.1.1 because some systems take 0.0.51 as default)Added optiondump_data_to_csv()to dump data from the database to csv filesLogging of errors to a (rotating) log file : "pyconquest_error.log"Added optionwrite_to_databaseto dicom_listener, if FALSE, sqlite db is not updated (for speed)For RTDOSE the referenced SOP UID of the RTPLAN is saved to DicomImages tableFor RTPLAN the referenced SOP UID of the RTSTUCT is saved to DicomImages tableversion 0.0.7added option not to check for double entries when rebuilding the database (for speed)added fast option to delete a patient from the databasedatabase indices are now created when creating a new databasemade index tables non unique to improve robustnessversion 0.0.6Rebuild_database_from_dicom()now has option to rebuild only missing directoriesWhen closing the database, the time elapsed is printedReferenced seriesuid is now extracted from RTSTRUCT and placed in dicomseries and dicomimages tableDicom_series_summary()now prints result directly to stdout, so no need for pandas to make a readable tableThe viewv_seriesnow only combines series and study table (previous version was too slow due to complexity)New view :v_seriesRTnow combines series,study and image tables for only RT dicom objects (so not for images)version 0.0.5a view (v_series) is added to the sqlite database that joins study,series and image tabledelete_series can now handle query to define the series to deleteseries argument of delete_series changed from positional to namedadded option to rebuild database for a single mrn  (mrn='1234567')added option sending_ae_title to send_dicom() and send_dicom_file()bugfix in name of file in store_dicom (wrong path was used)execute_db_query now has an option to return (only) a list of a single columnversion 0.0.4Added functioninsert_dictto class to easily insert your own data in the database. Also takes care of creating the
table when the first dict is insertedIf a database has no tables, tables are created automatically on db opening (so on instance creation)Increate_buildquery, now columns can be defined with formats deviating from default and the default can be definedNumber of  fractions and number of beams saved from RTPLAN to database columnsFor RTDOSE, RTPLAN and RTSTRUCT file hashes are calculated and saved in dicomimage tableAddeddelete_seriesfunction to delete the series from database and optionally delete the .dcm filesAddedfilter_roinames()andset_roi_filter()methods to facilitate filtering of roi name lists for child classesversion 0.0.3Improved documentation and docstringsuniformity in calling send and copy routines