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 == "" }