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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.