大家好

最近遇到一個問題
需要在原本的應用程式中添加查詢PACS服務上的dicom 影像功能
到處爬文找到很多文章:
 

### DICOM医学图像处理:fo-dicom网络传输之C-FIND and C-MOVE

 

### C-MOVE SCU端需要实现C-STORE SCP服务

 

## fo-dicom 套件

 
測試許久之後
發現查詢相對要簡單
但是要下載就是需要啟動一個臨時的 DicomService 來接收 dicom 影像
 
首先要在NuGet安裝  fo-dicom 這套件

有 github,但是我是在NuGet安裝的
至於版本我沒有測試出差異
 

## 查詢使用 C-FIND

這邊是新增查詢query
查詢條件預設是 Modality 與 studyDate
其他條件應該是用 cFind.Dataset.Add 方式加入
 
DicomCFindRequest cFind = DicomCFindRequest.CreateStudyQuery(
        modalitiesInStudy:"NM",
        studyDateTime: new DicomDateRange(dateTimePicker2.Value.AddDays(-1), dateTimePicker2.Value.AddDays(1))
        );
cFind.Dataset.Add(DicomTag.AccessionNumber, "N0*");
cFind.Dataset.Add(DicomTag.StudyTime, ""); // erases the tag 'StudyTime'
cFind.Dataset.Add(DicomTag.StudyInstanceUID, ""); // erases the tag 'Study Instance UID'
cFind.Dataset.Add(DicomTag.SeriesDescription, "");
cFind.Dataset.Add(DicomTag.StationName, "");


//新增取得返回資料事件
cFind.OnResponseReceived += (DicomCFindRequest request, DicomCFindResponse response) =>
{
    try
    {
        if (response.Dataset == null) { return; }
        string StudyInstanceUIDStr = response.Dataset.Get(DicomTag.StudyInstanceUID, "");
        cfindSUIDs.Add(StudyInstanceUIDStr);
    }
    catch (Exception ex)
    {
        MessageBox.Show("錯誤:" + ex.Message);
    }
};
 

##  C-MOVE 下載

然後如果要用 C-MOVE 功能則需要啟用 DicomService (會用到網路-需要注意該程式能夠通過防火牆)
啟動 DicomService 可以參考以下程式:
 
//新增自定義的 DicomService 類別 用於儲存 C MOVE檔案
public delegate DicomCStoreResponse OnCStoreRequestCallback(DicomCStoreRequest request);
class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
    private static DicomTransferSyntax[] AcceptedTransferSyntaxes = new DicomTransferSyntax[] {
            DicomTransferSyntax.ExplicitVRLittleEndian,
            DicomTransferSyntax.ExplicitVRBigEndian,
            DicomTransferSyntax.ImplicitVRLittleEndian
        };
    private static DicomTransferSyntax[] AcceptedImageTransferSyntaxes = new DicomTransferSyntax[] {
            // Lossless
            DicomTransferSyntax.JPEGLSLossless,
            DicomTransferSyntax.JPEG2000Lossless,
            DicomTransferSyntax.JPEGProcess14SV1,
            DicomTransferSyntax.JPEGProcess14,
            DicomTransferSyntax.RLELossless,
            // Lossy
            DicomTransferSyntax.JPEGLSNearLossless,
            DicomTransferSyntax.JPEG2000Lossy,
            DicomTransferSyntax.JPEGProcess1,
            DicomTransferSyntax.JPEGProcess2_4,
            // Uncompressed
            DicomTransferSyntax.ExplicitVRLittleEndian,
            DicomTransferSyntax.ExplicitVRBigEndian,
            DicomTransferSyntax.ImplicitVRLittleEndian
        };
    public CStoreSCP(Stream stream, Logger log) : base(stream, log)
    {
    }
    public void OnReceiveAssociationRequest(DicomAssociation association)
    {
        foreach (var pc in association.PresentationContexts)
        {
            if (pc.AbstractSyntax == DicomUID.Verification)
                pc.AcceptTransferSyntaxes(AcceptedTransferSyntaxes);
            else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None)
                pc.AcceptTransferSyntaxes(AcceptedImageTransferSyntaxes);
        }
        SendAssociationAccept(association);
    }
    public void OnReceiveAssociationReleaseRequest()
    {
        SendAssociationReleaseResponse();
    }
    public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
    {
    }
    public void OnConnectionClosed(int errorCode)
    {
    }
    public static OnCStoreRequestCallback OnCStoreRequestCallBack;
    public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
    {
        //自定義儲存方案
        if (OnCStoreRequestCallBack != null)
        {
            return OnCStoreRequestCallBack(request);
        }
        return new DicomCStoreResponse(request, DicomStatus.NoSuchActionType);
    }

    public void OnCStoreRequestException(string tempFileName, Exception e)
    {
    }

    public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request)
    {
        return new DicomCEchoResponse(request, DicomStatus.Success);
    }
    public void OnConnectionClosed(Exception exception)
    {
    }
}


// 設定 C-STORE SCP服務,接收C-MOVE SCP資料
StudyInstanceUID 算是用於辨識的唯一值

CStoreSCP.OnCStoreRequestCallBack = (request) =>
{
    try
    {
        var studyUid = request.Dataset.Get(DicomTag.StudyInstanceUID);
        path = Path.Combine(path, studyUid);
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        //******************** 儲存檔案 ********************
        path = Path.Combine(path, instUid) + ".dcm";
        request.File.Save(path);
    }
    catch (Exception ex)
    {
        MessageBox.Show("C-MOVE 錯誤:" + ex.Message);
    }
    return new DicomCStoreResponse(request, DicomStatus.Success);
};

var cstoreServer = new DicomServer(CmovePortNum);//新增儲存資料用SERVER服務
var cmoveClient = new DicomClient();//新增發出命令的Client
cmoveClient.NegotiateAsyncOps();//非同步執行
//這邊的 GoMoveDatas 裡面放的是 StudyInstanceUID
foreach (KeyValuePair<string, GoMoves> kvp in GoMoveDatas)
{
    //這邊的 kvp.Key 是 StudyInstanceUID 就是新增要下載的StudyInstanceUID的query
    DicomCMoveRequest CMove = new DicomCMoveRequest(LocalAE, kvp.Key);
    CMove.OnResponseReceived += (Dicom.Network.DicomCMoveRequest rq1, Dicom.Network.DicomCMoveResponse rs) =>
    {
        if (rs.Status == Dicom.Network.DicomStatus.Success)
        {
            return;
        }
        return;
    };
    cmoveClient.AddRequest(CMove);
}
//發送需求
cmoveClient.Send(RemoteIP, RemotePortNum, false, LocalAE, RemoteAE);
//接收完檔案後 停止 儲存資料用SERVER服務
cstoreServer.Stop();
 
個人認為要成功從 C-MOVE 下載影像主要是需要 CStoreSCP 類別 與 cstoreServer 啟用 以及 OnCStoreRequestCallBack 添加
給大家參考囉