pathology/dicom_proxy/dicom_proxy_flags.py (426 lines of code) (raw):
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Flags used in Digital Pathology Proxy Server."""
import os
import sys
from absl import flags
from pathology.dicom_proxy import proxy_const
from pathology.shared_libs.flags import secret_flag_utils
from pathology.shared_libs.iap_auth_lib import auth
# Digital Pathology Proxy Server Flask Config.
# Not a flag, requires initialization prior to flag init.
PROXY_SERVER_URL_PATH_PREFIX = secret_flag_utils.get_secret_or_env(
'URL_PATH_PREFIX', '/tile'
)
GUNICORN_WORKERS_FLG = flags.DEFINE_integer(
'gunicorn_workers',
int(
secret_flag_utils.get_secret_or_env(
'GUNICORN_WORKERS', int(os.cpu_count() * 3.4)
)
),
'Number of processes GUnicorn should launch',
)
GUNICORN_THREADS_FLG = flags.DEFINE_integer(
'gunicorn_threads',
int(secret_flag_utils.get_secret_or_env('GUNICORN_THREADS', 5)),
'Number of threads each GUnicorn processes should launch',
)
API_PORT_FLG = flags.DEFINE_integer('port', 8080, 'port to listen on')
ORIGINS_FLG = flags.DEFINE_multi_string(
'origins',
secret_flag_utils.get_secret_or_env(
'ORIGINS', 'http://localhost:5432'
), # Default host:port for local DPAS web host.
'Sites to allow requests from for CORS.',
)
HEALTH_CHECK_LOG_INTERVAL_FLG = flags.DEFINE_integer(
'health_check_log_interval',
int(secret_flag_utils.get_secret_or_env('HEALTH_CHECK_LOG_INTERVAL', 100)),
'Interval in seconds to log healthcheck.',
)
KEYWORDS_TO_MASK_FROM_CONNECTION_HEADER_LOG_FLG = flags.DEFINE_list(
'keywords_to_mask_from_connection_header_log',
secret_flag_utils.get_secret_or_env(
'KEYWORDS_TO_MASK_FROM_CONNECTION_HEADER_LOG',
','.join([
proxy_const.HeaderKeywords.IAP_JWT_HEADER,
proxy_const.HeaderKeywords.COOKIE,
proxy_const.HeaderKeywords.AUTH_HEADER_KEY,
]),
).split(','),
'List of keywords to strip from header logs.',
)
DEFAULT_DICOM_STORE_API_VERSION = secret_flag_utils.get_secret_or_env(
'DEFAULT_DICOM_STORE_API_VERSION', 'v1'
)
# IAP Authentication.
ENABLE_APPLICATION_DEFAULT_CREDENTIALS_FLG = flags.DEFINE_boolean(
'enable_app_default_credentials',
secret_flag_utils.get_bool_secret_or_env(
'ENABLE_APPLICATION_DEFAULT_CREDENTIALS'
),
(
'Enables application default credentials used to debug should '
' be False except for testing.'
),
)
VALIDATE_IAP_FLG = auth.VALIDATE_IAP_FLG
# ICC Profile Color Conversion.
THIRD_PARTY_ICC_PROFILE_DICRECTORY_FLG = flags.DEFINE_string(
'third_party_icc_profile_directory',
secret_flag_utils.get_secret_or_env(
'THIRD_PARTY_ICC_PROFILE_DIRECTORY', ''
),
'Directory that contains third party icc profiles.',
)
DISABLE_ICC_PROFILE_CORRECTION_FLG = flags.DEFINE_boolean(
'disable_icc_profile_color_correction',
secret_flag_utils.get_bool_secret_or_env('DISABLE_ICC_PROFILE_CORRECTION'),
'Disable iccprofile color correction.',
)
# ICC Profile Cache Config.
# Maximum number of icc_profile transforms cached in each process. Number
# should be small, total size per machine = cache size * process number
# Using env variable directly to allow it to be used in decorator.
MAX_SIZE_ICC_PROFILE_TRANSFORM_PROCESS_CACHE_FLG = int(
secret_flag_utils.get_secret_or_env(
'MAX_SIZE_ICC_PROFILE_TRANSFORM_THREAD_CACHE', 5
)
)
ICC_PROFILE_REDIS_CACHE_TTL_FLG = flags.DEFINE_integer(
'icc_profile_redis_cache_ttl',
int(
secret_flag_utils.get_secret_or_env('ICC_PROFILE_REDIS_CACHE_TTL', '-1')
),
'TTL (sec) of ICC_PROFILE redis cache. Value < 0 disables ttl.',
)
# Metadata Cache Config.
DISABLE_DICOM_METADATA_CACHING_FLG = flags.DEFINE_boolean(
'disable_dicom_metadata_caching',
secret_flag_utils.get_bool_secret_or_env('DISABLE_DICOM_METADATA_CACHING'),
'Disable DICOM metadata caching.',
)
USER_LEVEL_METADATA_TTL_FLG = flags.DEFINE_integer(
'user_level_metadata_cache_ttl_sec',
int(
secret_flag_utils.get_secret_or_env(
'USER_LEVEL_METADATA_TTL_SEC', '600'
)
),
'TTL (sec) of DICOM metadata cache.',
)
# Bearer Token Email Caching
SIZE_OF_BEARER_TOKEN_EMAIL_CACHE_FLG = flags.DEFINE_integer(
'size_of_bearer_token_email_cache',
int(
secret_flag_utils.get_secret_or_env(
'BEARER_TOKEN_EMAIL_CACHE_SIZE', 1000
)
),
'Maximum size of bearer token to email cache.',
)
# DICOM Store Instance Downloads.
DICOM_INSTANCE_DOWNLOAD_STREAMING_CHUNKSIZE_FLG = flags.DEFINE_integer(
'streaming_chunksize',
max(
int(
secret_flag_utils.get_secret_or_env('STREAMING_CHUNKSIZE', '512000')
),
1,
),
'Size in bytes of streaming chunks.',
)
# Client side image caching.
CACHE_CONTROL_TTL_RENDERED_FRAMES_FLG = flags.DEFINE_integer(
'cache_control_ttl_rendered_frames',
int(
secret_flag_utils.get_secret_or_env(
'CACHE_CONTROL_TTL_RENDERED_FRAMES', 3600
)
),
'Client side cache control control ttl for rendered single frames.',
)
# Image Generation.
JPEG_ENCODER_FLG = flags.DEFINE_string(
'jpeg_encoder_flg',
secret_flag_utils.get_secret_or_env('JPEG_ENCODER', 'CV2'),
'CV2 = OpenCV (Default); PIL=PIL.Image',
)
MAX_PARALLEL_FRAME_DOWNLOADS_FLG = flags.DEFINE_integer(
'max_thread_pool_size',
int(secret_flag_utils.get_secret_or_env('MAX_THREAD_POOL_SIZE', 20)),
'Max size of frame retrieval thread pool.',
)
MAX_NUMBER_OF_FRAMES_PER_REQUEST_FLG = flags.DEFINE_integer(
'max_number_of_frame_per_request',
min(
int(
secret_flag_utils.get_secret_or_env(
'MAX_NUMBER_OF_FRAMES_PER_REQUEST', 100
)
),
int(
secret_flag_utils.get_secret_or_env(
'MAX_MINI_BATCH_FRAME_REQUEST_SIZE', 500
)
),
),
'Maximum number of frames which can be requested at once.',
)
MAX_MINI_BATCH_FRAME_REQUEST_SIZE_FLG = flags.DEFINE_integer(
'max_mini_batch_frame_request_size',
int(
secret_flag_utils.get_secret_or_env(
'MAX_MINI_BATCH_FRAME_REQUEST_SIZE', 500
)
),
'Maximum number of frames which can be requested in a mini-batch.',
)
# Preemptive Frame Cache Loader.
DISABLE_PREEMPTIVE_INSTANCE_FRAME_CACHE_FLG = flags.DEFINE_boolean(
'disable_preemptive_instance_frame_cache',
secret_flag_utils.get_bool_secret_or_env(
'DISABLE_PREEMPTIVE_INSTANCE_FRAME_CACHE'
),
'Disable preemptive instance frame cache.',
)
PREEMPTIVE_INSTANCE_CACHE_MAX_INSTANCE_FRAME_NUMBER_FLG = flags.DEFINE_integer(
'preemptive_instance_cache_max_instance_frame_number',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_INSTANCE_CACHE_MAX_INSTANCE_FRAME_NUMBER', 500000
)
),
'Maximum number of frames in an instance.',
)
PREEMPTIVE_INSTANCE_CACHE_BLOCK_PIXEL_DIM_FLG = flags.DEFINE_integer(
'preemptive_instance_cache_block_pixel_dim',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_INSTANCE_CACHE_PIXEL_DIM', 8000
)
),
'Dimension in pixels of large cache block premptively loaded to cache.',
)
PREEMPTIVE_DISPLAY_CACHE_MIN_INSTANCE_FRAME_NUMBER_FLG = flags.DEFINE_integer(
'preemptive_display_cache_min_instance_frame_number',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_DISPLAY_CACHE_MIN_INSTANCE_FRAME_NUMBER', 10000
)
),
'Minimum number of frames in an instance required for to use display'
' cache.',
)
PREEMPTIVE_DISPLAY_CACHE_PIXEL_DIM_FLG = flags.DEFINE_integer(
'preemptive_display_cache_pixel_dim',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_DISPLAY_CACHE_PIXEL_DIM', 3000
)
),
'Number of pixels loaded on x and y dim by display cache.',
)
ENABLE_PREEMPTIVE_WHOLEINSTANCE_CACHING_FLG = flags.DEFINE_boolean(
'enable_preemptive_wholeinstance_caching',
secret_flag_utils.get_bool_secret_or_env(
'ENABLE_PREEMPTIVE_WHOLEINSTANCE_CACHING', True
),
'If True, enables caching whole instance at once. If False, cache frame'
' block size block of frames in instance at once.',
)
MAX_WHOLE_INSTANCE_CACHING_CPU_LOAD_FLG = flags.DEFINE_integer(
'max_whole_instance_caching_cpu_load',
int(
secret_flag_utils.get_secret_or_env(
'MAX_WHOLE_INSTANCE_CACHING_CPU_LOAD', 80
)
),
'Maximum CPU load to use whole instance caching.',
)
MAX_WHOLE_INSTANCE_CACHING_MEMORY_PRECENT_LOAD_FLG = flags.DEFINE_integer(
'max_whole_instance_caching_memory_precent_load',
int(
secret_flag_utils.get_secret_or_env(
'MAX_WHOLE_INSTANCE_CACHING_MEMORY_PRECENT_LOAD', 70
)
),
'Maximum precentage of memory load for whole instance caching.',
)
PREEMPTIVE_INSTANCE_CACHE_MAX_THREADS_FLG = flags.DEFINE_integer(
'preemptive_instance_cache_max_threads',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_INSTANCE_CACHE_MAX_THREADS', 1
)
),
(
'Maximum number of instance frame cache ingestion threads to run per'
' GUnicorn worker (process). Server hosted on C2N30 should default to'
' ~26 workers enabling by default 26 parallel cache loads. Keeping'
' number of cache loads per worker small (e.g. 1) will improve server'
' performance by limiting total thread load per-worker. Total number'
' of threads per worker = gunicorn worker threads + cache loading'
' threads + additional.'
),
)
PREEMPTIVE_INSTANCE_CACHE_MIN_INSTANCE_FRAME_NUMBER_FLG = flags.DEFINE_integer(
'preemptive_instance_cache_min_instance_frame_number',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_INSTANCE_CACHE_MIN_INSTANCE_FRAME_NUMBER', 10
)
),
(
'Min number of frames in an instance required to start preemptive frame'
' caching.'
),
)
PREEMPTIVE_WHOLEINSTANCE_RECACHING_TTL_FLG = flags.DEFINE_integer(
'preemptive_wholeinstance_recaching_ttl',
int(
secret_flag_utils.get_secret_or_env(
'PREEMPTIVE_WHOLEINSTANCE_RECACHING_TTL', 300
)
),
'Time in seconds before an instance frame caching request can be repeated.',
)
FRAME_CACHE_TTL_FLG = flags.DEFINE_integer(
'frame_cache_ttl',
int(secret_flag_utils.get_secret_or_env('FRAME_CACHE_TTL', 3600)),
'Time in seconds frames are stored in redis cache. Set to value < 0 to '
'disable ttl and keep frames in cache until removed via other mechanisms '
'(e.g. LRU).',
)
# Redis Cross Process Cache Flags.
REDIS_CACHE_HOST_IP_FLG = flags.DEFINE_string(
'redis_cache_host_ip',
secret_flag_utils.get_secret_or_env('REDIS_CACHE_HOST_IP', '127.0.0.1'),
'Redis cache host IP',
)
REDIS_CACHE_HOST_PORT_FLG = flags.DEFINE_integer(
'redis_cache_host_port',
int(secret_flag_utils.get_secret_or_env('REDIS_CACHE_HOST_PORT', '6379')),
'Redis cache host port',
)
REDIS_CACHE_DB_FLG = flags.DEFINE_integer(
'redis_cache_host_db',
int(secret_flag_utils.get_secret_or_env('REDIS_CACHE_HOST_DB', '0')),
'Redis cache host database',
)
REDIS_CACHE_MAXMEMORY_FLG = flags.DEFINE_integer(
'redis_cache_maxmemory',
int(secret_flag_utils.get_secret_or_env('REDIS_CACHE_MAXMEMORY', -1)),
'Max memory in gigabytes to allocate to local redis instance.',
)
REDIS_USERNAME_FLG = flags.DEFINE_string(
'redis_auth_username',
secret_flag_utils.get_secret_or_env('REDIS_USERNAME', None),
'Redis auth username.',
)
REDIS_AUTH_PASSWORD_FLG = flags.DEFINE_string(
'redis_auth_password',
secret_flag_utils.get_secret_or_env('REDIS_AUTH_PASSWORD', None),
'Redis auth password.',
)
REDIS_TLS_CERTIFICATE_AUTHORITY_GCS_URI_FLG = flags.DEFINE_string(
'redis_tls_certificate_authority_gcs_uri',
secret_flag_utils.get_secret_or_env(
'REDIS_TLS_CERTIFICATE_AUTHORITY_GCS_URI', ''
),
'GS uri for blob in buket which holds Redis Certificate Authority for TLS.',
)
REDIS_MIN_CACHE_FLUSH_INTERVAL_FLG = flags.DEFINE_integer(
'redis_min_cache_flush_interval',
int(
secret_flag_utils.get_secret_or_env(
'REDIS_MIN_CACHE_FLUSH_INTERVAL', 600
)
),
'Minimum time in sec between Redis cache flush operations.',
)
DISABLE_REDIS_INSTANCE_PROCESS_CACHE_IN_DEBUG_FLG = flags.DEFINE_boolean(
'disable_redis_instance_process_cache_in_debug',
bool('UNITTEST_ON_FORGE' in os.environ or 'unittest' in sys.modules),
'Automatically disable redis instance cache when debugging.',
)
# Cached Sparse Dicom Per frame functional groups metadata ttl
SPARSE_DICOM_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE_METADATA_CACHE_TTL_FLG = flags.DEFINE_integer(
'sparse_dicom_per_frame_functional_groups_sequence_metadata_cache_ttl',
int(
secret_flag_utils.get_secret_or_env(
'SPARSE_DICOM_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE_METADATA_CACHE_TTL',
3600,
)
),
'Time to live (sec) for cached sparse dicom per frame functional group'
' sequence metadata.',
)
# Annotations Flags.
DICOM_ANNOTATIONS_STORE_ALLOW_LIST = flags.DEFINE_list(
'dicom_annotations_store_allow_list',
secret_flag_utils.get_secret_or_env(
'DICOM_ANNOTATIONS_STORE_ALLOW_LIST', ''
).split(','),
'List of allowed base dicom url addresses that can be written to for'
' annotations. projects/.../locations/.../dicomWeb',
)
ENABLE_ANNOTATIONS_ENDPOINT_FLG = flags.DEFINE_boolean(
'enable_annotations_endpoint',
secret_flag_utils.get_bool_secret_or_env('ENABLE_ANNOTATIONS_ENDPOINT'),
'Enable annotations storage and deletion endpoint.',
)
ENABLE_DEBUG_FUNCTION_TIMING_FLG = flags.DEFINE_boolean(
'enable_debug_function_timing',
secret_flag_utils.get_bool_secret_or_env('ENABLE_DEBUG_FUNCTION_TIMING'),
'Enable logging of timing for decorated functions and methods.',
)
# ENABLE_AUGMENTED_STUDY_SEARCH_FLG adds the following tags to the study
# tag search response:
# https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.3.3.html
# Modalities in Study (0008,0061)
# Retrieve URL (0008,1190)
# Number of Study Related Series (0020,1206)
# Number of Study Related Instances (0020,1208)
# Instance Availability (0008,0056)
ENABLE_AUGMENTED_STUDY_SEARCH_FLG = flags.DEFINE_boolean(
'enable_augmented_study_search',
secret_flag_utils.get_bool_secret_or_env('ENABLE_AUGMENTED_STUDY_SEARCH'),
'Enable the return of augmented metadata for study search.',
)
# ENABLE_AUGMENTED_SERIES_SEARCH_FLG adds the following tags to the series
# tag search response:
# https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.3.3.2.html
# Retrieve URL (0008,1190)
# Number of Study Related Instances (0020,1208)
ENABLE_AUGMENTED_SERIES_SEARCH_FLG = flags.DEFINE_boolean(
'enable_augmented_series_search',
secret_flag_utils.get_bool_secret_or_env('ENABLE_AUGMENTED_SERIES_SEARCH'),
'Enable the return of augmented metadata for series search.',
)
# ENABLE_AUGMENTED_INSTANCE_SEARCH_FLG adds the following tags to the instance
# tag search response:
# https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.3.3.3.html
# Retrieve URL (0008,1190)
# Instance Availability (0008,0056)
ENABLE_AUGMENTED_INSTANCE_SEARCH_FLG = flags.DEFINE_boolean(
'enable_augmented_instance_search',
secret_flag_utils.get_bool_secret_or_env(
'ENABLE_AUGMENTED_INSTANCE_SEARCH', True
),
'Enable the return of augmented metadata for instance search.',
)
MAX_AUGMENTED_METADATA_DOWNLOAD_THREAD_COUNT_FLG = flags.DEFINE_integer(
'max_augmented_metadata_thread_count',
int(
secret_flag_utils.get_secret_or_env(
'MAX_AUGMENTED_METADATA_DOWNLOAD_THREAD_COUNT', '4'
)
),
'Maximum number of threads used to download instance tag for binary tag'
' metadata augmentation.',
)
MAX_AUGMENTED_METADATA_DOWNLOAD_SIZE_FLG = flags.DEFINE_integer(
'max_augmented_metadata_download_size',
int(
secret_flag_utils.get_secret_or_env(
'MAX_AUGMENTED_METADATA_DOWNLOAD_SIZE', str(3 * pow(2, 30))
)
),
'Maximum size of augmented metadata requests.',
)
DEFAULT_ANNOTATOR_INSTITUTION_FLG = flags.DEFINE_string(
'default_annotator_institution',
secret_flag_utils.get_secret_or_env(
'DEFAULT_ANNOTATOR_INSTITUTION', 'default_institution'
),
'Default annotator institution.',
)
# Bulk DATA URI
BULK_DATA_URI_PROTOCOL_FLG = flags.DEFINE_string(
'bulkdata_uri_protocol',
secret_flag_utils.get_secret_or_env('BULK_DATA_URI_PROTOCOL', 'https'),
'Protocal to return in bulkdata responses.',
)
PROXY_DICOM_STORE_BULK_DATA_FLG = flags.DEFINE_boolean(
'proxy_dicom_store_bulk_data',
secret_flag_utils.get_bool_secret_or_env('PROXY_DICOM_STORE_BULK_DATA'),
'If true rewrites DICOM store metadata to proxy bulkdata requests through'
' proxy.',
)
BULK_DATA_PROXY_URL_FLG = flags.DEFINE_string(
'bulk_data_proxy_url',
secret_flag_utils.get_secret_or_env('BULK_DATA_PROXY_URL', ''),
'If defined sets base url for proxy bulk data responses',
)
ENABLE_ANNOTATION_BULKDATA_METADATA_PATCH = flags.DEFINE_boolean(
'enable_annotation_bulkdata_metadata_patch',
secret_flag_utils.get_bool_secret_or_env(
'ENABLE_ANNOTATION_BULKDATA_METADATA_PATCH'
),
'If true downloads dicom metadata for annotations and merges it with store'
' returned metadata to fix store bug which results in store not returning'
' short binary tags.',
)