def test_ae_title_good(self): """ Check AE title change produces good value """ ae = AE(scu_sop_class=['1.2.840.10008.1.1']) ae.ae_title = ' TEST ' self.assertTrue(ae.ae_title == 'TEST ') ae.ae_title = ' TEST' self.assertTrue(ae.ae_title == 'TEST ') ae.ae_title = ' TEST' self.assertTrue(ae.ae_title == 'TEST ') ae.ae_title = 'a TEST' self.assertTrue(ae.ae_title == 'a TES') ae.ae_title = 'a TEST' self.assertTrue(ae.ae_title == 'a TEST ')
def setupDicomSCP(self, port=11112): global scpdir ae = AE() ae.ae_title = 'Temp' ae.supported_contexts = StoragePresentationContexts # ae.on_c_store = obj.on_c_store handlers = [(evt.EVT_C_STORE, self.handle_store)] # Returns a ThreadedAssociationServer instance self.server = ae.start_server(('localhost', port), block=False, evt_handlers=handlers) scpdir = tempfile.TemporaryDirectory()
def main(args=None): """Run the application.""" if args is not None: sys.argv = args args = _setup_argparser() if args.version: print(f"movescu.py v{__version__}") sys.exit() APP_LOGGER = setup_logging(args, "movescu") APP_LOGGER.debug(f"movescu.py v{__version__}") APP_LOGGER.debug("") # Create query (identifier) dataset try: # If you're looking at this to see how QR Move works then `identifer` # is a pydicom Dataset instance with your query keys, e.g.: # identifier = Dataset() # identifier.QueryRetrieveLevel = 'PATIENT' # identifier.PatientName = '*' identifier = create_dataset(args, APP_LOGGER) except Exception as exc: APP_LOGGER.exception(exc) sys.exit(1) # Create application entity ae = AE() # Start the Store SCP (optional) scp = None if args.store: transfer_syntax = ALL_TRANSFER_SYNTAXES[:] store_handlers = [(evt.EVT_C_STORE, handle_store, [args, APP_LOGGER])] ae.ae_title = args.store_aet for cx in AllStoragePresentationContexts: ae.add_supported_context(cx.abstract_syntax, transfer_syntax) scp = ae.start_server(("localhost", args.store_port), block=False, evt_handlers=store_handlers) ae.ae_title = args.calling_aet ae.acse_timeout = args.acse_timeout ae.dimse_timeout = args.dimse_timeout ae.network_timeout = args.network_timeout ae.requested_contexts = QueryRetrievePresentationContexts ae.supported_contexts = [] # Query/Retrieve Information Models if args.study: query_model = StudyRootQueryRetrieveInformationModelMove elif args.psonly: query_model = PatientStudyOnlyQueryRetrieveInformationModelMove else: query_model = PatientRootQueryRetrieveInformationModelMove # Extended Negotiation ext_neg = [] ext_opts = [args.relational_retrieval, args.enhanced_conversion] if any(ext_opts): app_info = b"" for option in ext_opts: app_info += b"\x01" if option else b"\x00" item = SOPClassExtendedNegotiation() item.sop_class_uid = query_model item.service_class_application_information = app_info ext_neg = [item] # Request association with remote AE assoc = ae.associate( args.addr, args.port, ae_title=args.called_aet, max_pdu=args.max_pdu, ext_neg=ext_neg, ) if assoc.is_established: # Send query move_aet = args.move_aet or args.calling_aet responses = assoc.send_c_move(identifier, move_aet, query_model) for (status, rsp_identifier) in responses: # If `status.Status` is one of the 'Pending' statuses then # `rsp_identifier` is the C-MOVE response's Identifier dataset if status and status.Status in [0xFF00, 0xFF01]: # `rsp_identifier` is a pydicom Dataset containing a query # response. You may want to do something interesting here... pass assoc.release() _EXIT_VALUE = 0 else: _EXIT_VALUE = 1 # Shutdown the Storage SCP (if used) if scp: scp.shutdown() sys.exit(_EXIT_VALUE)
def perform_move(self, self_info, source, destination, query={}, self_dir=None, anonymize_headers=True, studyuid_map=None, moved_studyuids={}, verbose=False): # ------------------ 1. Prepare options/parameters # set storage directory self.DESTINATION_DIRECTORY = self_dir if self_dir is not None else self.DESTINATION_DIRECTORY # set studyUID->studydir mappings self.STUDYUID_MAP = studyuid_map # anonymize self.ANONYMIZE_HEADERS = anonymize_headers # Track success of each query (will only be updated if we are moving to our self.MOVED_STUDYUIDS = moved_studyuids # ------------------ 2. Create our SCU (outgoing query sender) ae = AE() ae.ae_title = self_info['peer_aet'] # Add a requested presentation contexts (contexts that our SCU will SEND aka REQUEST) ae.add_requested_context(PatientRootQueryRetrieveInformationModelMove) # ------------------ 3. If we're transferring to ourselves, our SCU will double as a nonblocking SCP # START NON-BLOCKING STORAGE SCP in separate thread to receive the MOVE if destination['peer_name'] == self.main.SELF: ae.supported_contexts = StoragePresentationContexts handlers = [(evt.EVT_C_STORE, self.handle_store)] scp = ae.start_server(('', int(destination['peer_port'])), block=False, evt_handlers=handlers) # ------------------ 4. Connect to peer assoc = ae.associate(source['peer_ip'], int(source['peer_port']), ae_title=source['peer_aet']) # ------------------ 5. Create query # --- Add tags to dataset ds = Dataset() for tag_name, value in query.items(): if value: ds = self.set_tag(ds=ds, tag=self.get_tag_from_tagname(tag_name), value=value) # --- Query Retrieve Level ds = self.establish_qrlevel(ds) # ------------------ 6. Perform C_MOVE - move files to the destination (which must already be whitelisted in # the peer SCP) Only need to send the AET of the destination, as the IP/port of that AET should already be # known in the peer SCP's whitelist responses = assoc.send_c_move(ds, destination['peer_aet'], query_model='P') # --- check what transpired for (status, identifier) in responses: if status: # If the status is 'Pending', then the identifier is the C-MOVE response if status.Status in (0xFF00, 0xFF01): if verbose: print(' C-MOVE PENDING') pass else: if verbose: print(' C-MOVE COMPLETE') pass else: if verbose: print( ' Connection timed out, was aborted or received invalid response' ) pass # ------------------ 7. Close our local SCP scp.shutdown() # ------------------ 8. Release our SCU's connection to the peer assoc.release() # ------------------ 9. clear the anonymization mappings # technically don't need to do this since it's overwritten by the next call, but it helps me sleep at night self.STUDYUID_MAP = {} # ------------------ 10. return a dict with the names and counts of the original studyUIDs we've successfully # saved to file # 'self.MOVED_STUDYUIDS' is populated in the function on_c_store, which is called when our peer sends us data # and hence will only run when we ourselves are the MOVE destination return self.MOVED_STUDYUIDS
def perform_find(self, self_info, peer_info, query={}, verbose=[], xlsx_file=None, results=None): # ------------------ 1. CONNECT TO PEER ae = AE() ae.ae_title = self_info['peer_aet'] # --- Add a requested presentation contexts (contexts that our SCU will SEND aka REQUEST) ae.add_requested_context(PatientRootQueryRetrieveInformationModelFind) # --- holla assoc = ae.associate(peer_info['peer_ip'], int(peer_info['peer_port']), ae_title=peer_info['peer_aet']) # ------------------ 2. CONFIGURE QUERY # - Add any tags of interest that were passed into this function. # >Tags with associated values: act as filters - any response will match those parameters # >Tags without associated values: acts as a wildcard (will match anything) and thus act as queries - the response # will return the values for those tags # 'results' is a dict of tags/lists where we will store our query results in a (heading -> column) mapping if results is None: results = {} # --- Craft the final query dataset structure using our passed in query dict ds = Dataset() for tag_name, value in query.items(): ds = self.set_tag(ds=ds, tag=self.get_tag_from_tagname(tag_name), value=value) # --- Add these tag names into the results dict as well if tag_name not in results: results[tag_name] = [] # --- define QueryRetrieveLevel (PATIENT, STUDY, SERIES, IMAGE) # 'PATIENT' level == return one result per unique patient # 'STUDY' level == return one result per unique study # 'SERIES' level == return one result per unique series # use SERIES if we have it, otherwise default to STUDY ds = self.establish_qrlevel(ds) """ # We always want at bare minium a studyUID, so here we guarantee that it is in the query if 'studyUID' not in query: ds = self.set_tag(ds=ds, tag=self.get_tag_from_tagname('studyUID'), value='*') query['studyUID'] = '*' if 'studyUID' not in results: results['studyUID'] = [] """ # ------------------ 3. PERFORM QUERY (C-FIND) # Query model is 'P' for Patient. This isn't something we need to worry about, it's just the standard way to # organize the data #print('----- FIND -----') responses_find = assoc.send_c_find(ds, query_model='P') # ------------------ 4. PROCESS QUERY RESULTS # C-FIND returns a list of tuples (status-code, dataset) that match the query. push the datasets into 'matches' matches = [] # Parse through each raw query response, and save the identifiers for (status, identifier) in responses_find: if status: # If the find status is still 'Pending' then `identifier` describes one matching study if status.Status in (0xFF00, 0xFF01): matches.append(identifier) else: # Find is complete, no more matching studies # if len(verbose) > 1: print('\t\tC-FIND Complete') pass else: if len(verbose) > 0: print( '\t\tConnection timed out, was aborted or received invalid response' ) # ------------------ 5. Prepare to return a dictionary of our find results # FOR EACH MATCH FOR THIS PARTICULAR QUERY for match in matches: output = '' # --- push the results of this match into the 'results' dict for tag in query: value = '' # Use the tag tuple identifier (defined in pac.TAGS) to check tags in the query # response val = match.get(self.get_tag_from_tagname(tag)) if val is not None: value = str(val.value) results[tag].append(value) # ------------------ 6. Release the connection to the peer assoc.release() # ------------------ 7. Spread the word of our lord and savior jesus christ return results
return 0x0000 handlers = [(evt.EVT_C_STORE, handle_store)] # Initialise the Application Entity ae = AE() # Add a requested presentation context ae.supported_contexts(PatientRootQueryRetrieveInformationModelMove) # Add the Storage SCP's supported presentation contexts ae.supported_contexts = StoragePresentationContexts # Start our Storage SCP in non-blocking mode, listening on port 11120 ae.ae_title = b'OUR_STORE_SCP' scp = ae.start_server(('', 11112), block=False, evt_handlers=handlers) # Create out identifier (query) dataset ds = Dataset() ds.QueryRetrieveLevel = 'SERIES' # Unique key for PATIENT level ds.PatientID = '1234567' # Unique key for STUDY level ds.StudyInstanceUID = '1.2.3' # Unique key for SERIES level ds.SeriesInstanceUID = '1.2.3.4' # Associate with peer AE at IP 127.0.0.1 and port 11112 assoc = ae.associate('127.0.0.1', 11112)
def FnMoveScu(UID, nombre): q = 't' def handle_store(event): """Handle a C-STORE request event.""" ds = event.dataset ds.file_meta = event.file_meta # Save the dataset using the SOP Instance UID as the filename ds.save_as(ds.SOPInstanceUID, write_like_original=False) # Return a 'Success' status return 0x0000 handlers = [(evt.EVT_C_STORE, handle_store)] # Initialise the Application Entity ae = AE() # Add a requested presentation context ae.add_requested_context(PatientRootQueryRetrieveInformationModelMove) #ae.add_requested_context(CTImageStorage) # Add the Storage SCP's supported presentation contexts ae.supported_contexts = StoragePresentationContexts ae.add_requested_context('1.2.840.10008.5.1.4.1.1.2', transfer_syntax='1.2.840.10008.1.2.4.91') ae.add_supported_context('1.2.840.10008.5.1.4.1.1.2', transfer_syntax='1.2.840.10008.1.2.4.91') # Start our Storage SCP in non-blocking mode, listening on port 104 ae.ae_title = b'OUR_STORE_SCP' #print('---- ae -------') #print(ae) scp = ae.start_server(('', 11120), block=False, evt_handlers=handlers) # Create out identifier (query) dataset ds = Dataset() ds.QueryRetrieveLevel = 'STUDY' #ds.PatientID = '0' ds.StudyInstanceUID = UID ds.PatientName = nombre #ds.SOPInstanceUID = '1.2.826.0.1.3680043.8.1055.1.20111102150758825.42401392.26682309' # Associate with peer AE assoc = ae.associate('127.0.0.1', 11112) if assoc.is_established: # Use the C-MOVE service to send the identifier responses = assoc.send_c_move( ds, b'OUR_STORE_SCP', PatientRootQueryRetrieveInformationModelMove) for (status, identifier) in responses: if status: print('C-MOVE query status: 0x{0:04x}'.format(status.Status)) #print(status.NumberOfCompletedSuboperations) q = status.NumberOfCompletedSuboperations q = str(q) #print('iden:',identifier) #print('status',status) # If the status is 'Pending' then `identifier` is the C-MOVE response else: print( 'Se agotó el tiempo de conexión, se canceló o recibió una respuesta no válida' ) # Release the association assoc.release() else: print('Asociación rechazada, abortada o nunca conectada') # Stop our Storage SCP #scp.shutdown() return (q)
APP_LOGGER.error( 'Directory may not exist or you may not have write ' 'permission') # Failed - Out of Resources - IOError status_ds.Status = 0xA700 except: APP_LOGGER.error('Could not write file to specified directory:') APP_LOGGER.error(" {0!s}".format(os.path.dirname(filename))) # Failed - Out of Resources - Miscellaneous error status_ds.Status = 0xA701 return status_ds store_handlers = [(evt.EVT_C_STORE, handle_store)] ae.ae_title = args.store_aet ae.supported_contexts = StoragePresentationContexts scp = ae.start_server(('', args.store_port), block=False, evt_handlers=store_handlers) ae.ae_title = args.calling_aet ae.requested_contexts = QueryRetrievePresentationContexts # Request association with remote AE assoc = ae.associate(args.peer, args.port, ae_title=args.called_aet) if assoc.is_established: # Create query dataset ds = Dataset() ds.PatientName = '*'