DICOMweb ํ‘œ์ค€ ์‚ฌ์šฉ

์ด ํŽ˜์ด์ง€์—์„œ๋Š” Cloud Healthcare API์˜ DICOMweb ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ ๋ฐ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Cloud Healthcare API๊ฐ€ ์—ฌ๋Ÿฌ DICOMweb REST ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Cloud Healthcare API์˜ DICOM ์›น ๊ตฌํ˜„์€ RPC๊ฐ€ ์•„๋‹Œ REST๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Cloud Healthcare API DICOMweb CLI ์„ค์น˜

์ด ํŽ˜์ด์ง€์˜ ์ผ๋ถ€ ์ƒ˜ํ”Œ์—๋Š” DICOMweb ์„œ๋ฒ„์™€์˜ ์ƒํ˜ธ์ž‘์šฉ ๋ฐฉ๋ฒ•์„ ๋‹จ์ˆœํ™”ํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ๋„๊ตฌ์ธ Cloud Healthcare API DICOMweb CLI๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋Š” DICOM ํŒŒ์ผ ์ €์žฅ, ๊ฒ€์ƒ‰, ์‚ญ์ œ, ๊ฒ€์ƒ‰์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ์˜ GitHub ํŽ˜์ด์ง€์—๋Š” ์„ธ๋ถ€ ์„ค์น˜ ์š”๊ตฌ์‚ฌํ•ญ ๋ฐ ๋„๊ตฌ ๋งž์ถค์„ค์ • ๋ฐฉ๋ฒ•๊ณผ ๊ฐ™์€ ์ถ”๊ฐ€ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋„๊ตฌ๋Š” Python์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. Google Cloud์—์„œ Python์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Python ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

Python์„ ์„ค์ •ํ•œ ํ›„์—๋Š” PIP๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋„๊ตฌ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

pip install https://github.com/GoogleCloudPlatform/healthcare-api-dicomweb-cli/archive/v1.0.zip

์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Google Cloud ์„œ๋ฒ„๋กœ ์ธ์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์˜ต์…˜์„ ๊ตฌ์„ฑํ•˜๋ฉด ๋„๊ตฌ๊ฐ€ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.

DICOM ๋ฐ์ดํ„ฐ ์ €์žฅ

DICOM ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด ๋จผ์ € DICOM ์ €์žฅ์†Œ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Cloud Healthcare API๋Š” DICOM ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋•Œ ์ €์žฅ์†Œ ํŠธ๋žœ์žญ์…˜ RESTful ์›น ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ ์ €์žฅ์†Œ ํŠธ๋žœ์žญ์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๋‹ค์Œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DICOM ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์— application/dicom Accept ํ—ค๋”๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

  • DICOM ์ธ์Šคํ„ด์Šค ์ €์žฅ: ์ผ๋ฐ˜์ ์œผ๋กœ .dcm ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.
  • ์—ฌ๋Ÿฌ ๋ถ€๋ถ„ (multipart) ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ํ•ญ๋ชฉ ์ €์žฅ: ๊ฒฝ๊ณ„๋กœ ๊ตฌ๋ถ„๋œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ๋ถ€๋ถ„์œผ๋กœ ๋ฉ€ํ‹ฐํŒŒํŠธ ํŒŒ์ผ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์—์„œ Content-Type์„ multipart/related; type=application/dicom; boundary=BOUNDARY๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ BOUNDARY๋Š” ๋ฉ”์‹œ์ง€์˜ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์„ ๋‚˜ํƒ€๋‚ด๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๊ฐ’์ž…๋‹ˆ๋‹ค. ๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฉ”์‹œ์ง€ ๋งŒ๋“ค๊ธฐ์— ๊ด€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ RFC 1341์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

    ๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ DICOM ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

SOP_CLASS_UID, SOP_INSTANCE_UID, STUDY_INSTANCE_UID, SERIES_INSTANCE_UID ๊ฐ’์€ ์ œ๊ณต๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. UID๋Š” ๋‹ค์Œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋งˆ์นจํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ˆซ์ž ๊ฐ’๋งŒ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณดํ˜ธ ๊ฑด๊ฐ• ์ •๋ณด(PHI)๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์ €์žฅ์†Œ์— ์ธ์Šคํ„ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.storeInstances๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

DICOM ์ธ์Šคํ„ด์Šค ์ €์žฅ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.storeInstances๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • DICOM_INSTANCE_FILE: ๋กœ์ปฌ ๋จธ์‹ ์— ์žˆ๋Š” DICOM ์ธ์Šคํ„ด์Šค ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋กœ, .dcm ์ ‘๋ฏธ์‚ฌ๋กœ ๋๋‚ฉ๋‹ˆ๋‹ค.

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/dicom" \
--data-binary @DICOM_INSTANCE_FILE \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method POST `
-Headers $headers `
-InFile DICOM_INSTANCE_FILE `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies" | Select-Object -Expand Content
์ถœ๋ ฅ์€ ๋‹ค์Œ XML ์‘๋‹ต์ž…๋‹ˆ๋‹ค.

Go

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebStoreInstance stores the given dicomFile with the dicomWebPath.
func dicomWebStoreInstance(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath, dicomFile string) error {
	ctx := context.Background()

	dicomData, err := os.ReadFile(dicomFile)
	if err != nil {
		return fmt.Errorf("os.ReadFile: %w", err)
	}

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	call := storesService.StoreInstances(parent, dicomWebPath, bytes.NewReader(dicomData))
	call.Header().Set("Content-Type", "application/dicom")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("StoreInstances: %w", err)
	}
	defer resp.Body.Close()

	respBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %w", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("StoreInstances: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	fmt.Fprintf(w, "%s", respBytes)
	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.HttpClients;

public class DicomWebStoreInstance {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebStoreInstance(String dicomStoreName, String filePath)
      throws IOException, URISyntaxException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");
    // String filePath = "path/to/file.dcm";

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    HttpClient httpClient = HttpClients.createDefault();
    String uri = String.format("%sv1/%s/dicomWeb/studies", client.getRootUrl(), dicomStoreName);
    URIBuilder uriBuilder = new URIBuilder(uri).setParameter("access_token", getAccessToken());
    // Load the data from file representing the study.
    File f = new File(filePath);
    byte[] dicomBytes = Files.readAllBytes(Paths.get(filePath));
    ByteArrayEntity requestEntity = new ByteArrayEntity(dicomBytes);

    HttpUriRequest request =
        RequestBuilder.post(uriBuilder.build())
            .setEntity(requestEntity)
            .addHeader("Content-Type", "application/dicom")
            .build();

    // Execute the request and process the results.
    HttpResponse response = httpClient.execute(request);
    HttpEntity responseEntity = response.getEntity();
    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
      System.err.print(
          String.format(
              "Exception storing DICOM instance: %s\n", response.getStatusLine().toString()));
      responseEntity.writeTo(System.err);
      throw new RuntimeException();
    }
    System.out.println("DICOM instance stored: ");
    responseEntity.writeTo(System.out);
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }

  private static String getAccessToken() throws IOException {
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    return credential.refreshAccessToken().getTokenValue();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});
const fs = require('fs');

const dicomWebStoreInstance = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  // const dcmFile = 'file.dcm';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = 'studies';
  // Use a stream because other types of reads overwrite the client's HTTP
  // headers and cause storeInstances to fail.
  const binaryData = fs.createReadStream(dcmFile);
  const request = {
    parent,
    dicomWebPath,
    requestBody: binaryData,
  };

  const instance =
    await healthcare.projects.locations.datasets.dicomStores.storeInstances(
      request,
      {
        headers: {
          'Content-Type': 'application/dicom',
          Accept: 'application/dicom+json',
        },
      }
    );
  console.log('Stored DICOM instance:\n', JSON.stringify(instance.data));
};

dicomWebStoreInstance();

Python

def dicomweb_store_instance(project_id, location, dataset_id, dicom_store_id, dcm_file):
    """Handles the POST requests specified in the DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    # dcm_file = 'dicom000_0001.dcm'  # replace with a DICOM file
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicomweb_path = "{}/datasets/{}/dicomStores/{}/dicomWeb/studies".format(
        url, dataset_id, dicom_store_id
    )

    with open(dcm_file, "rb") as dcm:
        dcm_content = dcm.read()

    # Sets required "application/dicom" header on the request
    headers = {"Content-Type": "application/dicom"}

    response = session.post(dicomweb_path, data=dcm_content, headers=headers)
    response.raise_for_status()
    print("Stored DICOM instance:")
    print(response.text)
    return response

๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์—ฐ๊ตฌ ๋˜๋Š” ์‹œ๋ฆฌ์ฆˆ ์ €์žฅ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ๋ฉ€ํ‹ฐํŒŒํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋กœ ๊ตฌ์„ฑ๋œ DICOM ์—ฐ๊ตฌ ๋˜๋Š” ์‹œ๋ฆฌ์ฆˆ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • MULTIPART_FILE: ๋กœ์ปฌ ๋จธ์‹ ์— ์žˆ๋Š” ๋ฉ€ํ‹ฐํŒŒํŠธ ํŒŒ์ผ์˜ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ์— ์—ฌ๋Ÿฌ DICOM ์ธ์Šคํ„ด์Šค๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ ๊ฐ ์ธ์Šคํ„ด์Šค๋Š” ๊ฒฝ๊ณ„๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค.
  • BOUNDARY: ๋ฉ€ํ‹ฐํŒŒํŠธ ํŒŒ์ผ์—์„œ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ๊ณ„

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: multipart/related; type=application/dicom; boundary=BOUNDARY" \
--data-binary @MULTIPART_FILE \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method POST `
-Headers $headers `
-InFile MULTIPART_FILE `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies" | Select-Object -Expand Content
์ถœ๋ ฅ์€ ๋‹ค์Œ๊ณผ ๋น„์Šทํ•˜๋ฉฐ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

DICOM ์ธ์Šคํ„ด์Šค์˜ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค ์ง€์ •

๊ธฐ๋ณธ์ ์œผ๋กœ projects.locations.datasets.dicomStores.storeInstances ๋ฉ”์„œ๋“œ๋Š” Standard Storage ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ DICOM ์ €์žฅ์†Œ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ ๋จธ์‹ ์—์„œ DICOM ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ๋•Œ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ DICOM ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค ๋ณ€๊ฒฝ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ๋กœ์ปฌ ๋จธ์‹ ์—์„œ DICOM ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ๋•Œ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl

projects.locations.datasets.dicomStores.storeInstances ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • DICOM_INSTANCE_FILE: ๋กœ์ปฌ ๋จธ์‹ ์— ์žˆ๋Š” DICOM ์ธ์Šคํ„ด์Šค ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋กœ, .dcm ์ ‘๋ฏธ์‚ฌ๋กœ ๋๋‚ฉ๋‹ˆ๋‹ค.
  • STORAGE_CLASS: STANDARD, NEARLINE, COLDLINE, ARCHIVE์˜ DICOM ์ €์žฅ์†Œ์— ์žˆ๋Š” DICOM ์ธ์Šคํ„ด์Šค์˜ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

curl -X POST \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "Content-Type: application/dicom" \
    -H "Storage-Class: STORAGE_CLASS" \
    --data-binary @DICOM_INSTANCE_FILE \
    "https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies"

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๋Š” ๋‹ค์Œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

<NativeDicomModel>
  <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
    <Value number="1">https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID</Value>
  </DicomAttribute>
  <DicomAttribute tag="00081199" vr="SQ" keyword="ReferencedSOPSequence">
    <Item number="1">
      <DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
        <Value number="1">SOP_CLASS_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081155" vr="UI" keyword="ReferencedSOPInstanceUID">
        <Value number="1">SOP_INSTANCE_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
        <Value number="1">https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID</Value>
      </DicomAttribute>
    </Item>
  </DicomAttribute>
</NativeDicomModel>

PowerShell

projects.locations.datasets.dicomStores.storeInstances ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • DICOM_INSTANCE_FILE: ๋กœ์ปฌ ๋จธ์‹ ์— ์žˆ๋Š” DICOM ์ธ์Šคํ„ด์Šค ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋กœ, .dcm ์ ‘๋ฏธ์‚ฌ๋กœ ๋๋‚ฉ๋‹ˆ๋‹ค.
  • STORAGE_CLASS: STANDARD, NEARLINE, COLDLINE, ARCHIVE์˜ DICOM ์ €์žฅ์†Œ์— ์žˆ๋Š” DICOM ์ธ์Šคํ„ด์Šค์˜ ์Šคํ† ๋ฆฌ์ง€ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

$cred = gcloud auth application-default print-access-token
$headers = @{ "Authorization" = "Bearer $cred"; "Storage-Class" = "STORAGE_CLASS" }

Invoke-WebRequest `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/dicom" `
  -InFile DCM_FILE.dcm `
  -Uri "https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies" | Select-Object -Expand Content

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

<NativeDicomModel>
  <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
    <Value number="1">https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID</Value>
  </DicomAttribute>
  <DicomAttribute tag="00081199" vr="SQ" keyword="ReferencedSOPSequence">
    <Item number="1">
      <DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
        <Value number="1">SOP_CLASS_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081155" vr="UI" keyword="ReferencedSOPInstanceUID">
        <Value number="1">SOP_INSTANCE_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
        <Value number="1">https://healthcare.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID</Value>
      </DicomAttribute>
    </Item>
  </DicomAttribute>
</NativeDicomModel>

JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ JPEG ์ด๋ฏธ์ง€์—์„œ DICOM ์ธ์Šคํ„ด์Šค ๋งŒ๋“ค๊ธฐ

Cloud Healthcare API๋Š” JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ ๋ฐ JPEG ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Cloud Healthcare API๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, DICOM ํŒŒ์‹ฑ ๋ฐ ์ง๋ ฌํ™”๋ฅผ ์ง์ ‘ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฐ JPEG ํŒŒ์ผ์—์„œ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” HTTP ์š”์ฒญ์—๋Š” ์š”์ฒญ์˜ Content-Type์— ๋‹ค์Œ์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • multipart/related ๋ฏธ๋””์–ด ์œ ํ˜•
  • MIME ์œ ํ˜• application/dicom+json
  • boundary ๊ตฌ๋ถ„ ๊ธฐํ˜ธ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ JPEG ํŒŒ์ผ๋กœ JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ๊ธฐ์กด JPEG ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

JPEG ์ด๋ฏธ์ง€๋กœ JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ์ž‘์—…์€ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ๋‹จ๊ณ„๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

  1. JPEG ์ด๋ฏธ์ง€๊ฐ€ ํฌํ•จ๋œ DICOM ์ธ์Šคํ„ด์Šค์˜ JSON ํ‘œํ˜„์ด ํฌํ•จ๋œ ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์€ ์•„๋ž˜์— ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  2. 3๊ฐœ์˜ ๊ฒฝ๊ณ„ ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    • opening.file: JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์˜ ์‹œ์ž‘ ๊ฒฝ๊ณ„๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
    • middle.file: JPEG ์ด๋ฏธ์ง€์˜ ์ค‘๊ฐ„ ๊ฒฝ๊ณ„๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
    • closing.file: ๋ฉ”์‹œ์ง€์˜ ๋ชจ๋“  ๋ถ€๋ถ„์— ๋Œ€ํ•œ ์ข…๋ฃŒ ๊ฒฝ๊ณ„๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ฒฝ๊ณ„ ํŒŒ์ผ ๋‚ด์—์„œ JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ ๋ฐ JPEG ์ด๋ฏธ์ง€๋ฅผ ๋ฌถ์–ด์„œ multipart-request.file์ด๋ผ๋Š” ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณต๋˜๋Š” ๋‹ค์Œ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

  • ์ „์†ก ๊ตฌ๋ฌธ UID(1.2.840.10008.1.2.4.50)๋Š” ์ „์†ก ๊ตฌ๋ฌธ์„ JPEG ๊ธฐ์ค€์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ JPEG ์ด๋ฏธ์ง€๋Š” JPEG ๊ธฐ์ค€ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์‚ฌ์ง„ ํ•ด์„ ๊ฐ’(YBR_FULL_422)์€ ์ด๋ฏธ์ง€๊ฐ€ ๊ทธ๋ ˆ์ด ์Šค์ผ€์ผ์ด ์•„๋‹Œ ์ปฌ๋Ÿฌ๋ผ๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • BulkDataUri๋Š” ์ด๋ฏธ์ง€์˜ ์ž„์˜ ์„ค๋ช…์ž์ด๊ณ , ํ…œํ”Œ๋ฆฟ์—์„œ jpeg-image๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ ์ด๋ฏธ์ง€ ๊ฒฝ๊ณ„๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

SOP_CLASS_UID, SOP_INSTANCE_UID, STUDY_INSTANCE_UID, SERIES_INSTANCE_UID ๊ฐ’์€ ๋งˆ์นจํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ˆซ์ž ๊ฐ’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DICOM์€ ์ธ์Šคํ„ด์Šค, ํ™˜์ž, ์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ์— ๋Œ€ํ•ด ์‹๋ณ„์ž ๊ณ„์ธต์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ์ด๋Ÿฌํ•œ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด ๋…ผ๋ฆฌ์ ์ธ ์‹๋ณ„์ž ์„ธํŠธ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

SOP Class UID๋ฅผ ์ €์žฅ๋˜๋Š” ์ด๋ฏธ์ง€ ์œ ํ˜•์„ ์ง€์ •ํ•˜๋Š” ํ‘œ์ค€ SOP ํด๋ž˜์Šค ํ…Œ์ด๋ธ”์˜ ๊ฐ’์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

Rows๋ฅผ JPEG ์ด๋ฏธ์ง€์˜ ์„ธ๋กœ ๋†’์ด(ํ”ฝ์…€)๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. Columns๋ฅผ JPEG ์ด๋ฏธ์ง€์˜ ๊ฐ€๋กœ ๋„ˆ๋น„(ํ”ฝ์…€)๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

  1. ๋‹ค์Œ ํ…์ŠคํŠธ๋ฅผ instance.json์ด๋ผ๋Š” ํŒŒ์ผ์— ์ €์žฅํ•˜๊ณ , ์ง€์ •๋œ ๊ฒฝ์šฐ ๋ณ€์ˆ˜๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

    [{
     "00020010":{"vr":"UI","Value":["1.2.840.10008.1.2.4.50"]},
     "00080005":{"vr":"CS","Value":["ISO_IR 192"]},
     "00080016":{"vr":"UI","Value":["SOP_CLASS_UID"]},
     "00080018":{"vr":"UI","Value":["SOP_INSTANCE_UID"]},
     "0020000D":{"vr":"UI","Value":["STUDY_INSTANCE_UID"]},
     "0020000E":{"vr":"UI","Value":["SERIES_INSTANCE_UID"]},
     "00280002":{"vr":"US","Value":[3]},
     "00280004":{"vr":"CS","Value":["YBR_FULL_422"]},
     "00280006":{"vr":"US","Value":[0]},
     "00280008":{"vr":"IS","Value":[1]},
     "00280010":{"vr":"US","Value":[Rows]},
     "00280011":{"vr":"US","Value":[Columns]},
     "00280100":{"vr":"US","Value":[8]},
     "00280101":{"vr":"US","Value":[8]},
     "00280102":{"vr":"US","Value":[7]},
     "00280103":{"vr":"US","Value":[0]},
     "7FE00010":{"vr":"OB","BulkDataURI":"jpeg-image"}
    }]
  2. ์‹œ์ž‘(JSON ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ), ์ค‘๊ฐ„(JPEG ์ด๋ฏธ์ง€), ์ข…๋ฃŒ ๊ฒฝ๊ณ„๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    echo -ne "--DICOMwebBoundary\r\nContent-Type: application/dicom+json\r\n\r\n" > opening.file
    echo -ne "\r\n--DICOMwebBoundary\r\nContent-Location: jpeg-image\r\nContent-Type: image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.50\r\n\r\n" > middle.file
    echo -ne "\r\n--DICOMwebBoundary--" > closing.file
  3. ์ค‘๊ฐ„ ๋ฐ ์ข…๋ฃŒ ๊ฒฝ๊ณ„ ๋‚ด์—์„œ JPEG ์ด๋ฏธ์ง€๋ฅผ ๋ž˜ํ•‘ํ•ฉ๋‹ˆ๋‹ค. Cloud Healthcare API๋กœ ์ „์†กํ•˜๋Š” ์ถœ๋ ฅ ํŒŒ์ผ์€ multipart-request.file์ด๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

    cat opening.file instance.json middle.file image.jpg closing.file > multipart-request.file
  4. POST ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

    • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
    • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
    • multipart-request.file ํŒŒ์ผ
    • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ curl์„ ์‚ฌ์šฉํ•˜๋Š” POST ์š”์ฒญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl -X POST \
    -H "Content-Type: multipart/related; type=\"application/dicom+json\"; boundary=DICOMwebBoundary" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies \
    --data-binary @multipart-request.file

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ XML ํ˜•์‹์œผ๋กœ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

<NativeDicomModel>
  <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
    <Value number="1">https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID</Value>
  </DicomAttribute>
  <DicomAttribute tag="00081199" vr="SQ" keyword="ReferencedSOPSequence">
    <Item number="1">
      <DicomAttribute tag="00081150" vr="UI" keyword="ReferencedSOPClassUID">
        <Value number="1">SOP_CLASS_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081155" vr="UI" keyword="ReferencedSOPInstanceUID">
        <Value number="1">SOP_INSTANCE_UID</Value>
      </DicomAttribute>
      <DicomAttribute tag="00081190" vr="UR" keyword="RetrieveURL">
        <Value number="1">https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID</Value>
      </DicomAttribute>
    </Item>
  </DicomAttribute>
</NativeDicomModel>

DICOMweb CLI ์‚ฌ์šฉ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Cloud Healthcare API DICOMweb CLI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋‚˜ ์ด์ƒ์˜ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. DICOMweb CLI GitHub ์ €์žฅ์†Œ์—๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒ˜ํ”Œ์ด ๋” ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹จ์ผ DICOM ์ธ์Šคํ„ด์Šค ์ €์žฅ:

dcmweb \
  https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb \
  store DCM_FILE

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ๋‹ค์Œ ์ƒ˜ํ”Œ๊ณผ ๋น„์Šทํ•œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

TIMESTAMP -- DCM_FILE.dcm uploaded as https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID TIMESTAMP -- INSTANCE_UID
TIMESTAMP -- Transferred SIZE in COUNT files

์™€์ผ๋“œ ์นด๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ํŒŒ์ผ์„ ๋ณ‘๋ ฌ๋กœ ์ €์žฅ:

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ํ˜„์žฌ ์ž‘๋™ ์ค‘์ธ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์—ฌ๋Ÿฌ DICOM ํŒŒ์ผ์„ ๋ณ‘๋ ฌ๋กœ ๋ฐ˜๋ณตํ•ด์„œ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ํŒŒ์ผ์„ ๋ณ‘๋ ฌ๋กœ ์ €์žฅํ•˜๋ ค๋ฉด -m ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

dcmweb -m \
  https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb \
  store "./**.dcm"

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ๋‹ค์Œ ์ƒ˜ํ”Œ๊ณผ ๋น„์Šทํ•œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

TIMESTAMP -- DCM_FILE_1.dcm uploaded as https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID TIMESTAMP -- INSTANCE_UID
TIMESTAMP -- DCM_FILE_2.dcm uploaded as https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID TIMESTAMP -- INSTANCE_UID
TIMESTAMP -- DCM_FILE_3.dcm uploaded as https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID TIMESTAMP -- INSTANCE_UID
...
TIMESTAMP -- Transferred SIZE in COUNT files

DICOM ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰

์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ, ์ธ์Šคํ„ด์Šค, ํ”„๋ ˆ์ž„์„ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์ €์žฅ์†Œ์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜์˜ ๊ตฌํ˜„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์ €์žฅ์†Œ์—์„œ ์ธ์Šคํ„ด์Šค ๊ฒ€์ƒ‰ ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.searchForInstances๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/instances"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method GET `
-Headers $headers `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/instances" | Select-Object -Expand Content

API ํƒ์ƒ‰๊ธฐ

๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํŽ˜์ด์ง€๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ์— API ํƒ์ƒ‰๊ธฐ ํŒจ๋„์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ JSON ์‘๋‹ต์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebSearchInstances searches instances.
func dicomWebSearchInstances(w io.Writer, projectID, location, datasetID, dicomStoreID string) error {
	// projectID := "my-project"
	// location := "us-central1"
	// datasetID := "my-dataset"
	// dicomStoreID := "my-dicom-store"
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	resp, err := storesService.SearchForInstances(parent, "instances").Do()
	if err != nil {
		return fmt.Errorf("SearchForInstances: %w", err)
	}

	defer resp.Body.Close()

	respBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ioutil.ReadAll: %w", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("SearchForInstances: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}

	respString := string(respBytes)
	fmt.Fprintf(w, "Found instances: %s\n", respString)
	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.util.Collections;

public class DicomWebSearchForInstances {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebSearchForInstances(String dicomStoreName) throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    // Create request and configure any parameters.
    DicomStores.SearchForInstances request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .searchForInstances(dicomStoreName, "instances");

    // Execute the request and process the results.
    HttpResponse response = request.executeUnparsed();
    System.out.println("Dicom store instances found: \n" + response.toString());
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});

const dicomWebSearchForInstances = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = 'instances';
  const request = {parent, dicomWebPath};

  const instances =
    await healthcare.projects.locations.datasets.dicomStores.searchForInstances(
      request,
      {
        headers: {Accept: 'application/dicom+json,multipart/related'},
      }
    );
  console.log(`Found ${instances.data.length} instances:`);
  console.log(JSON.stringify(instances.data));
};

dicomWebSearchForInstances();

Python

def dicomweb_search_instance(project_id, location, dataset_id, dicom_store_id):
    """Handles the GET requests specified in DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicomweb_path = "{}/datasets/{}/dicomStores/{}/dicomWeb/instances".format(
        url, dataset_id, dicom_store_id
    )

    # Sets required application/dicom+json; charset=utf-8 header on the request
    headers = {"Content-Type": "application/dicom+json; charset=utf-8"}

    response = session.get(dicomweb_path, headers=headers)
    response.raise_for_status()

    instances = response.json()

    print("Instances:")
    print(json.dumps(instances, indent=2))

    return instances

DICOM ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰

์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜•์‹์œผ๋กœ ์š”์ฒญ์— DICOM ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฒ€์ƒ‰์„ ๋ฏธ์„ธ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํ™˜์ž ์ด๋ฆ„์ด ํฌํ•จ๋œ ์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ž์˜ ์ƒ˜ํ”Œ์—์„œ์™€ ๊ฐ™์ด ๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์ €์žฅ์†Œ์—์„œ ์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜ ๊ตฌํ˜„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ์ƒ˜ํ”Œ์€ ํ™˜์ž ์ด๋ฆ„์ด 'Sally Zhang'์ธ ์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ํ™˜์ž ์ด๋ฆ„์ด ๋‚˜์—ด๋œ DICOM ์ธ์Šคํ„ด์Šค์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ถ€๋ถ„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

...
{
  "vr": "PN",
  "Value": [
    {
      "Alphabetic": "Sally Zhang"
    }
  ]
}
...

DICOM ์ €์žฅ์†Œ์—์„œ ํ™˜์ž์™€ ๊ด€๋ จ๋œ ์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด PatientName DICOM ํƒœ๊ทธ๋กœ ๊ฒ€์ƒ‰ํ•˜๋Š” ์š”์ฒญ์— ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Cloud Healthcare API์—์„œ ์ง€์›๋˜๋Š” ๊ฒ€์ƒ‰ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies?PatientName=Sally%20Zhang"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method GET `
-Headers $headers `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies?PatientName=Sally%20Zhang" | Select-Object -Expand Content

API ํƒ์ƒ‰๊ธฐ

๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํŽ˜์ด์ง€๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ์— API ํƒ์ƒ‰๊ธฐ ํŒจ๋„์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ JSON ์‘๋‹ต์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"

	healthcare "google.golang.org/api/healthcare/v1"
)

// queryParamOpt is a googleapi.Option (https://godoc.org/google.golang.org/api/googleapi#CallOption)
// that adds query parameters to an API call.
type queryParamOpt struct {
	key, value string
}

func (qp queryParamOpt) Get() (string, string) { return qp.key, qp.value }

// dicomWebSearchStudies refines a DICOMweb studies search by appending DICOM tags to the request.
func dicomWebSearchStudies(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath string) error {
	// projectID := "my-project"
	// location := "us-central1"
	// datasetID := "my-dataset"
	// dicomStoreID := "my-dicom-store"
	// dicomWebPath := "studies"
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores

	name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	call := storesService.SearchForStudies(name, dicomWebPath)
	// Refine your search by appending DICOM tags to the
	// request in the form of query parameters. This sample
	// searches for studies containing a patient's name.
	patientName := queryParamOpt{key: "PatientName", value: "Sally Zhang"}
	resp, err := call.Do(patientName)
	if err != nil {
		return fmt.Errorf("Get: %w", err)
	}

	defer resp.Body.Close()

	respBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ioutil.ReadAll: %w", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("SearchForStudies: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	respString := string(respBytes)
	if len(respString) > 0 {
		fmt.Fprintf(w, "Found studies: %s\n", respString)
	} else {
		fmt.Println("No studies found.")
	}

	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.util.Collections;

public class DicomWebSearchStudies {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebSearchStudies(String dicomStoreName) throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    DicomStores.SearchForStudies request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .searchForStudies(dicomStoreName, "studies")
            // Refine your search by appending DICOM tags to the
            // request in the form of query parameters. This sample
            // searches for studies containing a patient's name.
            .set("PatientName", "Sally Zhang");

    // Execute the request and process the results.
    HttpResponse response = request.executeUnparsed();
    System.out.println("Studies found: \n" + response.toString());
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});

const dicomWebSearchStudies = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = 'studies';
  const request = {parent, dicomWebPath};

  const studies =
    await healthcare.projects.locations.datasets.dicomStores.searchForStudies(
      request,
      {
        // Refine your search by appending DICOM tags to the
        // request in the form of query parameters. This sample
        // searches for studies containing a patient's name.
        params: {PatientName: 'Sally Zhang'},
        headers: {Accept: 'application/dicom+json'},
      }
    );
  console.log(studies);

  console.log(`Found ${studies.data.length} studies:`);
  console.log(JSON.stringify(studies.data));
};

dicomWebSearchStudies();

Python

def dicomweb_search_studies(project_id, location, dataset_id, dicom_store_id):
    """Handles the GET requests specified in the DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicomweb_path = "{}/datasets/{}/dicomStores/{}/dicomWeb/studies".format(
        url, dataset_id, dicom_store_id
    )

    # Refine your search by appending DICOM tags to the
    # request in the form of query parameters. This sample
    # searches for studies containing a patient's name.
    params = {"PatientName": "Sally Zhang"}

    response = session.get(dicomweb_path, params=params)

    response.raise_for_status()

    print(f"Studies found: response is {response}")

    # Uncomment the following lines to process the response as JSON.
    # patients = response.json()
    # print('Patients found matching query:')
    # print(json.dumps(patients, indent=2))

    # return patients

DICOMweb CLI ์‚ฌ์šฉ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Cloud Healthcare API DICOMweb CLI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์ €์žฅ์†Œ์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰ ํ•„ํ„ฐ๋ง ๋ฐฉ๋ฒ•์„ ํฌํ•จํ•˜์—ฌ DICOMweb CLI GitHub ์ €์žฅ์†Œ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒ˜ํ”Œ์ด ๋” ์žˆ์Šต๋‹ˆ๋‹ค.

dcmweb \
  https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb \
  search instances

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ JSON ํ˜•์‹์œผ๋กœ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

[
   {
      "00080005":{
         "vr":"CS",
         "Value":[
            "CODE_STRING"
         ]
      },
      "00080016":{
         "vr":"UI",
         "Value":[
            "UNIQUE_IDENTIFIER"
         ]
      },
      "00080018":{
         "vr":"UI",
         "Value":[
            "UNIQUE_IDENTIFIER"
         ]
      },
      "00080020":{
         "vr":"DA",
         "Value":[
            "DATE_TIME"
         ]
      },
      "00080030":{
         "vr":"TM",
         "Value":[
            "TIME"
         ]
      },
      "00080060":{
         "vr":"CS",
         "Value":[
            "CODE_STRING"
         ]
      },
      "0008103E":{
         "vr":"LO",
         "Value":[
            "LONG_STRING"
         ]
      },
      "00100010":{
         "vr":"PN",
         "Value":[
            {
               "Alphabetic":"Anonymized"
            }
         ]
      },
   },

...

]

DICOM ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰

Cloud Healthcare API๋Š” DICOM ์ €์žฅ์†Œ์—์„œ ์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ, ์ธ์Šคํ„ด์Šค, ํ”„๋ ˆ์ž„์„ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•ด ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ ๊ฒ€์ƒ‰ ํŠธ๋žœ์žญ์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

์—ฐ๊ตฌ ๊ฒ€์ƒ‰

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ DICOM ์—ฐ๊ตฌ/์‹œ๋ฆฌ์ฆˆ/์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ถœ๋ ฅ ํŒŒ์ผ์„ ์ง€์ •ํ•  ๋•Œ .multipart์™€ ๊ฐ™์€ ํ™•์žฅ์ž๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ํ›„ ๋ฉ€ํ‹ฐํŒŒํŠธ ํŒŒ์ผ์„ ํŒŒ์‹ฑํ•˜์—ฌ ์—ฐ๊ตฌ์—์„œ ๊ฐœ๋ณ„ ์‹œ๋ฆฌ์ฆˆ ๋ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.studies.retrieveStudy๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

curl

์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์ถœ๋ ฅ ํŒŒ์ผ
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ curl์„ ์‚ฌ์šฉํ•˜๋Š” GET ์š”์ฒญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl -X GET \
     -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
     -H "Accept: multipart/related; type=application/dicom; transfer-syntax=*" \
     "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID" \
     --output FILENAME.multipart

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด DICOM ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

PowerShell

์—ฐ๊ตฌ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์ถœ๋ ฅ ํŒŒ์ผ
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Windows PowerShell์„ ์‚ฌ์šฉํ•œ GET ์š”์ฒญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred"; Accept = "multipart/related; type=application/dicom; transfer-syntax=*" }

Invoke-WebRequest `
  -Method Get `
  -Headers $headers `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID" | Select-Object -Expand Content
  -OutFile FILENAME.multipart `

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด DICOM ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"
	"os"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebRetrieveStudy retrieves all instances in the given dicomWebPath
// study.
func dicomWebRetrieveStudy(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath string, outputFile string) error {
	// projectID := "my-project"
	// location := "us-central1"
	// datasetID := "my-dataset"
	// dicomStoreID := "my-dicom-store"
	// dicomWebPath := "studies/1.3.6.1.4.1.11129.5.5.111396399857604"
	// outputFile := "study.multipart"
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores.Studies

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	resp, err := storesService.RetrieveStudy(parent, dicomWebPath).Do()
	if err != nil {
		return fmt.Errorf("RetrieveStudy: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode > 299 {
		return fmt.Errorf("RetrieveStudy: status %d %s: %s", resp.StatusCode, resp.Status, resp.Body)
	}

	file, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("os.Create: %w", err)
	}
	defer file.Close()
	if _, err := io.Copy(file, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}

	// When specifying the output file, use an extension like ".multipart".
	// Then, parse the downloaded multipart file to get each individual DICOM
	// file.
	fmt.Fprintf(w, "Study retrieved and downloaded to file: %v\n", outputFile)

	return nil
}

Java

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores.Studies;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;

public class DicomWebRetrieveStudy {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebRetrieveStudy(String dicomStoreName, String studyId)
      throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");
    // String studyId = "your-study-id";

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    // Create request and configure any parameters.
    Studies.RetrieveStudy request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .studies()
            .retrieveStudy(dicomStoreName, "studies/" + studyId);

    // Execute the request and process the results.
    HttpResponse response = request.executeUnparsed();

    // When specifying the output file, use an extension like ".multipart".
    // Then, parse the downloaded multipart file to get each individual
    // DICOM file.
    String outputPath = "study.multipart";
    OutputStream outputStream = new FileOutputStream(new File(outputPath));
    try {
      response.download(outputStream);
      System.out.println("DICOM study written to file " + outputPath);
    } finally {
      outputStream.close();
    }

    if (!response.isSuccessStatusCode()) {
      System.err.print(
          String.format("Exception retrieving DICOM study: %s\n", response.getStatusMessage()));
      throw new RuntimeException();
    }
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    HttpHeaders headers = new HttpHeaders();
    // The response's default transfer syntax is Little Endian Explicit.
    // As a result, if the file was uploaded using a compressed transfer syntax,
    // the returned object will be decompressed. This can negatively impact performance and lead
    // to errors for transfer syntaxes that the Cloud Healthcare API doesn't support.
    // To avoid these issues, and if the returned object's transfer syntax doesn't matter to
    // your application, use the
    // multipart/related; type="application/dicom"; transfer-syntax=* Accept Header.
    headers.setAccept("multipart/related; type=application/dicom; transfer-syntax=*");
    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});
const fs = require('fs');
const util = require('util');
const writeFile = util.promisify(fs.writeFile);
// When specifying the output file, use an extension like ".multipart."
// Then, parse the downloaded multipart file to get each individual
// DICOM file.
const fileName = 'study_file.multipart';

const dicomWebRetrieveStudy = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  // const studyUid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = `studies/${studyUid}`;
  const request = {parent, dicomWebPath};

  const study =
    await healthcare.projects.locations.datasets.dicomStores.studies.retrieveStudy(
      request,
      {
        headers: {
          Accept:
            'multipart/related; type=application/dicom; transfer-syntax=*',
        },
        responseType: 'arraybuffer',
      }
    );

  const fileBytes = Buffer.from(study.data);

  await writeFile(fileName, fileBytes);
  console.log(
    `Retrieved study and saved to ${fileName} in current directory`
  );
};

dicomWebRetrieveStudy();

Python

def dicomweb_retrieve_study(
    project_id, location, dataset_id, dicom_store_id, study_uid
):
    """Handles the GET requests specified in the DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    # study_uid = '1.3.6.1.4.1.5062.55.1.227'  # replace with the study UID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicomweb_path = "{}/datasets/{}/dicomStores/{}/dicomWeb/studies/{}".format(
        url, dataset_id, dicom_store_id, study_uid
    )

    # When specifying the output file, use an extension like ".multipart."
    # Then, parse the downloaded multipart file to get each individual
    # DICOM file.
    file_name = "study.multipart"

    response = session.get(dicomweb_path)

    response.raise_for_status()

    with open(file_name, "wb") as f:
        f.write(response.content)
        print(f"Retrieved study and saved to {file_name} in current directory")

    return response

์ธ์Šคํ„ด์Šค ๊ฒ€์ƒ‰

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ์ธ์Šคํ„ด์Šค ๊ฒ€์ƒ‰ ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ DICOM ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ๋•Œ Accept: application/dicom HTTP ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ€ํ‹ฐํŒŒํŠธ ๊ฒฝ๊ณ„๋ฅผ ํŒŒ์‹ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. transfer-syntax=*๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์›๋ž˜ ์ €์žฅ๋œ ํ˜•์‹์œผ๋กœ ํŒŒ์ผ์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํŠธ๋žœ์Šค์ฝ”๋”ฉ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.studies.series.instances.retrieveInstance๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

curl

์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์‹œ๋ฆฌ์ฆˆ UID, ์ธ์Šคํ„ด์Šค UID
  • ์ถœ๋ ฅ ํŒŒ์ผ ์ด๋ฆ„
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ curl์„ ์‚ฌ์šฉํ•˜๋Š” GET ์š”์ฒญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl -X GET \
     -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
     -H "Accept: application/dicom; transfer-syntax=*" \
     "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID" \
     --output FILENAME.dcm

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด DICOM ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

PowerShell

์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์‹œ๋ฆฌ์ฆˆ UID
  • ์ธ์Šคํ„ด์Šค UID
  • ์ถœ๋ ฅ ํŒŒ์ผ ์ด๋ฆ„
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Windows PowerShell์„ ์‚ฌ์šฉํ•œ GET ์š”์ฒญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred"; Accept = "application/dicom; transfer-syntax=*" }

Invoke-RestMethod `
  -Method Get `
  -Headers $headers `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID"
  -OutFile FILENAME.dcm `

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด DICOM ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"
	"os"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebRetrieveInstance retrieves a specific instance.
func dicomWebRetrieveInstance(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath string, outputFile string) error {
	// projectID := "my-project"
	// location := "us-central1"
	// datasetID := "my-dataset"
	// dicomStoreID := "my-dicom-store"
	// dicomWebPath := "studies/1.3.6.1.4.1.11129.5.5.1113639985/series/1.3.6.1.4.1.11129.5.5.1953511724/instances/1.3.6.1.4.1.11129.5.5.9562821369"
	// outputFile := "instance.dcm"
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	call := storesService.RetrieveInstance(parent, dicomWebPath)
	call.Header().Set("Accept", "application/dicom; transfer-syntax=*")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("RetrieveInstance: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode > 299 {
		return fmt.Errorf("RetrieveInstance: status %d %s: %s", resp.StatusCode, resp.Status, resp.Body)
	}

	file, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("os.Create: %w", err)
	}
	defer file.Close()
	if _, err := io.Copy(file, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}

	fmt.Fprintf(w, "DICOM instance retrieved and downloaded to file: %v\n", outputFile)

	return nil
}

Java

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;

public class DicomWebRetrieveInstance {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final String DICOMWEB_PATH = "studies/%s/series/%s/instances/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebRetrieveInstance(String dicomStoreName, String dicomWebPath)
      throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");
    // String dicomWebPath = String.format(DICOMWEB_PATH, "your-study-id", "your-series-id",
    // "your-instance-id");

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    // Create request and configure any parameters.
    Instances.RetrieveInstance request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .studies()
            .series()
            .instances()
            .retrieveInstance(dicomStoreName, dicomWebPath);

    // Execute the request and process the results.
    HttpResponse response = request.executeUnparsed();

    String outputPath = "instance.dcm";
    OutputStream outputStream = new FileOutputStream(new File(outputPath));
    try {
      response.download(outputStream);
      System.out.println("DICOM instance written to file " + outputPath);
    } finally {
      outputStream.close();
    }

    if (!response.isSuccessStatusCode()) {
      System.err.print(
          String.format("Exception retrieving DICOM instance: %s\n", response.getStatusMessage()));
      throw new RuntimeException();
    }
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    HttpHeaders headers = new HttpHeaders();
    headers.set("X-GFE-SSL", "yes");
    // Avoid parsing multipart boundaries by setting 'application/dicom' HTTP header.
    // Add 'transfer-syntax=*' to avoid transcoding by returning the file in the format it
    // was originally stored in.
    headers.setAccept("application/dicom; transfer-syntax=*");
    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});
const fs = require('fs');
const util = require('util');
const writeFile = util.promisify(fs.writeFile);
const fileName = 'instance_file.dcm';

const dicomWebRetrieveInstance = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  // const studyUid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0';
  // const seriesUid = '2.24.52329571877967561426579904912379710633';
  // const instanceUid = '1.3.6.2.4.2.14619.5.2.1.6280.6001.129311971280445372188125744148';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = `studies/${studyUid}/series/${seriesUid}/instances/${instanceUid}`;
  const request = {parent, dicomWebPath};

  const instance =
    await healthcare.projects.locations.datasets.dicomStores.studies.series.instances.retrieveInstance(
      request,
      {
        headers: {Accept: 'application/dicom; transfer-syntax=*'},
        responseType: 'arraybuffer',
      }
    );
  const fileBytes = Buffer.from(instance.data);

  await writeFile(fileName, fileBytes);
  console.log(
    `Retrieved DICOM instance and saved to ${fileName} in current directory`
  );
};

dicomWebRetrieveInstance();

Python

def dicomweb_retrieve_instance(
    project_id,
    location,
    dataset_id,
    dicom_store_id,
    study_uid,
    series_uid,
    instance_uid,
):
    """Handles the GET requests specified in the DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    # study_uid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0'  # replace with the study UID
    # series_uid = '2.24.52329571877967561426579904912379710633'  # replace with the series UID
    # instance_uid = '1.3.6.2.4.2.14619.5.2.1.6280.6001.129311971280445372188125744148'  # replace with the instance UID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicom_store_path = "{}/datasets/{}/dicomStores/{}".format(
        url, dataset_id, dicom_store_id
    )

    dicomweb_path = "{}/dicomWeb/studies/{}/series/{}/instances/{}".format(
        dicom_store_path, study_uid, series_uid, instance_uid
    )

    file_name = "instance.dcm"

    # Set the required Accept header on the request
    headers = {"Accept": "application/dicom; transfer-syntax=*"}
    response = session.get(dicomweb_path, headers=headers)
    response.raise_for_status()

    with open(file_name, "wb") as f:
        f.write(response.content)
        print(
            "Retrieved DICOM instance and saved to {} in current directory".format(
                file_name
            )
        )

    return response

์†Œ๋น„์ž ์ด๋ฏธ์ง€ ํ˜•์‹ ๊ฒ€์ƒ‰

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ๋ Œ๋”๋ง๋œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ Cloud Healthcare API ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ JPEG ๋˜๋Š” PNG์™€ ๊ฐ™์€ ์†Œ๋น„์ž ์ด๋ฏธ์ง• ํ˜•์‹์„ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ ๋ Œ๋”๋ง๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.studies.series.instances.retrieveRendered๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

curl

์ด๋ฏธ์ง€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์‹œ๋ฆฌ์ฆˆ UID
  • ์ธ์Šคํ„ด์Šค UID
  • ์ถœ๋ ฅ ํŒŒ์ผ ์ด๋ฆ„
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์—์„œ๋Š” curl์„ ์‚ฌ์šฉํ•˜์—ฌ GET ์š”์ฒญ์œผ๋กœ PNG ์ด๋ฏธ์ง€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

curl -X GET \
     -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
     -H "Accept: image/png" \
     "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/rendered" \
     --output FILENAME.png

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด PNG ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

PowerShell

์ด๋ฏธ์ง€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๋‹ค์Œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ์˜ ์ด๋ฆ„
  • DICOM ์ €์žฅ์†Œ์˜ ์ด๋ฆ„
  • ์—ฐ๊ตฌ ๊ณ ์œ  ์‹๋ณ„์ž(UID)
  • ์‹œ๋ฆฌ์ฆˆ UID
  • ์ธ์Šคํ„ด์Šค UID
  • ์ถœ๋ ฅ ํŒŒ์ผ ์ด๋ฆ„
  • ์•ก์„ธ์Šค ํ† ํฐ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Windows PowerShell์„ ์‚ฌ์šฉํ•˜์—ฌ GET ์š”์ฒญ์œผ๋กœ PNG ์ด๋ฏธ์ง€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred"; Accept = "image/png" }

Invoke-RestMethod `
  -Method Get `
  -Headers $headers `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/rendered"
  -OutFile FILENAME.png `

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด PNG ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"
	"os"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebRetrieveRendered retrieves a consumer imaging format like JPEG or PNG.
func dicomWebRetrieveRendered(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath string, outputFile string) error {
	// projectID := "my-project"
	// location := "us-central1"
	// datasetID := "my-dataset"
	// dicomStoreID := "my-dicom-store"
	// dicomWebPath := "studies/1.3.6.1.4.1.11129.5.5.1113639985/series/1.3.6.1.4.1.11129.5.5.1953511724/instances/1.3.6.1.4.1.11129.5.5.9562821369/rendered"
	// outputFile := "rendered_image.png"
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	call := storesService.RetrieveRendered(parent, dicomWebPath)
	call.Header().Set("Accept", "image/png")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("RetrieveRendered: %w", err)
	}

	defer resp.Body.Close()

	if resp.StatusCode > 299 {
		return fmt.Errorf("RetrieveRendered: status %d %s: %s", resp.StatusCode, resp.Status, resp.Body)
	}

	file, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("os.Create: %w", err)
	}
	defer file.Close()
	if _, err := io.Copy(file, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}

	fmt.Fprintf(w, "Rendered PNG image retrieved and downloaded to file: %v\n", outputFile)

	return nil
}

Java

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;

public class DicomWebRetrieveRendered {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final String DICOMWEB_PATH = "studies/%s/series/%s/instances/%s/rendered";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebRetrieveRendered(String dicomStoreName, String dicomWebPath)
      throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");
    // String dicomWebPath = String.format(DICOMWEB_PATH, "your-study-id", "your-series-id",
    // "your-instance-id");

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    // Create request and configure any parameters.
    Instances.RetrieveRendered request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .studies()
            .series()
            .instances()
            .retrieveRendered(dicomStoreName, dicomWebPath);

    // Execute the request and process the results.
    HttpResponse response = request.executeUnparsed();

    String outputPath = "image.png";
    OutputStream outputStream = new FileOutputStream(new File(outputPath));
    try {
      response.download(outputStream);
      System.out.println("DICOM rendered PNG image written to file " + outputPath);
    } finally {
      outputStream.close();
    }

    if (!response.isSuccessStatusCode()) {
      System.err.print(
          String.format(
              "Exception retrieving DICOM rendered image: %s\n", response.getStatusMessage()));
      throw new RuntimeException();
    }
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    HttpHeaders headers = new HttpHeaders();
    headers.set("X-GFE-SSL", "yes");
    // Retrieve using the PNG consumer imaging format.
    headers.setAccept("image/png");
    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});
const fs = require('fs');
const util = require('util');
const writeFile = util.promisify(fs.writeFile);
const fileName = 'rendered_image.png';

const dicomWebRetrieveRendered = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  // const studyUid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0';
  // const seriesUid = '2.24.52329571877967561426579904912379710633';
  // const instanceUid = '1.3.6.2.4.2.14619.5.2.1.6280.6001.129311971280445372188125744148';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = `studies/${studyUid}/series/${seriesUid}/instances/${instanceUid}/rendered`;
  const request = {parent, dicomWebPath};

  const rendered =
    await healthcare.projects.locations.datasets.dicomStores.studies.series.instances.retrieveRendered(
      request,
      {
        headers: {Accept: 'image/png'},
        responseType: 'arraybuffer',
      }
    );
  const fileBytes = Buffer.from(rendered.data);

  await writeFile(fileName, fileBytes);
  console.log(
    `Retrieved rendered image and saved to ${fileName} in current directory`
  );
};

dicomWebRetrieveRendered();

Python

def dicomweb_retrieve_rendered(
    project_id,
    location,
    dataset_id,
    dicom_store_id,
    study_uid,
    series_uid,
    instance_uid,
):
    """Handles the GET requests specified in the DICOMweb standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    # study_uid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0'  # replace with the study UID
    # series_uid = '2.24.52329571877967561426579904912379710633'  # replace with the series UID
    # instance_uid = '1.3.6.2.4.2.14619.5.2.1.6280.6001.129311971280445372188125744148'  # replace with the instance UID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicom_store_path = "{}/datasets/{}/dicomStores/{}".format(
        url, dataset_id, dicom_store_id
    )

    dicomweb_path = "{}/dicomWeb/studies/{}/series/{}/instances/{}/rendered".format(
        dicom_store_path, study_uid, series_uid, instance_uid
    )

    file_name = "rendered_image.png"

    # Sets the required Accept header on the request for a PNG image
    headers = {"Accept": "image/png"}
    response = session.get(dicomweb_path, headers=headers)
    response.raise_for_status()

    with open(file_name, "wb") as f:
        f.write(response.content)
        print(
            "Retrieved rendered image and saved to {} in current directory".format(
                file_name
            )
        )

    return response

๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰

์—ฐ๊ตฌ ๋˜๋Š” ์‹œ๋ฆฌ์ฆˆ์—์„œ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ƒ˜ํ”Œ์€ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ช…์„ธ์—์„œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.studies.series.instances.retrieveMetadata๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

retrieveMetadata๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด includefield=all ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ํ•„๋“œ ์ง‘ํ•ฉ์ด ๋ฉ”์„œ๋“œ๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ง€์—ฐ ์‹œ๊ฐ„์— ๋ฏผ๊ฐํ•˜๊ณ  ๋ชจ๋“  ํ•„๋“œ ๋Œ€์‹  ํŠน์ • ํ•„๋“œ ์ง‘ํ•ฉ์— ๋Œ€ํ•ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์—๋Š” retrieveMetadata๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”. ๋Œ€์‹  searchForInstances ๋ฉ”์„œ๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ํ•„๋“œ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋” ์ž‘์€ ํ•„๋“œ ์ง‘ํ•ฉ์ด ์‘๋‹ต๋˜๊ณ  ์ž‘์€ ํ•„๋“œ ์ง‘ํ•ฉ์€ ์ง€์—ฐ ์‹œ๊ฐ„์— ๋ฏผ๊ฐํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

retrieveMetadata๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ JSON ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. XML ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋ ค๋ฉด ์š”์ฒญ์— Accept: multipart/related; type="application/dicom+xml" HTTP ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • STUDY_INSTANCE_UID: ์—ฐ๊ตฌ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž
  • SERIES_INSTANCE_UID: ์‹œ๋ฆฌ์ฆˆ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž
  • INSTANCE_UID: ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/metadata"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method GET `
-Headers $headers `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/metadata" | Select-Object -Expand Content

API ํƒ์ƒ‰๊ธฐ

๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํŽ˜์ด์ง€๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ์— API ํƒ์ƒ‰๊ธฐ ํŒจ๋„์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ JSON ์‘๋‹ต์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์ผ๊ด„ ๋ฐ์ดํ„ฐ ๊ฒ€์ƒ‰

DICOM ์ธ์Šคํ„ด์Šค๋Š” ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๋ฐ์ดํ„ฐ ์š”์†Œ์—๋Š” ํ™˜์ž ์ด๋ฆ„์ด๋‚˜ ์—ฐ๊ตฌ ๋‚ ์งœ์™€ ๊ฐ™์€ ์ž‘์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜์ง€๋งŒ ์ผ๋ถ€์—๋Š” 'bulkdata'๋ผ๊ณ ๋„ ํ•˜๋Š” ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Cloud Healthcare API์—์„œ '๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ'๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์•Œ์•„๋ณด๋ ค๋ฉด ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ •์˜๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

Cloud Healthcare API๋Š” ์ด๋Ÿฌํ•œ '๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ' ์š”์†Œ๋ฅผ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์›์‹œ ๊ฐ’ ๋ฐ”์ดํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ projects.locations.datasets.dicomStores.studies.series.instances.bulkdata.retrieveBulkdata๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

  • ์ง์ ‘ ๊ฒ€์ƒ‰: ์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ, ์ธ์Šคํ„ด์Šค, ๊ฒ€์ƒ‰ํ•˜๋ ค๋Š” ํŠน์ • ํƒœ๊ทธ์˜ ์‹๋ณ„์ž๋ฅผ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ ์š”์ฒญ URL์„ ๊ตฌ์„ฑํ•˜๊ณ  retrieveBulkdata๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง์ ‘ ๊ฒ€์ƒ‰์€ ๋‹จ์ผ API ํ˜ธ์ถœ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ๊ฐ€์žฅ ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

  • DICOM ์ธ์Šคํ„ด์Šค ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•œ ๊ฒ€์ƒ‰: ์ธ์Šคํ„ด์Šค์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ฉด ์ง€์›๋˜๋Š” ๊ฐ bulkdata ํƒœ๊ทธ์— ๋Œ€ํ•œ BulkDataURI์ด ์‘๋‹ต์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด URI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ retrieveBulkdata ์š”์ฒญ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ๊ฐ€์ ธ์˜ค๊ธฐ ์ „์— ์ธ์Šคํ„ด์Šค์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ํƒœ๊ทธ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์ƒ˜ํ”Œ์€ ์ง์ ‘ ๊ฒ€์ƒ‰์„ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

REST

์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

  • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
  • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
  • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
  • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
  • STUDY_INSTANCE_UID: ์—ฐ๊ตฌ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž
  • SERIES_INSTANCE_UID: ์‹œ๋ฆฌ์ฆˆ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž
  • INSTANCE_UID: ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž
  • BULK_DATA_PATH: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ํƒœ๊ทธ์ž…๋‹ˆ๋‹ค(์˜ˆ: PixelData์˜ ๊ฒฝ์šฐ 7FE00010). ํ•ญ๋ชฉ ์ƒ‰์ธ i์—์„œ ์‹œํ€€์Šค (BBBB,BBBB) ๋‚ด์˜ ํƒœ๊ทธ (AAAA,AAAA)์˜ ๊ฒฝ์šฐ ๊ฒฝ๋กœ๋Š” BBBBBBBB/i/AAAAAAAA์ž…๋‹ˆ๋‹ค.
  • OUTPUT_FILE_PATH: DAT ํŒŒ์ผ์— ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋œ ๋กœ์ปฌ ๋จธ์‹ ์˜ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. .dat ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์„ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

curl

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
--output OUTPUT_FILE_PATH \
"https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/bulkdata/BULK_DATA_PATH"

PowerShell

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method GET `
-Headers $headers `
-OutFile OUTPUT_FILE_PATH `
-Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/INSTANCE_UID/bulkdata/BULK_DATA_PATH"
์„ฑ๊ณตํ•˜๋ฉด DAT ํŒŒ์ผ์ด ๋กœ์ปฌ ๋จธ์‹ ์— ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

DICOMweb CLI ์‚ฌ์šฉ

๋‹ค์Œ ์ƒ˜ํ”Œ์—์„œ๋Š” Cloud Healthcare API DICOMweb CLI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ DICOM ์ €์žฅ์†Œ์—์„œ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋จธ์‹ ์—์„œ ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. DICOMweb CLI GitHub ์ €์žฅ์†Œ์—๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒ˜ํ”Œ์ด ๋” ์žˆ์Šต๋‹ˆ๋‹ค.

dcmweb \
  https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb \
  retrieve

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  DICOM ํŒŒ์ผ์ด ๋จธ์‹ ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

TIMESTAMP -- Saving files into ./
TIMESTAMP -- Transferred SIZE in COUNT files

์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ ๋˜๋Š” ์ธ์Šคํ„ด์Šค ์‚ญ์ œ

Cloud Healthcare API๋Š” DICOM ์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ, ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•œ ๋…์  ์›น ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ด ์„œ๋น„์Šค๋Š” DICOMweb ํ‘œ์ค€ ์„œ๋น„์Šค์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ Cloud Healthcare API DICOM ์ ํ•ฉ์„ฑ ๋ฌธ์˜ ์‚ญ์ œ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

์—ฐ๊ตฌ ๋ฐ ์‹œ๋ฆฌ์ฆˆ์˜ ์‚ญ์ œ ์š”์ฒญ์€ ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์—ฐ๊ตฌ ๋˜๋Š” ์‹œ๋ฆฌ์ฆˆ์˜ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.

์ธ์Šคํ„ด์Šค์˜ ์‚ญ์ œ ์š”์ฒญ์€ ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—…์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋Œ€์‹  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋นˆ ์‘๋‹ต ๋ณธ๋ฌธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

{}

๋‹ค์Œ ์ƒ˜ํ”Œ์€ DICOM ์—ฐ๊ตฌ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ projects.locations.datasets.dicomStores.studies.delete๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

REST

  1. ์—ฐ๊ตฌ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

    ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

    • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
    • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
    • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
    • DICOM_STORE_ID: DICOM ์ €์žฅ์†Œ ID
    • STUDY_INSTANCE_UID: ์—ฐ๊ตฌ ์ธ์Šคํ„ด์Šค ๊ณ ์œ  ์‹๋ณ„์ž

    ์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

    curl

    ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    curl -X DELETE \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID"

    PowerShell

    ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    $cred = gcloud auth print-access-token
    $headers = @{ "Authorization" = "Bearer $cred" }

    Invoke-WebRequest `
    -Method DELETE `
    -Headers $headers `
    -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb/studies/STUDY_INSTANCE_UID" | Select-Object -Expand Content

    API ํƒ์ƒ‰๊ธฐ

    ๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํŽ˜์ด์ง€๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ์— API ํƒ์ƒ‰๊ธฐ ํŒจ๋„์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

    ๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ JSON ์‘๋‹ต์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

  2. ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—… ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

    ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๋‹ค์Œ์„ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

    • PROJECT_ID: Google Cloud ํ”„๋กœ์ ํŠธ ID
    • LOCATION: ๋ฐ์ดํ„ฐ ์„ธํŠธ ์œ„์น˜
    • DATASET_ID: DICOM ์ €์žฅ์†Œ์˜ ์ƒ์œ„ ๋ฐ์ดํ„ฐ ์„ธํŠธ
    • OPERATION_ID: ์žฅ๊ธฐ ์‹คํ–‰ ์ž‘์—…์—์„œ ๋ฐ˜ํ™˜๋œ ID

    ์š”์ฒญ์„ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ ์˜ต์…˜ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

    curl

    ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    curl -X GET \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/operations/OPERATION_ID"

    PowerShell

    ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

    $cred = gcloud auth print-access-token
    $headers = @{ "Authorization" = "Bearer $cred" }

    Invoke-WebRequest `
    -Method GET `
    -Headers $headers `
    -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/operations/OPERATION_ID" | Select-Object -Expand Content

    API ํƒ์ƒ‰๊ธฐ

    ๋ฉ”์„œ๋“œ ์ฐธ์กฐ ํŽ˜์ด์ง€๋ฅผ ์—ฝ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ์— API ํƒ์ƒ‰๊ธฐ ํŒจ๋„์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํ•„์ˆ˜ ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

    ๋‹ค์Œ๊ณผ ๋น„์Šทํ•œ JSON ์‘๋‹ต์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

Go

import (
	"context"
	"fmt"
	"io"

	healthcare "google.golang.org/api/healthcare/v1"
)

// dicomWebDeleteStudy deletes all instances in the given dicomWebPath study.
func dicomWebDeleteStudy(w io.Writer, projectID, location, datasetID, dicomStoreID, dicomWebPath string) error {
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %w", err)
	}

	storesService := healthcareService.Projects.Locations.Datasets.DicomStores.Studies

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", projectID, location, datasetID, dicomStoreID)

	if _, err := storesService.Delete(parent, dicomWebPath).Do(); err != nil {
		return fmt.Errorf("Delete: %w", err)
	}

	fmt.Fprintf(w, "Deleted %q\n", dicomWebPath)
	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcare.Projects.Locations.Datasets.DicomStores.Studies;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.util.Collections;

public class DicomWebDeleteStudy {
  private static final String DICOM_NAME = "projects/%s/locations/%s/datasets/%s/dicomStores/%s";
  private static final JsonFactory JSON_FACTORY = new GsonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void dicomWebDeleteStudy(String dicomStoreName, String studyId) throws IOException {
    // String dicomStoreName =
    //    String.format(
    //        DICOM_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-dicom-id");
    // String studyId = "your-study-id";

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();

    // Create request and configure any parameters.
    Studies.Delete request =
        client
            .projects()
            .locations()
            .datasets()
            .dicomStores()
            .studies()
            .delete(dicomStoreName, "studies/" + studyId);

    // Execute the request and process the results.
    request.execute();
    System.out.println("DICOM study deleted.");
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }
}

Node.js

const google = require('@googleapis/healthcare');
const healthcare = google.healthcare({
  version: 'v1',
  auth: new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  }),
});

const dicomWebDeleteStudy = async () => {
  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const dicomStoreId = 'my-dicom-store';
  // const studyUid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/dicomStores/${dicomStoreId}`;
  const dicomWebPath = `studies/${studyUid}`;
  const request = {parent, dicomWebPath};

  await healthcare.projects.locations.datasets.dicomStores.studies.delete(
    request
  );
  console.log('Deleted DICOM study');
};

dicomWebDeleteStudy();

Python

def dicomweb_delete_study(project_id, location, dataset_id, dicom_store_id, study_uid):
    """Handles DELETE requests equivalent to the GET requests specified in
    the WADO-RS standard.

    See https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/healthcare/api-client/v1/dicom
    before running the sample."""

    # Imports the google.auth.transport.requests transport
    from google.auth.transport import requests

    # Imports a module to allow authentication using Application Default Credentials (ADC)
    import google.auth

    # Gets credentials from the environment. google.auth.default() returns credentials and the
    # associated project ID, but in this sample, the project ID is passed in manually.
    credentials, _ = google.auth.default()

    scoped_credentials = credentials.with_scopes(
        ["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Creates a requests Session object with the credentials.
    session = requests.AuthorizedSession(scoped_credentials)

    # URL to the Cloud Healthcare API endpoint and version
    base_url = "https://healthcare.googleapis.com/v1"

    # TODO(developer): Uncomment these lines and replace with your values.
    # project_id = 'my-project'  # replace with your GCP project ID
    # location = 'us-central1'  # replace with the parent dataset's location
    # dataset_id = 'my-dataset'  # replace with the parent dataset's ID
    # dicom_store_id = 'my-dicom-store' # replace with the DICOM store ID
    # study_uid = '1.3.6.1.4.1.5062.55.1.2270943358.716200484.1363785608958.61.0'  # replace with the study UID
    url = f"{base_url}/projects/{project_id}/locations/{location}"

    dicomweb_path = "{}/datasets/{}/dicomStores/{}/dicomWeb/studies/{}".format(
        url, dataset_id, dicom_store_id, study_uid
    )

    # Sets the required application/dicom+json; charset=utf-8 header on the request
    headers = {"Content-Type": "application/dicom+json; charset=utf-8"}

    response = session.delete(dicomweb_path, headers=headers)
    response.raise_for_status()

    print("Deleted study.")

    return response

DICOMweb CLI ์‚ฌ์šฉ

๋‹ค์Œ ์ƒ˜ํ”Œ์€ Cloud Healthcare API DICOMweb CLI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ตฌ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

dcmweb \
    https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/dicomStores/DICOM_STORE_ID/dicomWeb \
   delete studies/STUDY_INSTANCE_UID

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๋Š” ์‚ญ์ œ ๋„๊ตฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ CLI ๋„๊ตฌ๊ฐ€ ํด๋งํ•˜๋Š” ์ž‘์—…์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ ๋˜๋Š” ์ธ์Šคํ„ด์Šค ์—…๋ฐ์ดํŠธ ๋˜๋Š” ํŒจ์น˜

DICOMweb ํ‘œ์ค€์€ DICOM ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ•˜์ง€ ์•Š์ง€๋งŒ Cloud Healthcare API๋Š” ์ˆ˜์ง‘ ํ›„ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•๊ณผ ๊ฐ€์ด๋“œ๋Š” DICOM ์—ฐ๊ตฌ, ์‹œ๋ฆฌ์ฆˆ, ์ธ์Šคํ„ด์Šค ์—…๋ฐ์ดํŠธ ๋ฐ ํŒจ์น˜๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.