Files
go-dicom-mwl/main.go
2025-03-19 14:43:06 +07:00

241 lines
7.6 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"time"
)
// WorklistItem represents a single item in the worklist
type WorklistItem struct {
PatientName string `json:"PatientName"`
PatientID string `json:"PatientID"`
PatientBirthDate string `json:"PatientBirthDate"`
PatientSex string `json:"PatientSex"`
AccessionNumber string `json:"AccessionNumber"`
StudyInstanceUID string `json:"StudyInstanceUID"`
RequestingPhysician string `json:"RequestingPhysician"`
ScheduledProcedureStepDescription string `json:"ScheduledProcedureStepDescription"`
ScheduledProcedureStepStartDate string `json:"ScheduledProcedureStepStartDate"`
ScheduledProcedureStepStatus string `json:"ScheduledProcedureStepStatus"`
Modality string `json:"Modality"`
}
// WorklistResponse is the structure of the JSON response
type WorklistResponse struct {
Count int `json:"count"`
Records []WorklistItem `json:"records"`
}
func main() {
http.HandleFunc("/worklists", handleWorklists)
port := 5200
fmt.Printf("Server started on port %d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
func handleWorklists(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
date := r.URL.Query().Get("date")
modality := r.URL.Query().Get("modality")
patientName := r.URL.Query().Get("patientName")
patientID := r.URL.Query().Get("patientId")
status := r.URL.Query().Get("status")
accessionNumber := r.URL.Query().Get("accessionNumber")
if date == "" {
date = time.Now().Format("20060102")
}
worklists, err := queryDICOMWorklist(date, modality, patientName, patientID, status, accessionNumber)
if err != nil {
http.Error(w, fmt.Sprintf("Error querying worklist: %v", err), http.StatusInternalServerError)
return
}
response := WorklistResponse{
Records: worklists,
Count: len(worklists),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func queryDICOMWorklist(date, modality, patientName, patientID, status, accessionNumber string) ([]WorklistItem, error) {
tempFile := "temp_dicom_output.txt"
args := []string{
"-v", "-W",
"-aec", "ABPACS",
"-aet", "DICOMWEB_PROXY",
"128.199.154.150", "11112",
"-k", "QueryRetrieveLevel=WORKLIST",
"-k", fmt.Sprintf("ScheduledProcedureStepSequence[0].ScheduledProcedureStepStartDate=%s", date),
"-k", "AccessionNumber",
"-k", "StudyInstanceUID",
"-k", "ScheduledProcedureStepSequence[0].Modality",
"-k", "ScheduledProcedureStepSequence[0].ScheduledProcedureStepDescription",
"-k", "ScheduledProcedureStepSequence[0].ScheduledProcedureStepStatus",
"-k", "PatientID",
"-k", "PatientName",
"-k", "PatientSex",
"-k", "PatientBirthDate",
"-k", "RequestingPhysician",
}
if modality != "" {
args = append(args, "-k", fmt.Sprintf("ScheduledProcedureStepSequence[0].Modality=%s", modality))
}
if patientName != "" {
args = append(args, "-k", fmt.Sprintf("PatientName=%s", patientName))
}
if patientID != "" {
args = append(args, "-k", fmt.Sprintf("PatientID=%s", patientID))
}
if status != "" {
args = append(args, "-k", fmt.Sprintf("ScheduledProcedureStepSequence[0].ScheduledProcedureStepStatus=%s", status))
}
if accessionNumber != "" {
args = append(args, "-k", fmt.Sprintf("AccessionNumber=%s", accessionNumber))
}
cmdString := fmt.Sprintf("findscu %s > %s 2>&1", strings.Join(args, " "), tempFile)
cmd := exec.Command("sh", "-c", cmdString)
err := cmd.Run()
if err != nil {
return nil, fmt.Errorf("error executing findscu: %v", err)
}
output, err := os.ReadFile(tempFile)
if err != nil {
return nil, fmt.Errorf("error reading output file: %v", err)
}
defer os.Remove(tempFile)
return parseDICOMOutput(string(output)), nil
}
func parseDICOMOutput(output string) []WorklistItem {
var worklists []WorklistItem
var currentItem *WorklistItem
lines := strings.Split(output, "\n")
inDataset := false
for i, line := range lines {
trimmedLine := strings.TrimSpace(line)
// Check if we're starting a new dataset response
if strings.Contains(trimmedLine, "Find Response:") && strings.Contains(trimmedLine, "(Pending)") {
if currentItem != nil && !isEmptyWorklistItem(*currentItem) {
worklists = append(worklists, *currentItem)
}
currentItem = &WorklistItem{}
inDataset = true
continue
}
// Skip lines that don't contain tag information
if !inDataset || !strings.Contains(trimmedLine, ") ") {
continue
}
// Extract tag information
extractTagInfo(trimmedLine, currentItem)
// Look ahead for sequence items (especially for Modality which is nested)
if i+1 < len(lines) && strings.Contains(lines[i], "ScheduledProcedureStepSequence") {
// Start looking for nested sequence items
for j := i + 1; j < len(lines) && !strings.Contains(lines[j], "ItemDelimitationItem"); j++ {
nestedLine := strings.TrimSpace(lines[j])
if strings.Contains(nestedLine, "0008,0060") && strings.Contains(nestedLine, "Modality") {
re := regexp.MustCompile(`\[([^\]]*)\]`)
matches := re.FindStringSubmatch(nestedLine)
if len(matches) >= 2 {
currentItem.Modality = strings.TrimSpace(matches[1])
}
} else if strings.Contains(nestedLine, "0040,0002") && strings.Contains(nestedLine, "ScheduledProcedureStepStartDate") {
re := regexp.MustCompile(`\[([^\]]*)\]`)
matches := re.FindStringSubmatch(nestedLine)
if len(matches) >= 2 {
currentItem.ScheduledProcedureStepStartDate = strings.TrimSpace(matches[1])
}
} else if strings.Contains(nestedLine, "0040,0007") && strings.Contains(nestedLine, "ScheduledProcedureStepDescription") {
re := regexp.MustCompile(`\[([^\]]*)\]`)
matches := re.FindStringSubmatch(nestedLine)
if len(matches) >= 2 {
currentItem.ScheduledProcedureStepDescription = strings.TrimSpace(matches[1])
}
} else if strings.Contains(nestedLine, "0040,0020") && strings.Contains(nestedLine, "ScheduledProcedureStepStatus") {
re := regexp.MustCompile(`\[([^\]]*)\]`)
matches := re.FindStringSubmatch(nestedLine)
if len(matches) >= 2 {
currentItem.ScheduledProcedureStepStatus = strings.TrimSpace(matches[1])
}
}
}
}
}
// Add the last item if it exists
if currentItem != nil && !isEmptyWorklistItem(*currentItem) {
worklists = append(worklists, *currentItem)
}
return worklists
}
func extractTagInfo(line string, item *WorklistItem) {
// Pattern for parsing DICOM tags from the format: (0008,0050) SH [CR.250319.4554.01]
re := regexp.MustCompile(`\(([0-9a-fA-F]{4}),([0-9a-fA-F]{4})\) \w+ \[([^\]]*)\]`)
matches := re.FindStringSubmatch(line)
if len(matches) < 4 {
return
}
group := matches[1]
element := matches[2]
value := strings.TrimSpace(matches[3])
// Map DICOM tags to struct fields
switch group + "," + element {
case "0010,0010": // PatientName
item.PatientName = value
case "0010,0020": // PatientID
item.PatientID = value
case "0010,0030": // PatientBirthDate
item.PatientBirthDate = value
case "0010,0040": // PatientSex
item.PatientSex = value
case "0008,0050": // AccessionNumber
item.AccessionNumber = value
case "0020,000d": // StudyInstanceUID
item.StudyInstanceUID = value
case "0032,1032": // RequestingPhysician
item.RequestingPhysician = value
}
}
func isEmptyWorklistItem(item WorklistItem) bool {
return item.PatientName == "" &&
item.PatientID == "" &&
item.AccessionNumber == "" &&
item.StudyInstanceUID == "" &&
item.Modality == ""
}