예제 #1
0
    def test_uid_conformance_false(self):
        """Test UID conformance with ENFORCE_UID_CONFORMANCE = False"""
        _config.ENFORCE_UID_CONFORMANCE = False
        primitive = SOPClassExtendedNegotiation()

        primitive.sop_class_uid = 'abc'
        assert primitive.sop_class_uid == 'abc'
예제 #2
0
    def test_uid_conformance_true(self):
        """Test UID conformance with ENFORCE_UID_CONFORMANCE = True"""
        _config.ENFORCE_UID_CONFORMANCE = True
        primitive = SOPClassExtendedNegotiation()

        with pytest.raises(ValueError):
            primitive.sop_class_uid = 'abc'
예제 #3
0
    def _check_sop_class_extended(self):
        """Check the user's response to a SOP Class Extended request.

        Returns
        -------
        list of pdu_primitives.SOPClassExtendedNegotiation
            The SOP Class Extended Negotiation items to be sent in response
        """
        # pylint: disable=broad-except
        try:
            default = evt.get_default_handler(evt.EVT_SOP_EXTENDED)
            if self.assoc.get_handlers(evt.EVT_SOP_EXTENDED) != default:
                user_response = evt.trigger(
                    self.assoc,
                    evt.EVT_SOP_EXTENDED,
                    {'app_info' : self.assoc.requestor.sop_class_extended}
                )
            else:
                user_response = self.assoc.ae.on_sop_class_extended(
                    self.assoc.requestor.sop_class_extended
                )
        except Exception as exc:
            user_response = {}
            LOGGER.error(
                "Exception raised in user's 'on_sop_class_extended' "
                "implementation"
            )
            LOGGER.exception(exc)

        if not isinstance(user_response, (type(None), dict)):
            LOGGER.error(
                "Invalid type returned by user's 'on_sop_class_extended' "
                "implementation"
            )
            user_response = {}

        if not user_response:
            return []

        items = []
        for sop_class, app_info in user_response.items():
            try:
                item = SOPClassExtendedNegotiation()
                item.sop_class_uid = sop_class
                item.service_class_application_information = app_info
                items.append(item)
            except Exception as exc:
                LOGGER.error(
                    "Unable to set the SOP Class Extended Negotiation "
                    "response values for the SOP Class UID {}"
                    .format(sop_class)
                )
                LOGGER.exception(exc)

        return items
예제 #4
0
    def test_conversion(self):
        """ Check converting to PDU item works correctly """
        primitive = SOPClassExtendedNegotiation()
        primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2'
        primitive.service_class_application_information = (
            b'\x02\x00\x03\x00\x01\x00')
        item = primitive.from_primitive()

        assert item.encode() == (
            b'\x56\x00\x00\x21\x00\x19\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30'
            b'\x30\x30\x38\x2e\x35\x2e\x31\x2e\x34\x2e\x31\x2e\x31\x2e\x32'
            b'\x02\x00\x03\x00\x01\x00')
예제 #5
0
    def add_sop_ext(self, primitive):
        """Add SOP Class Extended to the A-ASSOCIATE primitive."""
        req = {
            '1.2.3.4': b'\x00\x01',
            '1.2.840.10008.1.1': b'\x00\x01\x02\x03' * 10
        }

        for uid, data in req.items():
            item = SOPClassExtendedNegotiation()
            item.sop_class_uid = uid
            item.service_class_application_information = data
            primitive.user_information.append(item)
예제 #6
0
    def _check_sop_class_extended(self):
        """Check the user's response to a SOP Class Extended request.

        Returns
        -------
        list of pdu_primitives.SOPClassExtendedNegotiation
            The SOP Class Extended Negotiation items to be sent in response
        """
        # pylint: disable=broad-except
        try:
            user_response = evt.trigger(
                self.assoc,
                evt.EVT_SOP_EXTENDED,
                {'app_info' : self.requestor.sop_class_extended}
            )
        except Exception as exc:
            user_response = {}
            LOGGER.error(
                "Exception raised in handler bound to 'evt.EVT_SOP_EXTENDED'"
            )
            LOGGER.exception(exc)

        if not isinstance(user_response, (type(None), dict)):
            LOGGER.error(
                "Invalid type returned by handler bount to "
                "'evt.EVT_SOP_EXTENDED'"
            )
            user_response = {}

        if not user_response:
            return []

        items = []
        for sop_class, app_info in user_response.items():
            try:
                item = SOPClassExtendedNegotiation()
                item.sop_class_uid = sop_class
                item.service_class_application_information = app_info
                items.append(item)
            except Exception as exc:
                LOGGER.error(
                    f"Unable to set the SOP Class Extended Negotiation "
                    f"response values for the SOP Class UID {sop_class}"
                )
                LOGGER.exception(exc)

        return items
예제 #7
0
    def test_scp_handler_sop_extended(self):
        """Test handler event's assoc attribute with SOP Class Extended"""
        attrs = {}

        def handle_sop(event):
            return event.app_info

        def handle(event):
            attrs["context"] = event.context
            attrs["assoc"] = event.assoc
            attrs["request"] = event.request
            attrs["dataset"] = event.dataset
            return 0x0000

        handlers = [(evt.EVT_C_STORE, handle),
                    (evt.EVT_SOP_EXTENDED, handle_sop)]

        self.ae = ae = AE()
        ae.add_supported_context(CTImageStorage)
        ae.add_requested_context(CTImageStorage)
        scp = ae.start_server(("localhost", 11112),
                              block=False,
                              evt_handlers=handlers)

        ext_neg = []
        item = SOPClassExtendedNegotiation()
        item.sop_class_uid = "1.2.3"
        item.service_class_application_information = b"\x00\x01"
        ext_neg.append(item)

        item = SOPClassExtendedNegotiation()
        item.sop_class_uid = "1.2.4"
        item.service_class_application_information = b"\x00\x02"
        ext_neg.append(item)

        assoc = ae.associate("localhost", 11112, ext_neg=ext_neg)
        assert assoc.is_established
        status = assoc.send_c_store(DATASET)
        assert status.Status == 0x0000
        assoc.release()
        assert assoc.is_released

        ext = attrs["assoc"].acceptor.sop_class_extended
        assert ext["1.2.3"] == b"\x00\x01"
        assert ext["1.2.4"] == b"\x00\x02"

        scp.shutdown()
예제 #8
0
    def test_scp_handler_sop_class_extended(self):
        """Test that the SOP Class Extended info is available."""
        def on_ext(req):
            return req

        self.scp = DummyStorageSCP()
        self.scp.ae.on_sop_class_extended = on_ext
        self.scp.start()

        ae = AE()
        ae.add_requested_context(CTImageStorage)
        ae.acse_timeout = 5
        ae.dimse_timeout = 5

        ext_neg = []
        item = SOPClassExtendedNegotiation()
        item.sop_class_uid = '1.2.3'
        item.service_class_application_information = b'\x00\x01'
        ext_neg.append(item)

        item = SOPClassExtendedNegotiation()
        item.sop_class_uid = '1.2.4'
        item.service_class_application_information = b'\x00\x02'
        ext_neg.append(item)

        assoc = ae.associate('localhost', 11112, ext_neg=ext_neg)
        assert assoc.is_established
        status = assoc.send_c_store(DATASET)
        assert status.Status == 0x0000
        assoc.release()
        assert assoc.is_released

        info = self.scp.info['sop_class_extended']
        assert len(info) == 2
        assert info['1.2.3'] == b'\x00\x01'
        assert info['1.2.4'] == b'\x00\x02'

        self.scp.stop()
예제 #9
0
    def test_assignment_and_exceptions(self):
        """ Check incorrect types/values for properties raise exceptions """
        primitive = SOPClassExtendedNegotiation()

        ## Check assignment
        # SOP Class UID
        reference_uid = UID('1.2.840.10008.5.1.4.1.1.2')

        primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2'
        assert primitive.sop_class_uid == reference_uid

        primitive.sop_class_uid = '1.2.840.10008.5.1.4.1.1.2'
        assert primitive.sop_class_uid == reference_uid

        primitive.sop_class_uid = UID('1.2.840.10008.5.1.4.1.1.2')
        assert primitive.sop_class_uid == reference_uid

        # Service Class Application Information
        primitive.service_class_application_information = (
            b'\x02\x00\x03\x00\x01\x00')
        assert primitive.service_class_application_information == (
            b'\x02\x00\x03\x00\x01\x00')

        ## Check exceptions
        # SOP Class UID
        with pytest.raises(TypeError):
            primitive.sop_class_uid = 10

        with pytest.raises(TypeError):
            primitive.sop_class_uid = 45.2

        # Service Class Application Information
        with pytest.raises(TypeError):
            primitive.service_class_application_information = 10

        with pytest.raises(TypeError):
            primitive.service_class_application_information = 45.2

        # Python 2 compatibility all bytes are str
        #with pytest.raises(TypeError):
        #    primitive.service_class_application_information = 'abc'

        # No value set
        primitive = SOPClassExtendedNegotiation()
        with pytest.raises(ValueError):
            item = primitive.from_primitive()

        primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2'
        with pytest.raises(ValueError):
            item = primitive.from_primitive()

        primitive = SOPClassExtendedNegotiation()
        primitive.service_class_application_information = (
            b'\x02\x00\x03\x00\x01\x00')
        with pytest.raises(ValueError):
            item = primitive.from_primitive()
예제 #10
0
    def test_assignment_and_exceptions(self):
        """Check incorrect types/values for properties raise exceptions"""
        primitive = SOPClassExtendedNegotiation()

        ## Check assignment
        # SOP Class UID
        reference_uid = UID("1.2.840.10008.5.1.4.1.1.2")

        primitive.sop_class_uid = None
        assert primitive.sop_class_uid is None
        primitive.sop_class_uid = b"1.2.840.10008.5.1.4.1.1.2"
        assert primitive.sop_class_uid == reference_uid
        primitive.sop_class_uid = "1.2.840.10008.5.1.4.1.1.2"
        assert primitive.sop_class_uid == reference_uid
        primitive.sop_class_uid = UID("1.2.840.10008.5.1.4.1.1.2")
        assert primitive.sop_class_uid == reference_uid

        # Service Class Application Information
        primitive.service_class_application_information = None
        assert primitive.service_class_application_information is None
        primitive.service_class_application_information = b"\x02\x00\x03\x00\x01\x00"
        assert primitive.service_class_application_information == (
            b"\x02\x00\x03\x00\x01\x00"
        )

        ## Check exceptions
        # SOP Class UID
        with pytest.raises(TypeError):
            primitive.sop_class_uid = 10

        with pytest.raises(TypeError):
            primitive.sop_class_uid = 45.2

        # Service Class Application Information
        with pytest.raises(TypeError):
            primitive.service_class_application_information = 10

        with pytest.raises(TypeError):
            primitive.service_class_application_information = 45.2

        # No value set
        primitive = SOPClassExtendedNegotiation()
        with pytest.raises(ValueError):
            item = primitive.from_primitive()

        primitive.sop_class_uid = b"1.2.840.10008.5.1.4.1.1.2"
        with pytest.raises(ValueError):
            item = primitive.from_primitive()

        primitive = SOPClassExtendedNegotiation()
        primitive.service_class_application_information = b"\x02\x00\x03\x00\x01\x00"
        with pytest.raises(ValueError):
            item = primitive.from_primitive()
예제 #11
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)
예제 #12
0
def main(args=None):
    """Run the application."""
    if args is not None:
        sys.argv = args

    args = _setup_argparser()

    if args.version:
        print(f"getscu.py v{__version__}")
        sys.exit()

    APP_LOGGER = setup_logging(args, "getscu")
    APP_LOGGER.debug(f"getscu.py v{__version__}")
    APP_LOGGER.debug("")

    # Create query (identifier) dataset
    try:
        # If you're looking at this to see how QR Get 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)

    # Exclude these SOP Classes
    _exclusion = [
        EncapsulatedSTLStorage,
        EncapsulatedOBJStorage,
        EncapsulatedMTLStorage,
    ]
    store_contexts = [
        cx for cx in StoragePresentationContexts
        if cx.abstract_syntax not in _exclusion
    ]

    # Create application entity
    # Binding to port 0 lets the OS pick an available port
    ae = AE(ae_title=args.calling_aet)
    ae.acse_timeout = args.acse_timeout
    ae.dimse_timeout = args.dimse_timeout
    ae.network_timeout = args.network_timeout

    # Extended Negotiation - SCP/SCU Role Selection
    ext_neg = []
    ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet)
    ae.add_requested_context(StudyRootQueryRetrieveInformationModelGet)
    ae.add_requested_context(PatientStudyOnlyQueryRetrieveInformationModelGet)
    for cx in store_contexts:
        ae.add_requested_context(cx.abstract_syntax)
        # Add SCP/SCU Role Selection Negotiation to the extended negotiation
        # We want to act as a Storage SCP
        ext_neg.append(build_role(cx.abstract_syntax, scp_role=True))

    if args.study:
        query_model = StudyRootQueryRetrieveInformationModelGet
    elif args.psonly:
        query_model = PatientStudyOnlyQueryRetrieveInformationModelGet
    else:
        query_model = PatientRootQueryRetrieveInformationModelGet

    # Extended Negotiation - SOP Class Extended
    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.append(item)

    # Request association with remote
    assoc = ae.associate(
        args.addr,
        args.port,
        ae_title=args.called_aet,
        ext_neg=ext_neg,
        evt_handlers=[(evt.EVT_C_STORE, handle_store, [args, APP_LOGGER])],
        max_pdu=args.max_pdu,
    )

    if assoc.is_established:
        # Send query
        responses = assoc.send_c_get(identifier, query_model)
        for (status, rsp_identifier) in responses:
            # If `status.Status` is one of the 'Pending' statuses then
            #   `rsp_identifier` is the C-GET 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()
    else:
        sys.exit(1)
예제 #13
0
def main(args=None):
    """Run the application."""
    if args is not None:
        sys.argv = args

    args = _setup_argparser()

    if args.version:
        print(f"findscu.py v{__version__}")
        sys.exit()

    APP_LOGGER = setup_logging(args, "findscu")
    APP_LOGGER.debug(f"findscu.py v{__version__}")
    APP_LOGGER.debug("")

    # Create query (identifier) dataset
    try:
        # If you're looking at this to see how QR Find 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
    # Binding to port 0 lets the OS pick an available port
    ae = AE(ae_title=args.calling_aet)

    # Set timeouts
    ae.acse_timeout = args.acse_timeout
    ae.dimse_timeout = args.dimse_timeout
    ae.network_timeout = args.network_timeout

    # Set the Presentation Contexts we are requesting the Find SCP support
    ae.requested_contexts = (QueryRetrievePresentationContexts +
                             BasicWorklistManagementPresentationContexts)

    # Query/Retrieve Information Models
    if args.worklist:
        query_model = ModalityWorklistInformationFind
    elif args.study:
        query_model = StudyRootQueryRetrieveInformationModelFind
    elif args.psonly:
        query_model = PatientStudyOnlyQueryRetrieveInformationModelFind
    else:
        query_model = PatientRootQueryRetrieveInformationModelFind

    # Extended Negotiation
    ext_neg = []
    ext_opts = [
        args.relational_query,
        args.dt_matching,
        args.fuzzy_names,
        args.timezone_adj,
        args.enhanced_conversion,
    ]
    if not args.worklist and 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]
    elif args.worklist and any([args.fuzzy_names, args.timezone_adj]):
        app_info = b"\x01\x01"
        for option in [args.fuzzy_names, args.timezone_adj]:
            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 (QR/BWM) Find SCP
    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 C-FIND request, `responses` is a generator
        responses = assoc.send_c_find(identifier, query_model)
        # Used to generate filenames if args.write used
        fname = generate_filename()
        for (status, rsp_identifier) in responses:
            # If `status.Status` is one of the 'Pending' statuses then
            #   `rsp_identifier` is the C-FIND response's Identifier dataset
            if status and status.Status in [0xFF00, 0xFF01]:
                if args.write:
                    rsp_identifier.file_meta = get_file_meta(
                        assoc, query_model)
                    rsp_identifier.save_as(next(fname),
                                           write_like_original=False)

        # Release the association
        assoc.release()
    else:
        sys.exit(1)