Skip to main content

Setup Notes - PathCloud

Environment: gcp-pathcloud-stage01

Setup Steps

GCP Digital Pathology - DICOM
PathCloud DICOM Configuration
Medplum FHIR Server Pre-requisites
Meplum Fhir Server in Cloud Run
PathCloud Load-balancer
Medplum Admin App in Cloud Run
PathCloud App
Viewers (Slim, OHIF)
Image Transformation Svc
DICOMCast Webhook Svc DICOM Cast Bot Practitioner Create Bot & Subscription Sample Data in new Project (Giuseppe Gestalt) Orthanc PACS Server AI Studio Viewer & Server

GCP Digital Pathology

Follow Steps in IAC Playbook

Additional IAP Configuration for DICOM Proxy

  • Enable or disable IAP depending on use-case. (Public/Anonymous Access versus Private/Authenticated Access)

Add User to "IAP-Secured Web App User" role

  • manually configure initial users in IAP screen in Console

Enable CORS for IAP on DICOM-Proxy

  1. Click vertical elipse on right side of "default/dicom-proxy-service" row
  2. Click "Settings" and then Click "Enable HTTP Options"

Public DICOM Store & Proxy Configuration

  • additional configuration for accessing DICOM. Currently using GCP Digital Pathology setup for initial config. May transition to use of Cloud Run DICOM-PROXY and "manual" setup of DICOM Store, BigQuery, PubSub, Service Accounts (082924)

Deploy DICOM-PROXY-Public to CloudRun

  • Config is for PUBLIC Proxy (URL_PATH_PREFIX=/public01)
gcloud run deploy dicom-proxy-public01 \
--image=us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/dicom-proxy:latest \
--region=us-west2 --project=pathcloud-stage01 \
--port=8080 --allow-unauthenticated \
--memory 16G --cpu 4 --execution-environment=gen2 \
--cpu-boost --no-cpu-throttling \
--min-instances=1 --max-instances=100 --timeout=300 --concurrency=40 \
--service-account=dicom-proxy-k8s-sa@pathcloud-stage01.iam.gserviceaccount.com \
--set-env-vars "ORIGINS=*" \
--set-env-vars "VALIDATE_IAP=false" \
--set-env-vars "ENABLE_APPLICATION_DEFAULT_CREDENTIALS=true" \
--set-env-vars "REDIS_CACHE_HOST_IP=localhost" \
--set-env-vars "REDIS_CACHE_HOST_PORT=6379" \
--set-env-vars "ENABLE_DEBUG_FUNCTION_TIMING=true" \
--set-env-vars "URL_PATH_PREFIX=/public01" \
--set-env-vars "API_PORT_FLG=8080"

Create LB Backend and Network Endpoint Group (NEG)

  • in console, create Backend and follow prompt for new NEG.
  • use Cloud Run container NAME as convention for Backend and NEG name TODO - script creation of Backend and NEG

Disable Cloud CDN - can't use IAP if Cloud CDN is enabled

Create LB Route for backend to URL_PATH_PREFIX

  • add Host Path and Rules (enables use of multiple proxies from same DNS host) DNS Host: dicom.stage01.gestaltcloud.com Path Matcher:
defaultService: projects/pathcloud-stage01/global/backendServices/dicom-proxy-public01-backend
name: path-matcher-7
routeRules:
- description: route-rule-public01
matchRules:
- prefixMatch: /public01
priority: 1
service: projects/pathcloud-stage01/global/backendServices/dicom-proxy-public01-backend

Enable IAP on Backend

  • if Proxy is for Private Access then enable IAP (in this instance is public, so IAP disabled)

Configure BigQuery Instance for Public DICOM Store

  1. Copy table "slide-dicom-store-metadata" into a new dataset named: "dicom_pathology_public" (use Create Dataset option in copy)
  2. Delete all rows in copied table in new dataset
DELETE FROM `pathcloud-stage01.dicom_pathology_public.slide-dicom-store-metadata` 
WHERE MediaStorageSOPClassUID IS NOT null
  1. Create view named: slide-dicom-store-metadataView - copy query from same view in "dicom_pathology", run query, save sas View
  2. Create view name: pathcloud_slide-dicom-store-metadata_groupedView - copy query from same view in "dicom_pathology" (change dataset), run query, save as View
  • name is used in DICOMCast SQL Query
  1. May need to add service account to new dataset permissions (click "Sharing", then "Permissions") If permission not added, will be notified in DICOM Store setup

Configure Pub/Sub Topic & Webhook ubscription for Public DICOM Store

  1. In Pub/Sub "Topics" create Topic ID = transformation-dicom-store-topic (need to create in DICOMStore setup wizard)
  2. In Pub/Sub "Subscriptions" create new subscription: Subscription ID: pathcloud-stage01-webhook-dicom-public-medplum-subscription Topic: pathcloud-stage01-webhook-dicom-public-medplum-subscription Delivery Type: Push Endpoint URL: https://pathcloud-webhook-dicom-pubsub-392114647566.us-west2.run.app (default URL of Webhook container)

Setup new DICOM Store (Public)

  • use Console / Healthcare / Browser to create new DICOM store Name: slide-dicom-store-public
  • select Streaming Configurations and select "slide-dicom-store-metadata" in "dicom_pathology_dataset"
  • select Cloud Pub/Sub notifications - create new topic transformation-dicom-store-topic

Setup Project Pre-Requisites

Setup gcp digital pathology infrastructure

Enable OAuth Client to Access IAP Resources

Links:

Scripts:

  • create settings file
  cat << EOF > oauth-iap-access-settings.txt
access_settings:
oauth_settings:
programmatic_clients: ["1053568465268-t2coh1p3ke4lrhu6o042squicec9toed.apps.googleusercontent.com"]
EOF
  • Allowlist OAuth Client for IAP in PROJECT gcloud iap settings set oauth-iap-access-settings.txt --project=pathcloud-stage01

Setup Medplum FHIR Server Pre-requisites

Install PostgreSQL instance in SQL services
Create storage bucket for config files: "apps-config" Upload medplum db SQL script apps-config bucket Will use to create gold medplum db (previously exported from medplum-poc1 in gcp-pathology-poc1 project) Import medplum db SQL script to new database in PostgreSQL called medplum-stag01 (password of db is G3st@lt) Enable Cloud SQL Admin API (necessary for Cloud Run containers)

Configure SMTP Server for Medplum Config

  • using Mailgun for SMTP relay. Limits 100 emails /day
  • https://app.mailgun.com/ TODO - setup higher volume SMTP relay on SendGrid or Mailgun (090324)

Setup Medplum FHIR Server in Cloud Run

Configure new medplum config file (medplum.config.cloud.stage01.json)

  • configure env (eg stage01) in urls
  • configure db connection
  • configure Redis IP address

Remove domain restricted sharing on project to enable public access to Cloud Run containers (need to do this in Cloud Console)

Add Cloud Run service account (eg service-392114647566@serverless-robot-prod.iam.gserviceaccount.com) to project with Artifact Registry that has selected container image

Create Bucket for medplum file storage (binary's)

gcloud storage buckets create gs://medplum-server-files-stage01 --location=us-west2

Create Bucket for medplum config storage

  • need two buckets because can only volume mount a bucket at root of bucket gcloud storage buckets create gs://medplum-server-config-stage01 --location=us-west2
  • Name: medplum-server-config-stage01

Upload medplum config json

gcloud storage cp medplum.config.cloud.stage01.json gs://medplum-server-config-stage01

Build and Push medplum-fhir container image (Code)

  • see README in app-containers/server-medplum-fhir directory Build Container
  • Run inside app-containers/server-medplum-fhir
  • Container with Medplum files and build inside (3 GB) docker build -f Dockerfile.medplum.code --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/medplum-server-code:v4.0.2 ../../.

Push image to Artifact Registry docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/medplum-server-code:v4.0.2

Deploy Cloud Run container

  • change "file" arg to relevant medplum config
  • need to use Medplum Server - Code image instead of DIST (issue with token, can't create Client App)
gcloud config set project pathcloud-stage01 && \
gcloud beta run deploy medplum-server --image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/medplum-server-code:v4.0.2 --region=us-west2 --allow-unauthenticated \
--port 8103 --memory 4G \
--network default --subnet default \
--add-volume name=medplum-server-files,type=cloud-storage,bucket=medplum-server-files-stage01 \
--add-volume-mount volume=medplum-server-files,mount-path=/app/packages/server/binary \
--add-volume name=medplum-server-config,type=cloud-storage,bucket=medplum-server-config-stage01 \
--add-volume-mount volume=medplum-server-config,mount-path=/app/packages/server/config \
--add-cloudsql-instances pathcloud-stage01:us-west2:pathcloud-db-stage01 \
--command npm --args start,file:config/medplum.config.cloud.stage01.json

Setup PathCloud Load-balancer

Create new static IP address

gcloud compute addresses create pathcloud-stage01-lb-static-ip-address \
--network-tier=PREMIUM \
--ip-version=IPV4 \
--global

Create new GCP managed SSL Certificate

gcloud compute ssl-certificates create apps-stage01-ssl-v1 \
--description= \
--domains=fhir.stage01.gestaltcloud.com,apps.stage01.gestaltcloud.com,app.stage01.gestaltcloud.com,admin.stage01.gestaltcloud.com,pacs.stage01.gestaltcloud.com,ai.stage01.gestaltcloud.com,dicom.stage01.gestaltcloud.com,aistudio.stage01.gestaltcloud.com,viewer-public01.stage01.gestaltcloud.com \
--global

Create Network Endpoint Group connected to medplum-fhir-server in Cloud Run

TODO - create cli script for NEG

Create Backend for Medplum FHIR Server

TODO - create cli script for Backend Service

Initial setup and config of Load Balancer in project

New Loadbalancer in "Network Services"
Select following options:

  • Type of load balancer: Application Load Balancer
  • Public facing or internal: Public facing (external)
  • Global or single region deployment: Best for global workloads
  • Load balancer generation: Global external Application Load Balancer

Load Balancer Name: pathcloud-stage01-lb
Configure Frontend - http, https, configure static IP address, SSL Certificate

Setup Medplum Admin App in Cloud Run

  • NOTE: configure env file before build to use Vite mode (in order to pass specific env file to build command)

Build container

  • Run inside app-containers/app-admin

  • Build from medplum src docker build -f Dockerfile --build-arg APP_ENV_PATH=.env.pathcloud-stage01.cloudrun --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-admin:v4.0.2 ../../.

Test Container Locally

docker run --rm -p 3001:80 --name app-admin-container us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-admin:v4.0.2

Push image to Artifact Registry**

docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-admin:v4.0.2

Deploy container to Cloud Run

gcloud config set project pathcloud-stage01 &&
gcloud run deploy pathcloud-admin-app --image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-admin:v4.0.2 --port=80 --region=us-west2 --allow-unauthenticated --memory 4G

Load Balancer Setup

  • configure Backend and Network Endpoint Group (NEG)

Configure Load Balancer Route

defaultService: projects/pathcloud-stage01/global/backendServices/pathcloud-admin-app-backend name: path-matcher-2

Setup PathCloud App

Create new Medplum Project for environment

  • currently need to manually create in SuperAdmin project Project Name: PathCloud-Stage01 Project ID: 94525382-22d7-4169-811d-bea7b4c1de8d TODO - create medplum script for building new project

Build app-admin

cd apps/app-pathcloud
npm run clean
npx vite build --mode pathcloud-stage01.cloudrun

Build Container

cd container

docker build -f Dockerfile.apps --build-arg APP_DIST_DIR=../dist  --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-pathcloud:v1.4.3 ../.

Test Container Locally docker run --rm -p 3005:80 --name app-pathcloud-container-local us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-pathcloud:v1.4.3

Push container to Artifact Registry

docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-pathcloud:v1.4.3

Deploy container image to Cloud Run

gcloud config set project pathcloud-stage01 && \    
gcloud run deploy pathcloud-app --image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/app-pathcloud:v1.4.3 \
--region=us-west2 --port=80 --allow-unauthenticated \
--execution-environment=gen2 --min-instances=1 \
--memory 8G --cpu 2

Create Network Endpoint Group

TODO - create cli script for NEG

Create Backend Service

TODO - create cli script for Backend Service

Add domain to Google Auth

  • add app.stage01.gestaltcloud.com to "Authorized Javascript Origins" in GAuth "Credential".

Setup Viewers - Viewer-Clinical

Setup config file

  • be careful to use correct domain name for DICOM_PROXY TODO - modify viewer code to use config file from bucket, so don't need to rebuild container for each environment

Build container

cd app-containers/viewer-clinical

docker build -f Dockerfile.cloudrun --build-arg REACT_APP_CONFIG=gcp_pathcloud-stage01 --build-arg PUBLIC_URL=/viewer-clinical --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical .

Test Container Locally docker run --rm -p 3001:80 --name viewer-clinical-local us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical

Push container

docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical

Deploy container to Cloud Run

gcloud config set project pathcloud-stage01 && \
gcloud run deploy viewer-clinical --image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical --region=us-west2 --port=80 --allow-unauthenticated --memory 4G

Create Network Endpoint Group

TODO - create cli script for NEG

Create Backend Service

TODO - create cli script for Backend Service

Add routing rules to loadbalancer

  • /viewer-slim TODO - change path to "viewer-clinical"

Setup Viewers - Viewer-Clinical-Public (SLIM Public) (09/25/24)

Build Container

  • Run inside app-containers/viewer-clinical-public
    docker build -f Dockerfile.cloudrun
    --build-arg PUBLIC_URL=/
    --build-arg REACT_APP_CONFIG=gcp_pathcloud-stage01_public
    --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical-public .

Test Container Locally docker run --rm -p 3005:80 --name viewer-clinical-public_local us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical-public

Push image to Artifact Registry docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical-public

Deploy/Update Container in Cloud Run gcloud config set project pathcloud-stage01 &&
gcloud run deploy viewer-clinical-public
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-clinical-public --region=us-west2 --port=80 --allow-unauthenticated --memory 4G

Create Backend & Network Endpoint Group for viewer-clinical public container

Add route to load balancer

  • Route for: viewer-public01.stage01.gestaltcloud.com

Setup Viewers - OHIF

Add config file

  • This only needs to be done on initial project setup gcloud config set project pathcloud-stage01 && \ gcloud storage buckets create gs://viewer-ohif-stage01 --location=us-west2

Copy Config File to Google Bucket for Container Script gcloud config set project pathcloud-stage01 && \
gcloud storage cp ./repo-viewer-ohif/platform/app/public/config/gcp_pathcloud-stage01.js gs://viewer-ohif-stage01/app-config.js

Build container

cd app-containers/viewer-ohif

docker build -f Dockerfile.cloudrun.scriptbuild --build-arg PUBLIC_URL=/viewer/ \
--build-arg REACT_APP_CONFIG_FILE=gcp_pathcloud-stage01.js \
--tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif .

Test Container Locally

docker run --rm -p 3001:80 --name viewer-ohif-container-cloud us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif

Push container to Artifact Registry

docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif

Deploy container to Cloud Run

gcloud config set project pathcloud-stage01 && \ gcloud run deploy pathcloud-viewer-ohif
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif:latest
--execution-environment gen2
--allow-unauthenticated --memory 4G
--port=80 --region=us-west2
--add-volume name=bucket-viewer-ohif,type=cloud-storage,bucket=viewer-ohif-stage01
--add-volume-mount volume=bucket-viewer-ohif,mount-path=/mnt/viewer-config

Create Network Endpoint Group

TODO - create cli script for NEG

Create Backend Service

TODO - create cli script for Backend Service

Add routing rules to loadbalancer

  • /viewer
  • copy's match rule from gcp-pathology-poc1 lb

Setup Image Transformation Svc

Create Service Account:

Name: dicom-web-client@[projectid].iam.gserviceaccount.com Name: transform-svc-files-stage01

Roles: Healthcare DICOM Editor - roles/healthcare.dicomEditor Healthcare DICOM Store Administrator Healthcare Service Agent IAP-secured Web App User Logs Writer Service Account OpenID Connect Identity Token Creator - roles/iam.serviceAccountOpenIdTokenCreator Service Account Token Creator - roles/iam.serviceAccountTokenCreator Storage Object Viewer - roles/storage.objectViewer

TODO - script creation of Service Account (gcloud command started below)

gcloud iam service-accounts create dicom-web-client \
--description="Account to connect DICOMWeb Client to Standard DICOM Store" \
--display-name="dicom-web-client"
gcloud projects add-iam-policy-binding pathcloud-stage01 \
--member="serviceAccount:dicom-web-client@pathcloud-stage01.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountOpenIdTokenCreator" \
--role="roles/iam.serviceAccountTokenCreator" \
--role="roles/storage.objectViewer"

Export key for Service Account

  • manually create create key and save to file for import into secret

Create Temp-File Bucket

gcloud storage buckets create gs://transform-svc-files-stage01 --location=us-west2

Create secret for service account key

echo -n "my super secret data" | gcloud secrets create slide-dicom-store-gcp-pathology-service \
--replication-policy="automatic" \
--data-file=-

  • manually import dicom-web-client service account key into secret

Deploy ImageTransformSvc Container

gcloud beta run deploy image-transform-svc --image us-west2-docker.pkg.dev/gcp-pathology-poc1/image-transform-svc/image-transform-svc@sha256:8a1718df7e22200c3a0f9fa8a13af25f26ac3e050fbbcab8ed62ded04064d97f \
--region=us-west2 --allow-unauthenticated \
--memory 16G --cpu=4 --execution-environment=gen2 --cpu-boost \
--min-instances=0 --max-instances=10 --timeout=3600 \
--port 8080 --network default --subnet default --use-http2 \
--add-volume name=transform-svc-files,type=cloud-storage,bucket=transform-svc-files-stage01 \
--add-volume-mount volume=transform-svc-files,mount-path=/transform-svc-files \
--update-secrets=/secrets/slide-dicom-store-gcp-pathology-service.json=slide-dicom-store-gcp-pathology-service:latest \
--set-env-vars "SERVICE_ACCOUNT_KEY_LOCATION=/secrets/slide-dicom-store-gcp-pathology-service.json" \
--set-env-vars "URL=https://healthcare.googleapis.com/v1/projects/pathcloud-stage01/locations/us-west2/datasets/dicom-pathology/dicomStores/slide-dicom-store/dicomWeb" \
--set-env-vars "USE_CELERY=False" \
--set-env-vars "CELERY_BROKER_URL=redis://10.191.97.11:6379/9" \
--set-env-vars "CELERY_RESULT_BACKEND=redis://10.191.97.11:6379/9" \
--set-env-vars "KEEP_FILES_PATH=/transform-svc-files/temp_files" \
--set-env-vars "SFTPFileInputPath=/transform-svc-files/temp_files/Input" \
--set-env-vars "SFTPFileOutputPath=/transform-svc-files/temp_files/Output"

Test ImageTransformSvc

  • upload image through public enpoint to confirm working. TODO - add subdomain DNS entry and LB route for ImageTransformSvc so it is standard with project

Add transform.stage01.gestaltcloud.com to LoadBalancer

  1. Add dns entry to CloudDNS
  2. Create new SSL Certificate with Entry
  3. Create lb backend and NEG.
  4. Add routes to lb for dns entry
  5. Update PathCloud config with new URL

Deploy DICOMCast Pub/Sub Webhook (webhook-dicom-pubsub)

Create service account

gcloud iam service-accounts create pub-sub-cloud-run-webhook
--description="Monitoring PubSub with Webhook in CloudRun"
--display-name="pub-sub-cloud-run-webhook"

Deploy Container to Cloud Run

Deploy/Update Container in Cloud Run

  • IMPORTANT - concurrency must be set to 1 or will create multiple records
gcloud run deploy pathcloud-webhook-dicom-pubsub \
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/webhook-dicom-pubsub-medplum \
--allow-unauthenticated \
--concurrency=1 \
--min-instances=0 --max-instances=1 --execution-environment gen1 \
--region=us-west2 --port=8080 \
--set-env-vars "MEDPLUM_CLIENT_ID=6b3eb908-bcc1-48b9-a5aa-e05601e8d4b4" \
--set-env-vars "MEDPLUM_CLIENT_SECRET=957b5ae18386825bcc274ee328fefb80da320a06959dcefa56aab9be94236093" \
--set-env-vars "MEDPLUM_HOST_BOT_URL=fhir.stage01.gestaltcloud.com/v1.0/fhir/R4/Bot" \
--set-env-vars "MEDPLUM_BOT_ID=0e41eccc-518e-4242-9015-fb1913ce8c98"

Connect invoker service account to webhook cloudrun service gcloud run services add-iam-policy-binding pathcloud-webhook-dicom-pubsub
--member=serviceAccount:pub-sub-cloud-run-webhook@pathcloud-stage01.iam.gserviceaccount.com
--role=roles/run.invoker

Allow Pub/Sub to create auth tokens in project gcloud projects add-iam-policy-binding pathcloud-stage01
--member=serviceAccount:service-392114647566@gcp-sa-pubsub.iam.gserviceaccount.com
--role=roles/iam.serviceAccountTokenCreator

Create Pub/Sub subscription gcloud pubsub subscriptions create pathflow-transformation-dicom-store-topic --topic transformation-dicom-store-topic
--ack-deadline=10
--push-endpoint=https://pathcloud-webhook-dicom-pubsub-dn2iy4mhwa-wl.a.run.app/
--push-auth-service-account=pub-sub-cloud-run-webhook@pathcloud-stage01.iam.gserviceaccount.com

  • Send Pub/Sub test message gcloud pubsub topics publish transformation-dicom-store-topic --message "Runner to Medplum Hook"

gcloud pubsub topics publish transformation-dicom-store-topic --message "projects/gcp-pathology-poc1/locations/us-west2/datasets/dicom-pathology/dicomStores/slide-dicom-store/dicomWeb/studies/1.2.826.0.1.3680043.8.498.10197367396119578384812517504838792671/series/1.2.826.0.1.3680043.8.498.46958186722544799280797962364607502027/instances/1.2.826.0.1.3680043.8.498.76104310942462206423602528873584291653"

Configure Medplum FHIR Server with New Project (08/16/2024)

Rebuild Lookups in SuperAdmin for new DB

  • Structure Definitions
  • Search Parameters
  • Value Sets

Configure Medplum-FHIR DB & Project

Create New Project & Setup Project Level Params

Project Name: PathCloud-STAGE01 [ ] Copy Project level settings from Pathcloud-Template project (POC1)

Add Admin Users

  1. Add admin users
  2. Add Project Memberships
  3. Add "Site" to enable Google Auth
    1. Configure Name, Domain, Google Client ID, Google Secret, Recaptcha (for password reset)

Create PathCloud Public 01 Organization

  • used for storing public image pointers
  • Used for app env variable: PATHCLOUD_ORG_PUBLIC_01

Add ClientApplication for Default Auth

  1. Create ClientAppliction for Admin Access to Project
  2. Name: ClientApplication-DefaultAuthCredentials-PathCloud-STAGE01
  • Used for authentication by Postman, in Worklist, etc.
  • Used app env variable: PATHCLOUD_PROJECT_CLIENT_ID & PATHCLOUD_PROJECT_CLIENT_SECRET

Enable Google Admin SDK API in Medplum FHIR Server

Configure GCP Service Account Key for Admin SDK APIto Secret in Medplum FHIR Server

  1. Enable 'Admin SDK API' in project (If not already enabled)
  2. Create service account: medplum-bot (if not already created)
  3. Create new JSON key for service account "medplum-bot" in Cloud Console IAM/Service Accounts
  4. Stringify JSON from export key file (can use JSON.stringify(body of key file) in a Test Bot on Medplum)
  5. Create new secret at Medplum project (pathcloud-stage01)
  • Name: gcp-svc-account-key_medplum-bot
  • Type: string
  • Important: contents of key file must stringified
  1. Retrieve secret in bot using: const GCP_SVC_ACCT_KEY = event.secrets['gcp-svc-account-key_medplum-bot'].valueString;

Setup Google Group for IAP-secured User to Access DICOM Store

  1. Open IAM & Admin / Groups in Cloud Console
  2. Open 'gestaltdiagnostics' enterprise to manage Groups
  3. Click "Create"
  4. Enter NAME: gs-pathcloud-poc1-users and enter EMAIL: gs-pathcloud-poc1-users@gestaltdiagnostics.com
  5. Save new group
  6. Open IAM & Admin for specific project (eg gcp-pathology-poc1)
  7. Grant access for new role. Enter new group email address
  8. Add role: IAP-secured Web App User
  9. Add "medplum-bot..." service account to new "Group" as "Owner" role. (Perform in Cloud Console IAM&Admin/Groups page under gestaltdiagnostics.com).
  • Needs to be performed in Cloud Console, not in Workspace Admin Console
  1. Assign "medplum-bot" service account to Account / Admin Roles: Groups Admin in Workspace Admin Console
  1. Confirm that new user group is assinged "IAP-secured Web App User" role in IAP dicom-proxy-service.

Add GCP User Group for IAP as Secret in Medplum Fhir Server

  1. Create secret name: gcp-project-iap-user-group; Use value of GCP Group name (eg, gs-pathcloud-stage01-users@gestaltdiagnostics.com)

Setup Endpoints for DICOMCast Bot (added 9/24/24)

  1. Configure Endpoint-DICOM-QIDO-GCP-Private
  • Name is used to look-up resource in DICOMCast Bot so need exact string match.
  • URL = URL for Private DICOM Proxy
  1. Configure Endpoint-DICOM-QIDO-GCP-Public01
  • Name is used to look-up resource in DICOMCast Bot so need exact string match.
  • Connection Type: DICOM QIDO-RS
  • URL = URL for Public DICOM Proxy

Setup DICOMCast Bot & ClientApplication

  1. Configure Bot: Bot-CreateDICOMCast-STAGE01 in "PathCloud-[instance]"
  2. Change "Runtime Version" to vmcontext
  3. Configure service account and service account key for GCP BigQuery support in Bot
  • https://cloud.google.com/docs/authentication/provide-credentials-adc#on-prem
  • Using service account key and make it available to ADC
    1. Create Service Account
    gcloud iam service-accounts create medplumb-bot \
    --description="PathCloud Medplum Bot Access to GCP Services - BigQuery, etc" \
    --display-name="medplumb-bot"
    1. Add roles to Service Account
    gcloud projects add-iam-policy-binding pathcloud-stage01 \
    --member=serviceAccount:medplumb-bot@pathcloud-stage01.iam.gserviceaccount.com \
    --role="roles/owner"
    1. Export key for Service Account
    • manually create create key and save to file for import into secret
    1. Copy key file to /apps/bots/gcp-dicomstore-webhook-bigquery/svc_account_key folder in SRC code
    2. Rename file to: const keyFilePath = key_medplum-bot_pathcloud-stage01-2f3bfd0d513b.json
  1. Redeploy server-medplum-fhir container with new keyFile
  • see instructions at: Build and Push medplum-fhir container image (Code) above
  1. Create new BigQuery view for botcode retrieval:
  • Name: pathcloud_slide-dicom-store-metadata_groupedView Code:
SELECT 
*
FROM (SELECT MAX (LastUpdated) max_dt FROM `pathcloud-stage01.dicom_pathology.slide-dicom-store-metadata`
GROUP BY StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID),
`pathcloud-stage01.dicom_pathology.slide-dicom-store-metadata`
  1. Modify botcode-org-dicomcast.ts with changes for new Project and BigQuery

  2. const keyFilePath to use new Service Account key (no longer need to do as of 09/24/24)

  3. const gcp_projectId - use new project - pathcloud-stage01 (used in SQL query to BigQuery)

  4. Copy code from botcode-org-dicomcast.ts in apps/bots/src/gcp-dicomstore-webhook-bigquery and past into Bot editor

  5. Save & Deploy

  6. Configure ClientApplication for Bot - DICOMCast

  • Used to give access to Bot in webhook (ClientID and ClientSecret)
    Name: ClientApplication-Bot-CreateDICOMCast-STAGE01
  1. Configure ProjectMembership linking Bot and ClientApplication
  • Important step! Webhook will not work if not completed
    1. Project [Stage01] needs to have SuperAdmin rights to create ProjectMembership (082124)
    • If project doesn't have SuperAdmin rights when creating ProjectMembership get a Forbidden error.
    • Need to create ProjectMembership logged into PathCloud project (eg Stage01)
    1. Required settings must be provided
    2. User - ClientApplication - needs to be ClientApp created for Bot
    3. Profile - Bot -needs to "profile" of Bot
    4. Do not need to give "Admin" permission

Redeploy DICOMCast Webhook Container with Updated ENV Vars

  1. add MEDPLUM_CLIENT_ID, MEDPLUM_CLIENT_SECRET, MEDPLUM_BOT_ID to container deploy
  2. Redeploy Container
gcloud run deploy pathcloud-webhook-dicom-pubsub \
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/webhook-dicom-pubsub-medplum \
--no-allow-unauthenticated \
--max-instances=1 --execution-environment gen1 \
--region=us-west2 --port=8080 \
--set-env-vars "MEDPLUM_CLIENT_ID=6b3eb908-bcc1-48b9-a5aa-e05601e8d4b4" \
--set-env-vars "MEDPLUM_CLIENT_SECRET=957b5ae18386825bcc274ee328fefb80da320a06959dcefa56aab9be94236093" \
--set-env-vars "MEDPLUM_HOST_BOT_URL=fhir.stage01.gestaltcloud.com/v1.0/fhir/R4/Bot" \
--set-env-vars "MEDPLUM_BOT_ID=0e41eccc-518e-4242-9015-fb1913ce8c98"

Test DICOMCast Container is receiving messges from Webhook

gcloud pubsub topics publish transformation-dicom-store-topic --message "projects/gcp-pathology-poc1/locations/us-west2/datasets/dicom-pathology/dicomStores/slide-dicom-store/dicomWeb/studies/1.2.826.0.1.3680043.8.498.10197367396119578384812517504838792671/series/1.2.826.0.1.3680043.8.498.46958186722544799280797962364607502027/instances/1.2.826.0.1.3680043.8.498.76104310942462206423602528873584291653"

Setup Create-New-Practitioner Routine/Bot (AccessPolicy's, Bot, Subscription)

Create AccessPolicy - AccessPolicy-Practitioner-PrivateOrg-STAGE01

  • Create AccessPolicy in new Project (STAGE01)
  • Copy "resource" field from template AccessPolicy (Practitioner-DefaultOrg/Patient/DiagReport Access Policy)

Create AccessPolicy - AccessPolicy-Practitioner-PublicOrg-PUBLIC01-STAGE01

  • Create Access in new Project (STAGE01) - Name: AccessPolicy-Practitioner-PublicOrg-PUBLIC01-STAGE01
  • Copy "resource" field from template AccessPolicy (Practitioner-PublicOrg-Pathcloud-POC1-PUBLIC01)
  • Add "Compartment" in Edit screen: Organization/Org-PathCloud-STAGE01-PUBLIC-01
  • Modify resourceType: Patient, "compartment"
    reference "compartment": {
"reference": "Organization/c12305eb-ca0c-46a5-badd-7fa35e022d17",
"display": "Org-PathCloud-PUBLIC01-STAGE01"
}

TODO - add lookup for "compartment" in Patient resource filter; so don't have to remember to modify

Configure ClientApplication for auth of Bot - Create New Practitioner

  • used in "Bot - Create New Practitioner..."" code to authenticate for initial user creation
  • Create in Project: Super Admin (Very Important!)
  • Name: ClientApplication-Bot-CreateNewPractitioner-STAGE01
  • ProjectMembership: can use default membership created with ClientApplication - change to Admin

Configure Bot for "New Practitioner" creation routine

  • Create in Project: PathCloud-[envrionment]
  • Name: Bot-CreateNewPractitioner_STAGE01
  • Runtime Version: vmcontext
  1. ProjectMembership - can use default membership in PathCloud-[env] - change to Admin
  2. Copy Bot code from /apps/bots/src/create-practitioner-subs-user/botcode.ts

Configure constants and project specific parameters in Bot code

  1. client_id and client_secret constants with values from "ClientApplication-Bot-CreateNewPractitioner-STAGE01"
  2. pathcloud_project_id = PathCloud Project ID - 94525382-22d7-4169-811d-bea7b4c1de8d
  3. userconfiguration_id - confirm GUID is available in current project DB (SuperAdmin)
  4. publicOrg - change name of Public Org
  5. userOrgAccessPolicy - change name to AccessPolicy in project (AccessPolicy-Practitioner-PrivateOrg-STAGE01)
  6. publicOrgAccessPolicy - change name to AccessPolicy in project (AccessPolicy-Practitioner-PublicOrg-PUBLIC01-STAGE01)
  7. Click "Save" and "Deploy" in Bot Editor

Configure Subscription for New Practitioner creation routine

  • Create in Project: PathCloud-[environment]
  • Users will be created with ProjectId supplied in .env; The Subscription must be in the same project.
  • Status: Active
  • Reason: Bot-CreateNewPractitioner_STAGE01
  • Criteria: User
  • Channel:
    "channel": {
"type": "rest-hook",
"endpoint": "Bot/[BOT ID]",
"payload": "application/fhir+json"
}
  • Add "extension" to restrict subscriptions to only "CREATE" messages
  "extension": [
{
"url": "https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction",
"valueCode": "create"
}
],

Test NewPractitioner Creation Routine

  • If using an existing test user (eg family1@insynthion.com ) need to delete the following resources (in this order): ProjectMembership, Organization, PractitionerRole, Practitioner, User,
  • Register new user with Google Auth

Setup DeletePractitioner Routine/Bot (083024)

  • used to delete all resources associated with User/Practitioner
  • currently triggered by deleting User in Super Admin project

Configure Bot-DeletePractitioner-STAGE01

  • code in apps/bots/src/delete-practitioner folder
  • make sure required secrets (from CreateNewPractitioner bot) are configured
  1. Create new Bot named: `Bot-DeletePractitioner-[Env]
  2. Copy bot code (botcode_delete-practitioner.ts) using Bot-Editor
  3. Test bot

Configure Subscription for DeletePractitioner Bot

  • Create in Project: PathCloud-[environment]
  • Users will be deleted in Admin App; The Subscription must be in the same project.
  • Status: Active
  • Reason: Bot-DeletePractitioner_STAGE01
  • Criteria: User
  • Channel:
    "channel": {
"type": "rest-hook",
"endpoint": "Bot/[BOT ID]",
"payload": "application/fhir+json"
}
  • Add "extension" to restrict subscriptions to only "DELETE" messages
  "extension": [
{
"url": "https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction",
"valueCode": "delete"
}
],

Test DeletePractitioner Bot

Test NewUser/DICOMCast/ImageTransformation Functionality

  • Login with new user
  • Upload new SVS image
    Issue #1 - Using incorrect TRANSFORM_SVC_URL
    • Redploy App with correct URL Issue #2 - Practitioner Create Subscription wasn't working
    • Cause - User resource already existed (make sure to delete User)
    • Fix - Delete User resource Issue #3 - DICOMCast creating duplicate FHIR resources
    • Cause - concurrency of DICOMCast Webhook container was NOT set to 1
    • Fix - Set concurrency to 1

Setup DeleteImagingStudyResources Routine/Bot (092424)

  • used to delete all resources associated with ImagingStudy
  • currently triggered by deleting ImagingStudy in StudyWorklist

Configure Bot-DeleteImagingStudyResources-STAGE01

  • code in apps/bots/src/delete-imagingstudy-resources folder
  • make sure required secrets (from CreateNewPractitioner bot) are configured
  1. Create new Bot named: `Bot-DeleteImagingStudyResources-[Env]
  2. Add Identifier
  • used to identify DeleteImagingStudy bot during Delete routine in ImagingStudyEditDetails.tsx code
  {
system: 'http://gestaltcloud.com/identifiers/bots',
value: 'delete-imaging-study-resources'
}
  1. Copy bot code (botcode_delete-imagingstudy-resources.ts) using Bot-Editor
  2. Test bot

Add Sample Data in new Project (Giuseppe Gestalt)

Delete old Giuseppe Gestalt data if present

  • In Postman use SuperAdmin ClientApplication
  • Run expunge everything command: /fhir/R4/Patient/330e6a27-53f5-4010-96e3-d32f4138536f/$expunge?everything=true

Modify resource bundle other files

File: patient_Giuseppe-Gestalt_everything.json

  1. Change: Organization/c12305eb-ca0c-46a5-badd-7fa35e022d17 to current Public01 Org
  2. Change "display": Org-PathCloud-STAGE01-PUBLIC-01
  3. Change PathCloud project id: 94525382-22d7-4169-811d-bea7b4c1de8d
  4. Change fullUrl: https://app.poc1.gestaltcloud.com

File: data-observations_Giuseppe_Gestalt.json

  1. Change: Patient/3621dec2-ad36-4e5d-b916-c6d35f7b7f63

Upload WSI to Giuseppe Gestalt patient

Add Avatar Photo

Filename: giuseppe_gestalt_avatar.jpg

Orthanc PACS Server

Build & Deploy Container

Build Container

  • Run inside app-containers/orthanc
    docker build -f Dockerfile.cloudrun --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/orthanc .

Push image to Artifact Registry docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/orthanc

Create Buckets gcloud storage buckets create gs://orthanc-storage-stage01 --location=us-west2

Deploy/Update Container in Cloud Run

  • deploy container
  • NOTE: Using orthanc.pathcloud-stage01.yaml file for configuration
  • Note: using dicom-web-client service acct key file configured as GCP secret for ImageTransformationSvc above
  • Using Postgres plugin with CloudSQL for index
  • NOTE: Do not need to manually upload config file, it is attached to env-vars-file in deploy script
gcloud config set project pathcloud-stage01 && \
gcloud beta run deploy orthanc-pacs \
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/orthanc \
--execution-environment gen2 --allow-unauthenticated \
--region=us-west2 --port=8042 \
--add-cloudsql-instances pathcloud-stage01:us-west2:pathcloud-db-stage01 \
--add-volume name=bucket-orthanc-storage-stage01,type=cloud-storage,bucket=orthanc-storage-stage01 \
--add-volume-mount volume=bucket-orthanc-storage-stage01,mount-path=/bucket-orthanc-storage-stage01 \
--update-secrets=/secrets/dicom-web-client_pathcloud-stage01_service-key.json=slide-dicom-store-gcp-pathology-service:latest \
--env-vars-file=orthanc.pathcloud-stage01.yaml

Configure loadbalancer for pacs.stage01.gestaltcloud.com

Create Network Endpoint Group

TODO - create cli script for NEG

Create Backend Service

TODO - create cli script for Backend Service

Add routing rules to loadbalancer

  • pacs.stage01.gestaltcloud.com
  • copy's match rule from gcp-pathology-poc1 lb

Create SSL Certificate with Required Domains

  1. Create Classic SSL Cert
gcloud beta compute ssl-certificates create apps-stage01-ssl-v2 --project=pathcloud-stage01 --global --description=Certificate\ for\ Stage01\ Loadbalancer\ SSL --domains=fhir.stage01.gestaltcloud.com,apps.stage01.gestaltcloud.com,app.stage01.gestaltcloud.com,admin.stage01.gestaltcloud.com,pacs.stage01.gestaltcloud.com,ai.stage01.gestaltcloud.com,dicom.stage01.gestaltcloud.com,aistudio.stage01.gestaltcloud.com,viewer-public01.stage01.gestaltcloud.com,transform.stage01.gestaltcloud.com,docs.stage01.gestaltcloud.com,docs.gestaltcloud.com,app.gestaltcloud.com

  1. Add new cert into Load Balancer Frontend SSL

Build & Deploy PathCloud Docs Site

Build & Deployment Steps

Build App-PathCloud

  • cd apps/docs Run: npm run build

Build Container

  • Run inside apps/docs/container

  • Build from DIST folder docker build -f Dockerfile --build-arg APP_DIST_DIR=../build --tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/app-docs ../.

Push image to Artifact Registry docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/app-docs

Test Container Locally docker run --rm -p 3010:80 --name app-docs-container us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/app-docs

Deploy/Update Container in Cloud Run gcloud config set project pathcloud-stage01 &&
gcloud run deploy pathcloud-docs --image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/app-docs
--port=80 --region=us-west2 --allow-unauthenticated --memory 2G

Setup LoadBalancer and Routing

Create Network Endpoint Group

TODO - create cli script for NEG

Create Backend Service

TODO - create cli script for Backend Service

Add routing rules to loadbalancer

Add Questionnaire to FHIR Server

  1. Copy from ./app-pathcloud/config/Questionnaire-practitioner-onboard-consent.json Note: Confirm that identifier is present, since this is how Questionnaire is retrieved by application.

Configure Pub/Sub and Service Accounts for Bot Creation of Topic/Subscription (10/30/24)

Update notes for Org-based DICOM Store Version (01/15/25)

Backup Medplum FHIR DB

  1. Export "medplum-stage01" via SQL scripts to "apps-config" bucket
  2. Setup new database for back-up purpose "medplum-stage01-backup"
  3. Import SQL scripts into backup dp
  4. Test that application will run on backup db
  5. Change FHIR service config back to production db.

Upgrade Medplum FHIR Container to same version as POC1

  • upgrade Medplum FHIR to v3.2.19

Additional "Secrets" Parameters in Project Config

  • add additional "secrets" in Project gcp-project - [string] = "pathcloud-stage01" gcp-dicom-location-dataset01 - [string] = "locations/us-west2/datasets/dicom-pathology" gcp-dicom-store-project-default - [string] = "slide-dicom-store" gcp-dicom-store-public01 - [string] = "slide-dicom-store-public" gcp-dicom-proxy-private01 - [string] = "https://dicom-gcp.stage01.gestaltcloud.com/tile" gcp-dicom-proxy-public01 - [string] = "https://dicom.stage01.gestaltcloud.com/public01" ClientApplication-CreateNewPractitioner_ID - [string] = "a21ab5f4-4801-4dc8-b287-964d465593ab" ClientApplication-CreateNewPractitioner_SECRET - [string] = " a0571848a82dfc30b8b66397558cf0661b73906e86fb1851c32829d974b85b79"

Setup Public & Private Endpoints

Public Endpoint

  • configure "Identifier"
  • configure "Address"
  • configure "Payload" Generic Private Endpoint
  • configure "Identifier"
  • configure "Address"
  • configure "Payload"

Setup New Pub/Sub Topic & Subscription for dicomStore

  • Topic: projects/pathcloud-stage01/subscriptions/pathcloud-org-dicomstore-topic-sub
  • Subscription
    • Modify subscription to "Push"
    • Add Endpoint URL for Webhook Container
    • Enable Authentication

Configure Bots in Medplum

  • make sure "identifier" field is added for CreateOrganizationDicomstore & DeleteImagingStudy

Deploy New Bot Updates

  1. Copy .env.pathcloud.stage01 TO .env
  2. Copy medplum.config.stage01.json TO medplum.config.json
  3. Login to medplum CLI with stage01 ClientApplication (see README in apps/bots directory)
  4. Run script in apps/bots directory: ./scripts/deploy-bots-stage01.sh

Testing Updated Bots and Config with Multi-DICOMStore Features

Issues

  1. AccessPolicies - CreatePractitioner bot had hardcoded names for the Access Policies in the POC1 project. Solution: Change the AccessPolicy name to a generic name common to each project. When setting up new project need to make sure use same naming convention.
  2. Endpoint Public - make sure Endpoint has a "Managing Organization" field set for Public Org
  3. Viewer-Clinical - didn't have updated STAGE01 config file

GCP Dev-Viewer With Script Deploy 03/13/25

Create Bucket gcloud storage buckets create gs://viewer-ohif-dev-stage01 --location=us-west2

Copy file to be used in script gcloud storage cp ./repo-viewer-ohif/platform/app/public/config/gcp_pathcloud-stage01_dev-viewer.js gs://viewer-ohif-dev-stage01/app-config.js

docker build -f Dockerfile.cloudrun.dev-viewer --build-arg PUBLIC_URL=/dev-viewer/
--build-arg REACT_APP_CONFIG_FILE=gcp_pathcloud-stage01_dev-viewer.js
--build-arg REPO_BRANCH="tags/releases-dev/v3.9.1-beta.021125"
--tag us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif-dev:Feature2502 .

Test docker run --rm -p 3001:80 --name viewer-ohif-dev-local
-v ./repo-viewer-ohif/platform/app/public/config/gcp_pathcloud-stage01_dev-viewer.js:/mnt/viewer-config/app-config.js
us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif-dev:Feature2502

Push docker push us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif-dev:Feature2502

Deploy gcloud config set project pathcloud-stage01 &&
gcloud run deploy viewer-ohif-dev
--image us-west2-docker.pkg.dev/gcp-pathology-poc1/pathcloud/stage01/viewer-ohif-dev:Feature2502
--execution-environment gen2
--allow-unauthenticated --memory 2G
--region=us-west2 --port=80
--add-volume name=bucket-viewer-ohif-dev-stage01,type=cloud-storage,bucket=viewer-ohif-dev-stage01
--add-volume-mount volume=bucket-viewer-ohif-dev-stage01,mount-path=/mnt/viewer-config