Skip to content

Commit

Permalink
First attempt afib1
Browse files Browse the repository at this point in the history
  • Loading branch information
kerrinpine committed Jun 24, 2024
1 parent cdeb69b commit aa1cbb3
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 0 deletions.
2 changes: 2 additions & 0 deletions recipes/afib1/OpenRecon.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM stebo85/bet4
LABEL "com.siemens-healthineers.magneticresonance.openrecon.metadata:1.1.0"="ewogICJnZW5lcmFsIjogewogICAgIm5hbWUiOiB7CiAgICAgICJlbiI6ICJkZWZhY2luZyIKICAgIH0sCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAidmVuZG9yIjogIm5ldXJvZGVzayIsCiAgICAiaW5mb3JtYXRpb24iOiB7CiAgICAgICJlbiI6ICJEZWZhY2luZyBwaXBlbGluZS4iCiAgICB9LAogICAgImlkIjogImRlZmFjaW5nIiwKICAgICJyZWd1bGF0b3J5X2luZm9ybWF0aW9uIjogewogICAgICAiZGV2aWNlX3RyYWRlX25hbWUiOiAiZGVmYWNpbmciLAogICAgICAicHJvZHVjdGlvbl9pZGVudGlmaWVyIjogIjEuMC4wIiwKICAgICAgIm1hbnVmYWN0dXJlcl9hZGRyZXNzIjogIkVybGFuZ2VuLCBHZXJtYW55IiwKICAgICAgIm1hZGVfaW4iOiAiREUiLAogICAgICAibWFudWZhY3R1cmVfZGF0ZSI6ICIyMDIzLzAyLzE0IiwKICAgICAgIm1hdGVyaWFsX251bWJlciI6ICJkZWZhY2luZ18yLjAuMCIsCiAgICAgICJndGluIjogIjAwODYwMDAwMTcxMjEyIiwKICAgICAgInVkaSI6ICIoMDEpMDA4NjAwMDAxNzEyMTIoMjEpMS4zLjAiLAogICAgICAic2FmZXR5X2FkdmljZXMiOiAiIiwKICAgICAgInNwZWNpYWxfb3BlcmF0aW5nX2luc3RydWN0aW9ucyI6ICJPcGVuIFJlY29uIERlZmFjaW5nIHBpcGVsaW5lIiwKICAgICAgImFkZGl0aW9uYWxfcmVsZXZhbnRfaW5mb3JtYXRpb24iOiAiIgogICAgfQogIH0sCiAgInJlY29uc3RydWN0aW9uIjogewogICAgInRyYW5zZmVyX3Byb3RvY29sIjogewogICAgICAicHJvdG9jb2wiOiAiSVNNUk1SRCIsCiAgICAgICJ2ZXJzaW9uIjogIjEuNC4xIgogICAgfSwKICAgICJwb3J0IjogOTAwMiwKICAgICJlbWl0dGVyIjogImltYWdlIiwKICAgICJpbmplY3RvciI6ICJpbWFnZSIsCiAgICAiY2FuX3Byb2Nlc3NfYWRqdXN0bWVudF9kYXRhIjogZmFsc2UsCiAgICAiY2FuX3VzZV9ncHUiOiBmYWxzZSwKICAgICJtaW5fY291bnRfcmVxdWlyZWRfZ3B1cyI6IDAsCiAgICAibWluX3JlcXVpcmVkX2dwdV9tZW1vcnkiOiAyMDQ4LAogICAgIm1pbl9yZXF1aXJlZF9tZW1vcnkiOiA0MDk2LAogICAgIm1pbl9jb3VudF9yZXF1aXJlZF9jcHVfY29yZXMiOiAxLAogICAgImNvbnRlbnRfcXVhbGlmaWNhdGlvbl90eXBlIjogIlBST0RVQ1QiCiAgfSwKICAicGFyYW1ldGVycyI6IFsKICAgIHsKICAgICAgImlkIjogImNvbmZpZyIsCiAgICAgICJsYWJlbCI6IHsKICAgICAgICAiZW4iOiAiY29uZmlnIgogICAgICB9LAogICAgICAidHlwZSI6ICJjaG9pY2UiLAogICAgICAidmFsdWVzIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICJpbnZlcnRjb250cmFzdCIsCiAgICAgICAgICAibmFtZSI6IHsKICAgICAgICAgICAgImVuIjogImludmVydGNvbnRyYXN0IgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgImlkIjogInJnYiIsCiAgICAgICAgICAibmFtZSI6IHsKICAgICAgICAgICAgImVuIjogInJnYiIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJpZCI6ICJzaW1wbGVmZnQiLAogICAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAgICJlbiI6ICJzaW1wbGVmZnQiCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICBdLAogICAgICAiZGVmYXVsdCI6ICJpbnZlcnRjb250cmFzdCIsCiAgICAgICJpbmZvcm1hdGlvbiI6IHsKICAgICAgICAiZW4iOiAiRGVmaW5lIHRoZSBjb25maWcgdG8gYmUgZXhlY3V0ZWQgYnkgTVJEIHNlcnZlciIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImlkIjogImN1c3RvbWNvbmZpZyIsCiAgICAgICJsYWJlbCI6IHsKICAgICAgICAiZW4iOiAiQ3VzdG9tIENvbmZpZyIKICAgICAgfSwKICAgICAgInR5cGUiOiAic3RyaW5nIiwKICAgICAgImRlZmF1bHQiOiAiIiwKICAgICAgImluZm9ybWF0aW9uIjogewogICAgICAgICJlbiI6ICJDdXN0b20gY29uZmlnIGZpbGUgbm90IGxpc3RlZCBpbiBkcm9wLWRvd24gbWVudSIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImlkIjogIm9wdGlvbnMiLAogICAgICAibGFiZWwiOiB7CiAgICAgICAgImVuIjogIm9wdGlvbnMiCiAgICAgIH0sCiAgICAgICJ0eXBlIjogImNob2ljZSIsCiAgICAgICJ2YWx1ZXMiOiBbCiAgICAgICAgewogICAgICAgICAgImlkIjogIm5vbmUiLAogICAgICAgICAgIm5hbWUiOiB7CiAgICAgICAgICAgICJlbiI6ICJub25lIgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgImlkIjogInJvaSIsCiAgICAgICAgICAibmFtZSI6IHsKICAgICAgICAgICAgImVuIjogInJvaSIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICJpZCI6ICJjb2xvcm1hcCIsCiAgICAgICAgICAibmFtZSI6IHsKICAgICAgICAgICAgImVuIjogImNvbG9ybWFwIgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgXSwKICAgICAgImRlZmF1bHQiOiAibm9uZSIsCiAgICAgICJpbmZvcm1hdGlvbiI6IHsKICAgICAgICAiZW4iOiAiRGVmaW5lIGFkZGl0aW9uYWwgb3B0aW9ucyIKICAgICAgfQogICAgfSwKICAgIHsKICAgICAgImlkIjogImRvdWJsZSIsCiAgICAgICJsYWJlbCI6IHsKICAgICAgICAiZW4iOiAiZG91YmxlIgogICAgICB9LAogICAgICAidHlwZSI6ICJkb3VibGUiLAogICAgICAibWluaW11bSI6IDAsCiAgICAgICJtYXhpbXVtIjogMTAwLAogICAgICAiZGVmYXVsdCI6IDEuMCwKICAgICAgImluZm9ybWF0aW9uIjogewogICAgICAgICJlbiI6ICJBIGRvdWJsZSBwYXJhbWV0ZXIiCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJpZCI6ICJmcmVldGV4dCIsCiAgICAgICJsYWJlbCI6IHsKICAgICAgICAiZW4iOiAiZnJlZXRleHQiCiAgICAgIH0sCiAgICAgICJ0eXBlIjogInN0cmluZyIsCiAgICAgICJkZWZhdWx0IjogIiIsCiAgICAgICJpbmZvcm1hdGlvbiI6IHsKICAgICAgICAiZW4iOiAiRnJlZSB0ZXh0IGRhdGEiCiAgICAgIH0KICAgIH0KICBdCn0="
105 changes: 105 additions & 0 deletions recipes/afib1/OpenReconLabel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"general": {
"name": { "en": "afib1" },
"version": "1.0.0",
"vendor": "neurodesk",
"information": { "en": "afib1 pipeline." },
"id": "afib1",
"regulatory_information": {
"device_trade_name":"afib1",
"production_identifier":"1.0.0",
"manufacturer_address":"Erlangen, Germany",
"made_in":"DE",
"manufacture_date":"2023/02/14",
"material_number":"afib1_2.0.0",
"gtin":"00860000171212",
"udi":"(01)00860000171212(21)1.3.0",
"safety_advices":"",
"special_operating_instructions":"Open Recon afib1 pipeline",
"additional_relevant_information":""
}
},
"reconstruction": {
"transfer_protocol": {
"protocol": "ISMRMRD",
"version": "1.4.1"
},
"port": 9002,
"emitter": "image",
"injector": "image",
"can_process_adjustment_data": false,
"can_use_gpu": false,
"min_count_required_gpus": 0,
"min_required_gpu_memory": 2048,
"min_required_memory": 4096,
"min_count_required_cpu_cores": 1,
"content_qualification_type": "PRODUCT"
},
"parameters": [
{
"id": "config",
"label": { "en": "config" },
"type": "choice",
"values": [
{
"id": "invertcontrast",
"name": { "en": "invertcontrast" }
},
{
"id": "rgb",
"name": { "en": "rgb" }
},
{
"id": "simplefft",
"name": { "en": "simplefft" }
}
],
"default": "invertcontrast",
"information": { "en": "Define the config to be executed by MRD server" }
},
{
"id": "customconfig",
"label": { "en":"Custom Config" },
"type": "string",
"default": "",
"information": { "en": "Custom config file not listed in drop-down menu" }
},
{
"id": "options",
"label": { "en": "options" },
"type": "choice",
"values": [
{
"id": "none",
"name": { "en": "none" }
},
{
"id": "roi",
"name": { "en": "roi" }
},
{
"id": "colormap",
"name": { "en": "colormap" }
}
],
"default": "none",
"information": { "en": "Define additional options" }
},
{
"id": "double",
"label": { "en": "double" },
"type": "double",
"minimum": 0,
"maximum": 100,
"default": 1.0,
"information": { "en": "A double parameter" }
},
{
"id": "freetext",
"label": { "en":"freetext" },
"type": "string",
"default": "",
"information": { "en": "Free text data" }
}
]
}
Binary file not shown.
1 change: 1 addition & 0 deletions recipes/afib1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Readme will be converted to PDF
Binary file added recipes/afib1/README.pdf
Binary file not shown.
116 changes: 116 additions & 0 deletions recipes/afib1/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@


import json
import jsonschema
import base64
import os

# Validate JSON file against OpenRecon schema and write Dockerfile
jsonFilePath = 'OpenReconLabel.json'
schemaFilePath = '../OpenReconSchema_1.1.0.json'
dockerfilePath = 'OpenRecon.dockerfile'
baseDockerImage = 'kpinecbs/afib1'

def validateJson(jsonFilePath, schemaFilePath):
try:
# Load the JSON data from the file
with open(jsonFilePath, 'r') as jsonFile:
jsonData = json.load(jsonFile)

# Load the JSON schema from the file
with open(schemaFilePath, 'r') as schemaFile:
schemaData = json.load(schemaFile)

# Create a JSON Schema validator
validator = jsonschema.Draft7Validator(schemaData)

# Validate the JSON data against the schema
errors = list(validator.iter_errors(jsonData))

if not errors:
print("JSON is valid against the schema.")
return True
else:
print("JSON is not valid against the schema. Errors:")
for error in errors:
print(error)
return False

except Exception as e:
print(f"An error occurred: {e}")

# Write Dockerfile
if validateJson(jsonFilePath, schemaFilePath):
with open(jsonFilePath, 'r') as jsonFile:
jsonData = json.load(jsonFile)
jsonString = json.dumps(jsonData, indent=2)
encodedJson = base64.b64encode(jsonString.encode('utf-8')).decode('utf-8')

with open(dockerfilePath, 'w') as file:
labelName = 'com.siemens-healthineers.magneticresonance.openrecon.metadata:1.1.0'
labelStr = 'LABEL "' + labelName + '"="' + encodedJson + '"'

file.writelines('FROM ' + baseDockerImage)
file.writelines('\n' + labelStr)
print('Wrote Dockerfile:', os.path.abspath(dockerfilePath))
else:
raise Exception('Not writing Dockerfile because JSON is not valid')

# Build Docker image, save to a .tar file, and package into a .zip file for OpenRecon
# The documentation must be a valid PDF!
# README.pdf is autogenerated from README.md, but if it should be overwritten with a dedicated docs.pdf thats's possible
if os.path.isfile('docs.pdf'):
docsFile = 'docs.pdf'
else:
docsFile = 'README.pdf'

# Filename must match information contained in the JSON
version = jsonData['general']['version']
vendor = jsonData['general']['vendor']
name = jsonData['general']['name']['en']

# Check documentation file exists
if not os.path.isfile(docsFile):
raise Exception('Could not find documentation file: ' + docsFile)

# Check 7-zip exists
zipExe = '/usr/bin/7z'

if not os.path.isfile(zipExe):
raise Exception('Could not find 7-Zip executable: ' + zipExe + '\nPlease download and install 7-Zip')

dockerImagename = ('OpenRecon_' + vendor + '_' + name + ':' + 'V' + version).lower()
baseFilename = 'OpenRecon_' + vendor + '_' + name + '_V' + version

import subprocess
import shutil

try:
# Build Docker image docker buildx build --platform linux/amd64
print('Attempting to create Docker image with tag:', dockerImagename, '...')
output = subprocess.check_output(['docker', 'buildx', 'build', '--platform', 'linux/amd64', '--no-cache', '--progress=plain', '-t', dockerImagename, '-f', dockerfilePath, './'], stderr=subprocess.STDOUT)
print('Docker build output:\n' + output.decode('utf-8'))

# Save Docker image to a .tar file
print('Saving Docker image file with name:', baseFilename + '.tar', '...')
output = subprocess.check_output(['docker', 'save', '-o', baseFilename + '.tar', dockerImagename], stderr=subprocess.STDOUT)
print('Docker save output:\n' + output.decode('utf-8'))

# Copy documentation file with appropriate filename
print('Copying documentation to file with name:', baseFilename + '.pdf', '...')
try:
shutil.copy(docsFile, baseFilename + '.pdf')
print(f'File copied from {docsFile} to {baseFilename}.pdf')
except IOError as e:
print(f'An error occurred: {e}')

# Zip into a package
print('Packaging files into zip with name:', baseFilename + '.zip', '...')
output = subprocess.check_output([zipExe, 'a', '-tzip', '-mm=Deflate', baseFilename + '.zip', baseFilename + '.tar', baseFilename + '.pdf'], stderr=subprocess.STDOUT)
print('Zip packaging output:\n' + output.decode('utf-8'))

except subprocess.CalledProcessError as e:
# If the command returns a non-zero exit status, it will raise a CalledProcessError
print('Command failed with return code:', e.returncode)
print('Error output:\n' + e.output.decode('utf-8'))

0 comments on commit aa1cbb3

Please sign in to comment.