pydicom / pynetdicom

A Python implementation of the DICOM networking protocol
https://pydicom.github.io/pynetdicom
MIT License
510 stars 180 forks source link

Invalid 'Called AE Title' value 'LWPACS' - must not be an empty str #967

Closed WenzheDai closed 1 month ago

WenzheDai commented 2 months ago

I solved the character problem by using the link in the way, but occasionally this error occurs. That link is: here

File "/usr/local/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/dul.py", line 445, in run_reactor
    self.state_machine.do_action(event)
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/fsm.py", line 90, in do_action
    next_state = action[1](self.dul)
                 ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/fsm.py", line 232, in AE_3
    dul.to_user_queue.put(cast("A_ASSOCIATE", pdu.to_primitive()))
                                              ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/pdu.py", line 899, in to_primitive
    primitive.called_ae_title = self.reserved_aet
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/pdu_primitives.py", line 206, in called_ae_title
    str, set_ae(value, "Called AE Title", False, False)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pynetdicom/utils.py", line 192, in set_ae
    raise ValueError(msg)
ValueError: Invalid 'Called AE Title' value - must not be an empty str

environment: Linux-5.3.0-64-generic-x86_64-with-glibc2.27 Python 3.11.5 (main, Dec 22 2023, 06:42:28) [GCC 7.5.0] pydicom 2.4.2 pynetdicom 2.0.2

I'd like to know if there's any way to solve this problem or avoid it.

scaramallion commented 2 months ago

Could you post your code that produces the exception above, please? And if possible the output from:

from pynetdicom import AE, debug_logger
from pynetdicom import _config
from pynetdicom.sop_class import Verification

def my_validator(value): 
    print(f"AE: '{value}'")
    enc = [f"{x:02X}" for x in value.encode("ascii")]
    print(f"Encoded AE: {' '.join(enc)}")
    return (True, "")

_config.VALIDATORS['AE'] = my_validator

debug_logger()

ae = AE()
ae.add_requested_context(Verification)
assoc = ae.associate("ip addr", port)  # fill in your actual values
if assoc.is_established:
    status = assoc.send_c_echo()
    assoc.release()

And maybe try with v2.1.1, there was a fix for testing reserved A-ASSOCIATE-AC values.

WenzheDai commented 2 months ago

I have to say that the above problems do not always occur, which also makes me confused.

These are my code:

import traceback
from typing import Callable, Tuple
import pydicom
import pynetdicom
from loguru import logger
from pydicom import uid
from pynetdicom import _config, _validators, sop_class
from pynetdicom.status import code_to_category

from common_utils.pacs_postman.entities import AeInfo
from common_utils.pacs_postman.envs import global_envs
from common_utils.pacs_postman.errors import AssociateError

class Associate:
    def __init__(
        self, ae_info: AeInfo, client_ae_title: str, task_type: str, color: bool = True):
        self.server_ip = ae_info.server_ip
        self.server_port = ae_info.server_port
        self.server_ae = ae_info.server_ae
        self.ae_instance: pynetdicom.ae.ApplicationEntity = self._gen_ae_instance(
            client_ae_title, task_type, color
        )
    def _gen_ae_instance(
        self, client_ae_title: str, task_type: str, color: bool
    ) -> pynetdicom.ae.ApplicationEntity:
        ae_instance = pynetdicom.AE(ae_title=client_ae_title)
        if task_type == "push":
            ae_instance.add_requested_context(
                sop_class.CTImageStorage,
                [uid.ImplicitVRLittleEndian, uid.ExplicitVRLittleEndian],
            )
            self._reset_ae_title_validator()
        else:
            PrintManagementMetaSopClass = (
                sop_class.BasicColorPrintManagementMeta
                if color
                else sop_class.BasicGrayscalePrintManagementMeta
            )
            ae_instance.add_requested_context(PrintManagementMetaSopClass)
        return ae_instance

    def __enter__(self) -> pynetdicom.association.Association:
        self.assoc: pynetdicom.association.Association = self.ae_instance.associate(
            addr=self.server_ip, port=self.server_port, ae_title=self.server_ae
        )
        if self.assoc.is_established:
            return self.assoc
        raise AssociateError("associate Faild")

    def __exit__(self, exc_type, exc_val, exc_tb):  # type:ignore
        self.assoc.release()
        if exc_type:
            logger.error(f"Associate Failed, {traceback.format_exc()}")
        return False

    def _reset_ae_title_validator(self) -> None:
        if self.server_ae not in global_envs.UNVALIDATED_AE_TITLES:
            _config.VALIDATORS["AE"] = _validators.validate_ae
            return

        def my_validator(value: str) -> Tuple[bool, str]:
            logger.info(f"AE: {value}")
            enc = [f"{x:02X}" for x in value.encode("ascii")]
            logger.info(f"Encoded AE: {' '.join(enc)}")
            return (True, value)

        _config.VALIDATORS["AE"] = my_validator

with _associate.Associate(ae_info, client_ae_title, "push") as assoc:
     logger.info("AE instance start ...")
     for image_ds in images_ds_list:
         try:
             status = assoc.send_c_store(image_ds)
             _associate.check_status(status, StoreSCUError, "Store SCU")
         except Exception:
             if retry is False:
                 raise

             logger.warning(f"Store SCU error: {traceback.format_exc()}")

These are the outputs of your code: why do not you use ae_title when you use associate to connect ?

AE: 'PYNETDICOM'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D
AE: 'ANY-SCP'
Encoded AE: 41 4E 59 2D 53 43 50
AE: 'PYNETDICOM'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D
AE: 'PYNETDICOM_202'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D 5F 32 30 32
AE: 'PYNETDICOM_202'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D 5F 32 30 32
I: Requesting Association
AE: 'PYNETDICOM'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D
AE: 'ANY-SCP'
Encoded AE: 41 4E 59 2D 53 43 50
AE: 'Default'
Encoded AE: 44 65 66 61 75 6C 74
AE: 'Default'
Encoded AE: 44 65 66 61 75 6C 74
AE: 'PYNETDICOM'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D
AE: 'ANY-SCP'
Encoded AE: 41 4E 59 2D 53 43 50
AE: 'PYNETDICOM_202'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D 5F 32 30 32
D: Request Parameters:
D: ======================= OUTGOING A-ASSOCIATE-RQ PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.2.0.2
D: Our Implementation Version Name:   PYNETDICOM_202
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM
D: Called Application Name:     ANY-SCP
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Verification SOP Class
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Implicit VR Little Endian
D:       =Explicit VR Little Endian
D:       =Deflated Explicit VR Little Endian
D:       =Explicit VR Big Endian
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested Asynchronous Operations Window Negotiation: None
D: Requested User Identity Negotiation: None
D: ========================== END A-ASSOCIATE-RQ PDU ==========================
AE: 'LW PACS v7.010'
Encoded AE: 4C 57 20 50 41 43 53 20 76 37 2E 30 31 30
D: Accept Parameters:
D: ======================= INCOMING A-ASSOCIATE-AC PDU ========================
D: Their Implementation Class UID:    1.2.826.0.1.3680043.2.461
D: Their Implementation Version Name: LW PACS v7.010
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM
D: Called Application Name:     ANY-SCP
D: Their Max PDU Receive Size:  16382
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =Verification SOP Class
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Implicit VR Little Endian
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
AE: 'ANY-SCP'
Encoded AE: 41 4E 59 2D 53 43 50 00 00 00 00 00 0F 00 00 00
AE: 'PYNETDICOM'
Encoded AE: 50 59 4E 45 54 44 49 43 4F 4D 00 00 0F 00 00 00
AE: 'LW PACS v7.010'
Encoded AE: 4C 57 20 50 41 43 53 20 76 37 2E 30 31 30
I: Association Accepted
I: Sending Echo Request: MsgID 1
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Received Echo Response (Status: 0x0000 - Success)
I: Releasing Association
scaramallion commented 2 months ago

Can you test with v2.1.1? The exception is coming from incorrectly checking the values for the reserved Called AE Title parameter which got fixed in 2.1.0.