Bad file format :(

Hello!
I’m currently trying to create a simple python script that takes a list of .tif image files, creates .dcm files with those images as PixelData and uploads those to a local Orthanc server.

I keep having code 400 responses

Cannot parse an invalid DICOM file (size: 4211412 bytes)

Some people had the same issue and it was related to the size of the file but in my case, I have been able to post heavier but working dicom files (generated by the DragonFly software) with a python script, using the same method.

The problem has to lie in my .tif to .dcm conversion and to be honest, I have no idea how it is supposed to be done. Here is the simplified code that I have :

# Convert TIF to NumPy array
image = Image.open(tif_path)
image_array = np.array(image)

# Define output DICOM filename
dicom_filename = tif_path.replace(".tif", ".dcm")

# Create a proper DICOM dataset
file_meta = pydicom.dataset.FileMetaDataset()
file_meta.MediaStorageSOPClassUID = pydicom.uid.SecondaryCaptureImageStorage
file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian

# Create a DICOM dataset with the file metadata
ds = FileDataset(dicom_filename, {}, file_meta=file_meta, preamble=b"\0" * 128)

# Set essential DICOM tags
ds.StudyInstanceUID = pydicom.uid.generate_uid()
ds.SeriesInstanceUID = pydicom.uid.generate_uid()
ds.SOPInstanceUID = pydicom.uid.generate_uid()

# Set image properties
ds.PixelData = image_array.tobytes()

# Save as DICOM file
ds.save_as(dicom_filename)

# Upload to Orthanc
with open(dicom_filename, 'rb') as f:
    response = requests.post(
        f"{ORTHANC_URL}/instances",
        auth=(USERNAME, PASSWORD),
        headers={'Content-Type': 'application/dicom'},
        data=f
    )

Thanks for reading my post, let me know if you have any advice.

Hello

Even though your question is really not related to Orthanc, I have made a few changes and the following script successfully converts a tiff to a DCM that can be imported and previewed in Orthanc.

Haven’t done much and let o3-mini-high fill the missing info : your script did not specify what the pixel properties were.

YMMV with color tiffs, though (this tiff I am using has been converted from a DICOM one… I can give it to you if required)

import datetime
import numpy as np
from PIL import Image
import pydicom
from pydicom.dataset import FileDataset
import pydicom.uid
import requests

tif_path = '/home/bgo/test_dicom/MPRAGE-MR000000.tif'

ORTHANC_URL = 'http://localhost:10042'
USERNAME = 'orthanc'
PASSWORD = 'orthanc'

# Convert TIF to NumPy array
image = Image.open(tif_path)
image_array = np.array(image)

# Define output DICOM filename
dicom_filename = tif_path.replace(".tif", ".dcm")

# Create file meta information
file_meta = pydicom.dataset.FileMetaDataset()
file_meta.MediaStorageSOPClassUID = pydicom.uid.SecondaryCaptureImageStorage
file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
file_meta.ImplementationClassUID = pydicom.uid.PYDICOM_IMPLEMENTATION_UID
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian

# Create a DICOM dataset using the file meta
ds = FileDataset(dicom_filename, {}, file_meta=file_meta, preamble=b"\0" * 128)

# Set the creation date/time (DICOM requires specific date/time formats)
dt = datetime.datetime.now()
ds.ContentDate = dt.strftime('%Y%m%d')
ds.ContentTime = dt.strftime('%H%M%S')

# Set required DICOM tags for the study and series
ds.Modality = "OT"  # Other
ds.StudyInstanceUID = pydicom.uid.generate_uid()
ds.SeriesInstanceUID = pydicom.uid.generate_uid()
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID

# Set image dimensions and related tags
if image_array.ndim == 2:  # Grayscale image
    ds.Rows, ds.Columns = image_array.shape
    ds.SamplesPerPixel = 1
    ds.PhotometricInterpretation = "MONOCHROME2"
elif image_array.ndim == 3:  # Color image (e.g., RGB)
    ds.Rows, ds.Columns, samples = image_array.shape
    ds.SamplesPerPixel = samples
    ds.PhotometricInterpretation = "RGB"
    # Note: Ensure that the image data is in the expected RGB planar order if needed.
else:
    raise ValueError(f"Unsupported image shape: {image_array.shape}")

# Set pixel data attributes based on the image's data type
if image_array.dtype == np.uint8:
    ds.BitsAllocated = 8
    ds.BitsStored = 8
    ds.HighBit = 7
    ds.PixelRepresentation = 0
elif image_array.dtype == np.uint16:
    ds.BitsAllocated = 16
    ds.BitsStored = 16
    ds.HighBit = 15
    ds.PixelRepresentation = 0
else:
    raise ValueError(f"Unsupported image dtype: {image_array.dtype}")

# Set the PixelData element
ds.PixelData = image_array.tobytes()

# Save the DICOM file
ds.save_as(dicom_filename)

# Upload to Orthanc
with open(dicom_filename, 'rb') as f:
    response = requests.post(
        f"{ORTHANC_URL}/instances",
        auth=(USERNAME, PASSWORD),
        headers={'Content-Type': 'application/dicom'},
        data=f
    )
1 Like

Thanks for your answer but I just copy-pasted your code and added this at the end of it :

if response.status_code == 200:
    print(f"Successfully uploaded: {dicom_filename}")
else:
    print(f"Upload failed for {dicom_filename}: {response.status_code} - {response.text}")

And I get the same code 400 response :

400 - {
“Details” : “Cannot parse an invalid DICOM file (size: 4211492 bytes)”,
“HttpError” : “Bad Request”,
“HttpStatus” : 400,
“Message” : “Bad file format”,
“Method” : “POST”,
“OrthancError” : “Bad file format”,
“OrthancStatus” : 15,
“Uri” : “/instances”
}

Hi Oscar,

There’s something more to it, because the file imports correctly on my Orthanc instance.

I have put the source TIFF that I used and the resulting DCM in this Google drive share: 2025-03-10-tiff-to-dcm - Google Drive

Maybe try to import the .dcm to see if it imports correctly in your Orthanc instance and, if it is the case, maybe examine the different behavior between your version of the script and mine.

Maybe my version of the script does not work on your particular TIFF flavor?

For the record, here is the exact version of the script I used below, as well as the output of uv pip list (sorry for the noise… this is my sandbox venv)

Hope this helps

import datetime
import numpy as np
from PIL import Image
import pydicom
from pydicom.dataset import FileDataset
import pydicom.uid
import requests

#tif_path = '/home/bgo/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tiff-0.9.0/tests/benches/kodim07-lzw.tif'
tif_path = '/mnt/c/temp/MPRAGE-MR000000.tif'

ORTHANC_URL = 'http://localhost:8042'
USERNAME = 'orthanc'
PASSWORD = 'orthanc'

# Convert TIF to NumPy array
image = Image.open(tif_path)
image_array = np.array(image)

# Define output DICOM filename
dicom_filename = tif_path.replace(".tif", ".dcm")

# Create file meta information
file_meta = pydicom.dataset.FileMetaDataset()
file_meta.MediaStorageSOPClassUID = pydicom.uid.SecondaryCaptureImageStorage
file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
file_meta.ImplementationClassUID = pydicom.uid.PYDICOM_IMPLEMENTATION_UID
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian

# Create a DICOM dataset using the file meta
ds = FileDataset(dicom_filename, {}, file_meta=file_meta, preamble=b"\0" * 128)

# Set the creation date/time (DICOM requires specific date/time formats)
dt = datetime.datetime.now()
ds.ContentDate = dt.strftime('%Y%m%d')
ds.ContentTime = dt.strftime('%H%M%S')

# Set required DICOM tags for the study and series
ds.Modality = "OT"  # Other
ds.StudyInstanceUID = pydicom.uid.generate_uid()
ds.SeriesInstanceUID = pydicom.uid.generate_uid()
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID

# Set image dimensions and related tags
if image_array.ndim == 2:  # Grayscale image
    ds.Rows, ds.Columns = image_array.shape
    ds.SamplesPerPixel = 1
    ds.PhotometricInterpretation = "MONOCHROME2"
elif image_array.ndim == 3:  # Color image (e.g., RGB)
    ds.Rows, ds.Columns, samples = image_array.shape
    ds.SamplesPerPixel = samples
    ds.PhotometricInterpretation = "RGB"
    # Note: Ensure that the image data is in the expected RGB planar order if needed.
else:
    raise ValueError(f"Unsupported image shape: {image_array.shape}")

# Set pixel data attributes based on the image's data type
if image_array.dtype == np.uint8:
    ds.BitsAllocated = 8
    ds.BitsStored = 8
    ds.HighBit = 7
    ds.PixelRepresentation = 0
elif image_array.dtype == np.uint16:
    ds.BitsAllocated = 16
    ds.BitsStored = 16
    ds.HighBit = 15
    ds.PixelRepresentation = 0
else:
    raise ValueError(f"Unsupported image dtype: {image_array.dtype}")

# Set the PixelData element
ds.PixelData = image_array.tobytes()

# Save the DICOM file
ds.save_as(dicom_filename)

# Upload to Orthanc
with open(dicom_filename, 'rb') as f:
    response = requests.post(
        f"{ORTHANC_URL}/instances",
        auth=(USERNAME, PASSWORD),
        headers={'Content-Type': 'application/dicom'},
        data=f
    )
    print(response.status_code, response.text)
❯ uv pip list
Using Python 3.11.10 environment at: pyscripts/orthanc-scripts/.venv
Package            Version
------------------ ---------
aiohappyeyeballs   2.4.8
aiohttp            3.11.13
aiosignal          1.3.2
anyio              4.8.0
attrs              25.1.0
certifi            2025.1.31
charset-normalizer 3.4.1
frozenlist         1.5.0
h11                0.14.0
httpcore           1.0.7
httpx              0.28.1
idna               3.10
markdown-it-py     3.0.0
mdurl              0.1.2
multidict          6.1.0
numpy              2.2.3
pillow             11.1.0
pip                25.0.1
propcache          0.3.0
pydicom            3.0.1
pygments           2.19.1
requests           2.32.3
rich               13.9.4
setuptools         75.8.2
sniffio            1.3.1
typing-extensions  4.12.2
urllib3            2.3.0
wheel              0.45.1
yarl               1.18.3
1 Like