Ejemplo n.º 1
0
 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()
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
            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 = '*'