fix: findscu go mkiso
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -78,7 +79,17 @@ func (s *DicomService) FetchDICOMMultiple(ctx context.Context, accessionNumbers
|
||||
default:
|
||||
}
|
||||
|
||||
exitCode, _, stderr, err := dicom.RunMoveSCU(
|
||||
studyUIDs, err := s.findStudyUIDs(ctx, acc)
|
||||
if err != nil {
|
||||
slog.Warn("findscu failed for accession",
|
||||
"accession", acc,
|
||||
"error", err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, studyUID := range studyUIDs {
|
||||
exitCode, _, stderr, err := dicom.RunMoveSCUByStudyUID(
|
||||
ctx,
|
||||
s.cfg.DCMTK.Movescu,
|
||||
s.cfg.OurAE.AETitle,
|
||||
@@ -86,24 +97,26 @@ func (s *DicomService) FetchDICOMMultiple(ctx context.Context, accessionNumbers
|
||||
s.cfg.PACS.Host,
|
||||
s.cfg.PACS.Port,
|
||||
port,
|
||||
acc,
|
||||
studyUID,
|
||||
300, // 5 min timeout
|
||||
)
|
||||
if err != nil {
|
||||
slog.Warn("movescu failed for accession",
|
||||
slog.Warn("movescu failed for study uid",
|
||||
"accession", acc,
|
||||
"study_uid", studyUID,
|
||||
"exit_code", exitCode,
|
||||
"stderr", stderr,
|
||||
"error", err,
|
||||
)
|
||||
// Continue with next accession — partial success is acceptable
|
||||
continue
|
||||
}
|
||||
slog.Info("movescu completed",
|
||||
"accession", acc,
|
||||
"study_uid", studyUID,
|
||||
"exit_code", exitCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Count files retrieved
|
||||
filesCount, err := countFiles(destDir)
|
||||
@@ -140,8 +153,13 @@ func (s *DicomService) fetchDICOMWithPort(ctx context.Context, accessionNumber,
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Run movescu
|
||||
exitCode, _, stderr, err := dicom.RunMoveSCU(
|
||||
studyUIDs, err := s.findStudyUIDs(ctx, accessionNumber)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, studyUID := range studyUIDs {
|
||||
exitCode, _, stderr, err := dicom.RunMoveSCUByStudyUID(
|
||||
ctx,
|
||||
s.cfg.DCMTK.Movescu,
|
||||
s.cfg.OurAE.AETitle,
|
||||
@@ -149,17 +167,19 @@ func (s *DicomService) fetchDICOMWithPort(ctx context.Context, accessionNumber,
|
||||
s.cfg.PACS.Host,
|
||||
s.cfg.PACS.Port,
|
||||
port,
|
||||
accessionNumber,
|
||||
studyUID,
|
||||
300, // 5 min timeout
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("movescu failed (exit %d): %s (stderr: %s)", exitCode, err, stderr)
|
||||
return 0, fmt.Errorf("movescu failed for study uid %q (exit %d): %s (stderr: %s)", studyUID, exitCode, err, stderr)
|
||||
}
|
||||
|
||||
slog.Info("movescu completed",
|
||||
"accession", accessionNumber,
|
||||
"study_uid", studyUID,
|
||||
"exit_code", exitCode,
|
||||
)
|
||||
}
|
||||
|
||||
// Count files
|
||||
filesCount, err = countFiles(destDir)
|
||||
@@ -174,6 +194,24 @@ func (s *DicomService) fetchDICOMWithPort(ctx context.Context, accessionNumber,
|
||||
return filesCount, nil
|
||||
}
|
||||
|
||||
func (s *DicomService) findStudyUIDs(ctx context.Context, accessionNumber string) ([]string, error) {
|
||||
findscuBin := filepath.Join(filepath.Dir(s.cfg.DCMTK.Movescu), "findscu")
|
||||
studyUIDs, _, stderr, err := dicom.RunFindSCUStudyUIDs(
|
||||
ctx,
|
||||
findscuBin,
|
||||
s.cfg.OurAE.AETitle,
|
||||
s.cfg.PACS.AETitle,
|
||||
s.cfg.PACS.Host,
|
||||
s.cfg.PACS.Port,
|
||||
accessionNumber,
|
||||
60,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("findscu failed for accession %q: %w (stderr: %s)", accessionNumber, err, stderr)
|
||||
}
|
||||
return studyUIDs, nil
|
||||
}
|
||||
|
||||
// waitForStorescpReady waits for storescp to start or detects early failure.
|
||||
func waitForStorescpReady(resultCh <-chan dicom.StorescpResult) error {
|
||||
select {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -101,17 +102,72 @@ func StartStoresCP(ctx context.Context, bin, aeTitle string, port int, outputDir
|
||||
return ch, stop, nil
|
||||
}
|
||||
|
||||
// RunMoveSCU executes movescu for the given accession number.
|
||||
// RunFindSCUStudyUIDs queries PACS by accession number and returns matching StudyInstanceUID values.
|
||||
func RunFindSCUStudyUIDs(ctx context.Context, bin, ourAE, pacsAE, pacsHost string, pacsPort int, accessionNumber string, timeoutSec int) (studyUIDs []string, stdout, stderr string, err error) {
|
||||
args := []string{
|
||||
"-v",
|
||||
"-aet", ourAE,
|
||||
"-aec", pacsAE,
|
||||
pacsHost, strconv.Itoa(pacsPort),
|
||||
"-S",
|
||||
"-k", "0008,0052=STUDY",
|
||||
"-k", fmt.Sprintf("0008,0050=%s", accessionNumber),
|
||||
"-k", "0020,000D",
|
||||
}
|
||||
|
||||
if timeoutSec > 0 {
|
||||
args = append(args, "-to", strconv.Itoa(timeoutSec))
|
||||
}
|
||||
|
||||
slog.Info("running findscu",
|
||||
"bin", bin,
|
||||
"accession", accessionNumber,
|
||||
"pacs", fmt.Sprintf("%s@%s:%d", pacsAE, pacsHost, pacsPort),
|
||||
)
|
||||
|
||||
cmd := exec.CommandContext(ctx, bin, args...)
|
||||
var outBuf, errBuf bytes.Buffer
|
||||
cmd.Stdout = &outBuf
|
||||
cmd.Stderr = &errBuf
|
||||
|
||||
err = cmd.Run()
|
||||
stdout = outBuf.String()
|
||||
stderr = errBuf.String()
|
||||
combined := stdout + "\n" + stderr
|
||||
|
||||
if err != nil {
|
||||
return nil, stdout, stderr, fmt.Errorf("findscu failed: %w", err)
|
||||
}
|
||||
|
||||
uidRe := regexp.MustCompile(`\(0020,000d\) UI \[([^\]]+)\]`)
|
||||
seen := make(map[string]bool)
|
||||
for _, match := range uidRe.FindAllStringSubmatch(combined, -1) {
|
||||
uid := strings.TrimSpace(match[1])
|
||||
if uid == "" || seen[uid] {
|
||||
continue
|
||||
}
|
||||
seen[uid] = true
|
||||
studyUIDs = append(studyUIDs, uid)
|
||||
}
|
||||
|
||||
if len(studyUIDs) == 0 {
|
||||
return nil, stdout, stderr, fmt.Errorf("no StudyInstanceUID found for accession %q", accessionNumber)
|
||||
}
|
||||
|
||||
return studyUIDs, stdout, stderr, nil
|
||||
}
|
||||
|
||||
// RunMoveSCUByStudyUID executes movescu for the given StudyInstanceUID.
|
||||
// It uses Study Root query/retrieve model (via -S flag).
|
||||
func RunMoveSCU(ctx context.Context, bin, ourAE, pacsAE, pacsHost string, pacsPort, moveDestPort int, accessionNumber string, timeoutSec int) (exitCode int, stdout, stderr string, err error) {
|
||||
func RunMoveSCUByStudyUID(ctx context.Context, bin, ourAE, pacsAE, pacsHost string, pacsPort, moveDestPort int, studyUID string, timeoutSec int) (exitCode int, stdout, stderr string, err error) {
|
||||
args := []string{
|
||||
"-aet", ourAE,
|
||||
"-aec", pacsAE,
|
||||
"-aem", ourAE,
|
||||
pacsHost, strconv.Itoa(pacsPort),
|
||||
"-S", // Study Root query/retrieve
|
||||
"-k", fmt.Sprintf("0008,0050=%s", accessionNumber),
|
||||
"-k", "0010,0020=", // Patient ID wildcard (required for Study Root)
|
||||
"-k", "0008,0052=STUDY",
|
||||
"-k", fmt.Sprintf("0020,000D=%s", studyUID),
|
||||
"--no-port",
|
||||
}
|
||||
|
||||
@@ -121,7 +177,7 @@ func RunMoveSCU(ctx context.Context, bin, ourAE, pacsAE, pacsHost string, pacsPo
|
||||
|
||||
slog.Info("running movescu",
|
||||
"bin", bin,
|
||||
"accession", accessionNumber,
|
||||
"study_uid", studyUID,
|
||||
"pacs", fmt.Sprintf("%s@%s:%d", pacsAE, pacsHost, pacsPort),
|
||||
"dest", fmt.Sprintf("%s:%d", ourAE, moveDestPort),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user