Sunday, November 19, 2023

DICOM Image Caching with Redis

 Summary

This post covers a demonstration of the usage of Redis for caching DICOM imagery.  I use a Jupyter Notebook to step through loading and searching DICOM images in a Redis Enterprise environment.

Architecture




Redis Enterprise Environment

Screen-shot below of the resulting environment in Docker.



Sample DICOM Image

I use a portion of sample images included with the Pydicom lib.  Below is an example:


Code Snippets

Data Load

The code below loops through the Pydicom-included DICOM files.  Those that contain the meta-data that is going to be subsequently used for some search scenarios are broken up into 5 KB chunks and stored as Redis Strings.  Those chunks and the meta-data are then saved to a Redis JSON object.  The chunks' Redis key names are stored as an array in that JSON object.

def load_chunks(key, file, chunk_size):
i = 0
chunk_keys = []
with open(file, 'rb') as infile:
while chunk := infile.read(chunk_size):
chunk_key = f'chunk:{key}:{i}'
client.set(chunk_key, chunk)
chunk_keys.append(chunk_key)
i += 1
return chunk_keys
count = 0
pydicom.config.settings.reading_validation_mode = pydicom.config.RAISE
for file in pydicom.data.get_testdata_files():
try:
ds = pydicom.dcmread(file)
key = f'file:{os.path.basename(file)}'
image_name = os.path.basename(file)
protocol_name = re.sub(r'\s+', ' ', ds.ProtocolName)
patient_sex = ds.PatientSex
study_date = ds.StudyDate
manufacturer = ds.Manufacturer.upper()
chunk_keys = load_chunks(key, file, CHUNK_SIZE)
client.json().set(key, '$', {
'imageName': image_name,
'protocolName': protocol_name,
'patientSex': patient_sex,
'studyDate': study_date,
'manufacturer': manufacturer,
'chunks': chunk_keys
})
count += 1
except:
pass
print(f'Files loaded: {count}')

Search Scenario 1

This code retrieves all the byte chunks for a DICOM image where the Redis key is known.  Strictly, speaking this isn't a 'search'.  I'm simply performing a JSON GET for a key name.

file_name = 'JPGExtended.dcm'
t1 = perf_counter()
results = client.json().get(f'file:{file_name}', '$.chunks')
total_bytes = get_bytes(results[0])
t2 = perf_counter()
print(f'Exec time: {round((t2-t1)*1000,2)} ms')
print(f'Bytes Retrieved: {len(total_bytes)}')

Search Scenario 2

The code below demonstrates how to put together a Redis Search on the image meta-data.  In this case, we're looking for a DICOM image with a protocolName of 194 and studyDate in 2019.

query = Query('@protocolName:194 @studyDate:{2019*}')\
.return_field('$.chunks', as_field='chunks')\
.return_field('$.imageName', as_field='imageName')
t1 = perf_counter()
result = client.ft('dicom_idx').search(query)
total_bytes = bytearray()
if len(result.docs) > 0:
total_bytes = get_bytes(json.loads(result.docs[0].chunks))
t2 = perf_counter()
print(f'Exec time: {round((t2-t1)*1000,2)} ms')
print(f'Image name: {result.docs[0].imageName}')
print(f'Bytes Retrieved: {len(total_bytes)}')

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.