241 lines
7.6 KiB
Go
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 == ""
|
|
}
|