OHIF Study List Shows "0 Studies" Despite Valid DICOM JSON Data. How to fix that?

Thanks for your detailed question. The short answer is that OHIF’s DICOM JSON data source search implementation does not work well at all for OHIF’s study list. I believe the DICOM JSON data source is meant primarily/exclusively for the viewer.

This said there is nothing to stop you from changing the code in the link I sent to be something like this for your own purposes…

         const [key, value] = Object.entries(param)[0];

          let studies = [];
          if (key in mappings) {
            // todo: should fetch from dicomMetadataStore
            studies = findStudies(mappings[key], value);
          } else {
            // assume a study list query
            studies = _store.urls.map(metaData => metaData.studies.map(aStudy => aStudy)).flat();
          }

Hope this helps.

Hello @jbocce And thank you very much for your time.

But, I don’t think you fully understand the problem I’m currently experiencing.

The problem isn’t with the search function itself. It’s the Datatable that isn’t displaying at all in the Laravel view, showing0 Studies*” / “*No studies available”, as you can see in the screenshot.

This contrasts with the OHIF VIEWER page, which correctly lists the data in the DataTable, as you can see in the screenshot:

Yet:

  1. :white_check_mark: The DICOM JSON data is correctly formatted
  2. :white_check_mark: The data contains 1 study with 2 series and 5 instances
  3. :white_check_mark: OHIF successfully fetches and parses the JSON (confirmed in console logs)
  4. :white_check_mark: The same data works perfectly when loading a single study directly

Screenshot attached: Study List showing “Aucune étude disponible” (No studies available)

Environment

  • OHIF Viewer Version: v3.x (using advanced-dicomviewer build)
  • Data Source: DICOM JSON (@ohif/extension-default.dataSourcesModule.dicomjson)
  • Backend: Laravel PHP application
  • Browser: Chrome/Edge (latest)

Configuration

window.config = {
    routerBasename: '/insurance/medical-imaging/documents-list',
    showStudyList: true,
    extensions: [],
    modes: [],
    dataSources: [
        {
            namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
            sourceName: 'dicomjson',
            configuration: {
                friendlyName: 'Laravel DICOM JSON - Documents List',
                name: 'json',
            },
        }
    ],
    defaultDataSourceName: 'dicomjson',
};

Data Loading Approach

I’m using a fetch interceptor to provide the data:

CODE HTML-JAVASCRIPT:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/>
    <meta name="theme-color" content="#000000"/>
    <title>{{ __('insurance::lang.documents_list') }} - {{ config('app.name') }}</title>
    <link href="{{ asset('modules/insurance/advanced-dicomviewer/app.bundle.css') }}" rel="stylesheet">
</head>
<body>
    <noscript>{{ __('insurance::lang.javascript_required') }}</noscript>
    
    <div id="root"></div>

    <div class="back-btn-overlay">
        <button onclick="closeViewer()" class="btn-back">← {{ __('insurance::lang.back') }}</button>
    </div>

    <!-- Configuration & Scripts -->
    <script>
        // Set Public URL for webpack chunks
        window.PUBLIC_URL = '{{ asset("modules/insurance/advanced-dicomviewer") }}/';
        
        console.log('📄 Documents count:', {{ $documents->count() }});
        console.log('🌐 Current URL:', window.location.href);

        // Load embedded DICOM data directly
        const dicomData = {!! json_encode($dicomJsonData) !!};
        
        console.log('✅ DICOM data loaded from embedded script');
        console.log('📊 Studies count:', dicomData.studies ? dicomData.studies.length : 0);
        console.log('📋 Full data:', dicomData);
        
        // Store globally for OHIF and debugging
        window.dicomData = dicomData;
        
        // Create a fake URL for OHIF to "fetch" from
        const dataUrl = 'dicomjson://embedded-data.json';
        
        // Override OHIF's data fetching to use our embedded data
        // This intercepts the fetch call and returns our data directly
        const originalFetch = window.fetch;
        window.fetch = function(url, options) {
            console.log('🔍 Fetch intercepted:', url, typeof url);
            
            // Handle null or undefined URLs
            if (!url || url === 'null' || url === 'undefined') {
                console.log('⚠️ Invalid URL detected, returning embedded data');
                return Promise.resolve(new Response(JSON.stringify(dicomData), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                }));
            }
            
            // If OHIF is trying to fetch DICOM JSON data, return our embedded data
            if (typeof url === 'string' && (
                url.includes('dicomjson') || 
                url.includes('.json') || 
                url.startsWith('blob:') ||
                url.includes('embedded-data')
            )) {
                console.log('✅ Returning embedded DICOM data instead of fetching');
                console.log('📊 Data structure:', {
                    hasStudies: !!dicomData.studies,
                    studiesCount: dicomData.studies ? dicomData.studies.length : 0,
                    firstStudy: dicomData.studies && dicomData.studies[0] ? {
                        StudyInstanceUID: dicomData.studies[0].StudyInstanceUID,
                        PatientName: dicomData.studies[0].PatientName,
                        seriesCount: dicomData.studies[0].series ? dicomData.studies[0].series.length : 0,
                        firstSeries: dicomData.studies[0].series && dicomData.studies[0].series[0] ? {
                            SeriesInstanceUID: dicomData.studies[0].series[0].SeriesInstanceUID,
                            instancesCount: dicomData.studies[0].series[0].instances ? dicomData.studies[0].series[0].instances.length : 0
                        } : null
                    } : null
                });
                
                const response = new Response(JSON.stringify(dicomData), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                });
                
                // Log when response is consumed
                const originalJson = response.json.bind(response);
                response.json = async function() {
                    console.log('📥 OHIF is parsing the JSON response');
                    const data = await originalJson();
                    console.log('✅ JSON parsed successfully:', data);
                    return data;
                };
                
                return Promise.resolve(response);
            }
            
            // For all other requests, use the original fetch
            return originalFetch.apply(this, arguments);
        };
        
        console.log('✅ Fetch interceptor installed');
        
        // OHIF Configuration - Disable Study List and load study directly
        const studyUIDs = dicomData.studies.map(s => s.StudyInstanceUID).join(',');
        
        window.config = {
            routerBasename: '/insurance/medical-imaging/documents-list',
            showStudyList: true, // Disable study list - load directly
            extensions: [],
            modes: [],
            dataSources: [
                {
                    namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
                    sourceName: 'dicomjson',
                    configuration: {
                        friendlyName: 'Laravel DICOM JSON - Documents List',
                        name: 'json',
                    },
                }
            ],
            defaultDataSourceName: 'dicomjson',
        };
        
        // CRITICAL FIX: OHIF Study List expects data in a specific format
        // We need to ensure the data is available BEFORE OHIF initializes
        // Store data in a way OHIF can access it
        window.__OHIF_DICOM_JSON_DATA__ = dicomData;
        
        // Set URL parameters to load the study directly
        const currentUrl = new URL(window.location.href);
        if (!currentUrl.searchParams.has('url')) {
            currentUrl.searchParams.set('url', dataUrl);
            currentUrl.searchParams.set('StudyInstanceUIDs', studyUIDs);
            window.history.replaceState({}, '', currentUrl);
            console.log('✅ URL parameters set:', {
                url: dataUrl,
                StudyInstanceUIDs: studyUIDs
            });
            console.log('✅ Data stored in window.__OHIF_DICOM_JSON_DATA__');
        }

        // Close Function
        function closeViewer() {
            // Return to medical imaging list
            window.location.href = "{{ route('insurance.medical-imaging.index') }}";
        }

        // Use MutationObserver to handle dynamically loaded content
        let mutationTimeout;
        const observer = new MutationObserver(function(mutations) {
            clearTimeout(mutationTimeout);
            mutationTimeout = setTimeout(customizeDicomViewer, 50);
        });

        // Start observing when document is ready
        window.addEventListener('load', function() {
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });

        // Log documents info for debugging
        console.log('🔵 DICOM Documents List Mode Initialized');
        console.log('📄 Total Documents:', {{ $documents->count() }});
        console.log('🌐 Current URL:', window.location.href);
        console.log('⚙️ showStudyList:', window.config.showStudyList);
        console.log('🔗 URL params:', new URL(window.location.href).searchParams.getAll('url'));
        
        // Don't restore URL - let OHIF manage it
        // The URL disappearing is normal OHIF behavior after reading the parameter
    </script>

    <!-- Bundle Scripts -->
    <script defer="defer" src="{{ asset('modules/insurance/advanced-dicomviewer/app.bundle.b431c7757a418c9d162d.js') }}"></script>
</body>
</html>

Console Logs

📄 Documents count: 5
✅ DICOM data loaded from embedded script
📊 Studies count: 1
✅ Fetch interceptor installed
✅ URL parameter set: dicomjson://embedded-data.json
🔍 Fetch intercepted: dicomjson://embedded-data.json string
✅ Returning embedded DICOM data instead of fetching
📥 OHIF is parsing the JSON response
✅ JSON parsed successfully: {studies: Array(1)}

The data is successfully fetched and parsed, but the Study List remains empty.

DICOM JSON Data Format

Backend PHP Method

/**
 * Generate DICOM JSON for all documents (OHIF Study List compatible)
*/
private function generateDICOMJson($documents)
{
    $dicomJson = ['studies' => []];
    
    // Determine the correct download route based on user role
    $user = auth()->user();
    $business_id = $user->business_id ?? request()->session()->get('user.business_id');
    
    // SMART CONTACT RESOLUTION - but ONLY for policy holders (user_customer)
    $contact_id = null;
    if ($user->user_type === 'user_customer') {
        $contact_id = $user->contact_id ?? $user->crm_contact_id ?? ($user->contact ? $user->contact->id : null);
        
        // If no direct link, try matching by Email
        if (!$contact_id && $user->email) {
            $linkedContact = \App\Contact::where('business_id', $business_id)
                ->where('email', $user->email)
                ->where('type', 'customer')
                ->first();
                
            if ($linkedContact) {
                $contact_id = $linkedContact->id;
            }
        }
    }
    
    $isPolicyHolder = (bool)$contact_id;

    foreach($documents as $doc) {
        $fileFullPath = storage_path('app/public/' . $doc->file_path);
        
        // Ensure procedure_id is available (load relationship if needed)
        if (!$doc->procedure_id && $doc->relationLoaded('procedure')) {
            $doc->procedure_id = $doc->procedure->id;
        }
        
        // Generate proper URL for WADO-URI retrieval based on user role
        if ($isPolicyHolder) {
            // Policy holder route - ensure we have procedure_id
            $procedureId = $doc->procedure_id ?? $doc->procedure->id ?? null;
            if (!$procedureId) {
                \Log::error('Missing procedure_id for document', ['doc_id' => $doc->id]);
                continue;
            }
            $fileUrl = route('insurance.policy-holder-portal.medical-imaging.documents.download', [
                'procedureId' => $procedureId, 
                'documentId' => $doc->id
            ]);
        } else {
            // Admin route
            $fileUrl = route('insurance.medical-imaging.document.download', ['documentId' => $doc->id]);
        }

        $dicom = Nanodicom::factory($fileFullPath);
        if (!$dicom || !$dicom->is_dicom()) {
            continue;
        }
        $dicom->parse()->profiler_diff('parse');

        // Extraction Logic
        $StudyInstanceUID = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x000D));
        $StudyDate = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0020));
        $StudyTime = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0030));
        $StudyDescription = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x1030));
        $PatientName = $this->cleanDICOMTagValue($dicom->value(0x0010, 0x0010));
        $PatientID = $this->cleanDICOMTagValue($dicom->value(0x0010, 0x0020));
        $PatientBirthDate = $this->cleanDICOMTagValue($dicom->value(0x0010, 0x0030));
        $AccessionNumber = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0050));
        $PatientAge = $this->cleanDICOMTagValue($dicom->value(0x0010, 0x1010));
        $PatientSex = $this->cleanDICOMTagValue($dicom->value(0x0010, 0x0040));
        $NumInstances = 0;
        $Modalities = '';
        $SeriesInstanceUID = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x000E));
        $SeriesDescription = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x103E));
        $SeriesNumber = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x0011));
        $Modality = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0060));
        $SliceThickness = $this->cleanDICOMTagValue($dicom->value(0x0018, 0x0050));
        $Columns = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0011));
        $Rows = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0010));
        $InstanceNumber = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x0013));
        $SOPClassUID = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0016));
        $PhotometricInterpretation = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0004));
        $BitsAllocated = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0100));
        $BitsStored = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0101));
        $PixelRepresentation = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0103));
        $SamplesPerPixel = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0002));
        $PixelSpacing = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0030));
        $HighBit = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0102));
        $ImageOrientationPatient = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x0037));
        $ImagePositionPatient = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x0032));
        $FrameOfReferenceUID = $this->cleanDICOMTagValue($dicom->value(0x0020, 0x0052));
        $ImageType = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0008));
        $SOPInstanceUID = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0018));
        $WindowCenter = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x1050));
        $WindowWidth = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x1051));
        $SeriesDate = $this->cleanDICOMTagValue($dicom->value(0x0008, 0x0021));
        $NumberOfFrames = $this->cleanDICOMTagValue($dicom->value(0x0028, 0x0008));

        if (!$StudyInstanceUID || !$SeriesInstanceUID || !$SOPInstanceUID) {
            continue;
        }

        // STUDY
        $studyIndex = $this->arrayFindIndex($dicomJson['studies'], 'StudyInstanceUID', $StudyInstanceUID);
        if ($studyIndex < 0) {
            $study = array(
                'StudyInstanceUID' => $StudyInstanceUID,
                'StudyDate' => $StudyDate,
                'StudyTime' => $StudyTime,
                'StudyDescription' => $StudyDescription,
                'PatientName' => $PatientName,
                'PatientID' => $PatientID,
                'PatientBirthDate' => $PatientBirthDate,
                'AccessionNumber' => $AccessionNumber,
                'PatientAge' => $PatientAge,
                'PatientSex' => $PatientSex,
                'NumInstances' => $NumInstances,
                'Modalities' => $Modalities,
                'series' => array(),
            );
            array_push($dicomJson['studies'], $study);
            $studyIndex = count($dicomJson['studies']) - 1;
        }

        // SERIES
        $seriesIndex = $this->arrayFindIndex($dicomJson['studies'][$studyIndex]['series'], 'SeriesInstanceUID', $SeriesInstanceUID);
        if ($seriesIndex < 0) {
            $series = array(
                'SeriesInstanceUID' => $SeriesInstanceUID,
                'SeriesDescription' => $SeriesDescription,
                'SeriesNumber' => $SeriesNumber,
                'Modality' => $Modality,
                'SliceThickness' => $SliceThickness,
                'instances' => array(),
            );
            array_push($dicomJson['studies'][$studyIndex]['series'], $series);
            $seriesIndex++;
        }

        // INSTANCE
        $instance = array(
            'metadata' => array(
                'Columns' => $Columns,
                'Rows' => $Rows,
                'InstanceNumber' => $InstanceNumber,
                'SOPClassUID' => $SOPClassUID,
                'PhotometricInterpretation' => $PhotometricInterpretation,
                'BitsAllocated' => $BitsAllocated,
                'BitsStored' => $BitsStored,
                'PixelRepresentation' => $PixelRepresentation,
                'SamplesPerPixel' => $SamplesPerPixel,
                'PixelSpacing' => $PixelSpacing ? array_map('trim', explode('\\', $PixelSpacing)) : $PixelSpacing,
                'HighBit' => $HighBit,
                'ImageOrientationPatient' => $ImageOrientationPatient ? array_map('trim', explode('\\', $ImageOrientationPatient)) : $ImageOrientationPatient,
                'ImagePositionPatient' => $ImagePositionPatient ? array_map('trim', explode('\\', $ImagePositionPatient)) : $ImagePositionPatient,
                'FrameOfReferenceUID' => $FrameOfReferenceUID,
                'ImageType' => $ImageType ? array_map('trim', explode('\\', $ImageType)) : $ImageType,
                'Modality' => $Modality,
                'SOPInstanceUID' => $SOPInstanceUID,
                'SeriesInstanceUID' => $SeriesInstanceUID,
                'StudyInstanceUID' => $StudyInstanceUID,
                'WindowCenter' => $WindowCenter ? explode('\\', $WindowCenter)[0] : $WindowCenter,
                'WindowWidth' => $WindowWidth ? explode('\\', $WindowWidth)[0] : $WindowWidth,
                'SeriesDate' => $SeriesDate,
                'NumberOfFrames' => $NumberOfFrames,
            ),
            'url' => $fileUrl, // Remove 'dicomweb:' prefix - use direct HTTP URL
        );

        if ($NumberOfFrames > 1) {
            for ($i = 1; $i <= $NumberOfFrames; $i++) {
                $instance['url'] = 'wadouri:' . $fileUrl . '?frame=' . $i;
                array_push($dicomJson['studies'][$studyIndex]['series'][$seriesIndex]['instances'], $instance);
            }
        } else {
            $instance['url'] = 'wadouri:' . $fileUrl;
            array_push($dicomJson['studies'][$studyIndex]['series'][$seriesIndex]['instances'], $instance);
        }

        $dicomJson['studies'][$studyIndex]['NumInstances']++;
        if ($dicomJson['studies'][$studyIndex]['Modalities'] == '' || !in_array($Modality, explode(',', $dicomJson['studies'][$studyIndex]['Modalities']))) {
            if ($dicomJson['studies'][$studyIndex]['Modalities'] == '') {
                $dicomJson['studies'][$studyIndex]['Modalities'] = $Modality;
            } else {
                $dicomJson['studies'][$studyIndex]['Modalities'] .= ',' . $Modality;
            }
        }
    }

    return $dicomJson;
}

JSON Output

{
  "studies": [
    {
      "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
      "StudyDate": "20151207",
      "StudyTime": "073153",
      "StudyDescription": "KUNAS",
      "PatientName": "Anonymized^^",
      "PatientID": "0",
      "PatientBirthDate": "",
      "AccessionNumber": "",
      "PatientAge": "062Y",
      "PatientSex": "M",
      "NumInstances": 5,
      "Modalities": "CT",
      "series": [
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
          "SeriesDescription": "Recon 2: natyve",
          "SeriesNumber": "3",
          "Modality": "CT",
          "SliceThickness": "1.250000",
          "instances": [
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "23.750"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122144812.67150581479849961656",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/16/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "2",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "22.500"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412223327.715064009569544986469",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/17/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "3",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "21.250"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122151882.45167682355874425452",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/18/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "4",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "20.000"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412233466.713186822987280938805",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/19/download"
            },
            {
              "metadata": {
                "Columns": 888,
                "Rows": 1595,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.545455",
                  "0.596847"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "-1.000000"
                ],
                "ImagePositionPatient": [
                  "-265.000",
                  "-0.000",
                  "70.000"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "LOCALIZER"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704114429740.76421139551157313203",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "50",
                "WindowWidth": "500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/1/download"
            }
          ]
        },
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
          "SeriesDescription": "scout",
          "SeriesNumber": "1",
          "Modality": "CT",
          "SliceThickness": "870.000000",
          "instances": []
        }
      ]
    }
  ]
}

What I’ve Tried

  1. :white_check_mark: Blob URLs - Created Blob URLs from JSON data
  2. :white_check_mark: Data URLs - Tried base64-encoded data URLs
  3. :white_check_mark: Fetch interception - Intercepted all fetch calls to return data
  4. :white_check_mark: Direct embedding - Stored data in window.dicomData
  5. :white_check_mark: URL parameters - Added ?url=... parameter
  6. :white_check_mark: Different URL schemes - Tried dicomjson://, blob:, data:, http://

All approaches result in:

  • :white_check_mark: Data is fetched successfully
  • :white_check_mark: Data is parsed successfully
  • :x: Study List shows “0 Studies”

Working Alternative

When I disable Study List (showStudyList: false) and pass StudyInstanceUIDs parameter, OHIF loads and displays the study perfectly but return “Error (404) - We can’t find the page you’re looking for”.
This confirms the data format is correct.

Request for Help

Could someone from the OHIF team or community help me understand:

  • Why Study List doesn’t display the studies despite successful data loading?
  • What’s the correct approach to use Study List with pre-loaded DICOM JSON data?
  • If this is a limitation, what’s the recommended alternative?

My goal is to do as for OHIF VIEWER by displaying the DICOM data in the Datatable with all the functionalities and the “Viewers / Segmentation / etc…” buttons “OHIF VIEWER” does BUT DESPITE ALL MY EFFORTS THE DATATABLE IS STILL EMPTY IN MY CASE.

Yes. I understand completely. The reason that the study list displays 0 studies is because the study list does NOT work at all with a JSON data source. If you try editing the code as I suggested it might help.

Sorry. This is all I have for now.

Hello @jbocce and thanks again for your help.

It works now after after having corrected this Modules\Insurance\advanced-dicomviewer\extensions\default\src\DicomJSONDataSource\index.js and rebuild with Yarn.

But when I click on the Viewer from Study List’s Datatable, I access this Screen:

Why is it arranged like this??? Is it normal? I thought it should be arranged image by image, but it displays everything together in a single rectangular frame, even though the images weren’t uploaded on the same day and don’t belong to all users, since each user of my application has their own DICOM document or image.

I remind you that JSON Output is:

{
  "studies": [
    {
      "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
      "StudyDate": "20151207",
      "StudyTime": "073153",
      "StudyDescription": "KUNAS",
      "PatientName": "Anonymized^^",
      "PatientID": "0",
      "PatientBirthDate": "",
      "AccessionNumber": "",
      "PatientAge": "062Y",
      "PatientSex": "M",
      "NumInstances": 5,
      "Modalities": "CT",
      "series": [
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
          "SeriesDescription": "Recon 2: natyve",
          "SeriesNumber": "3",
          "Modality": "CT",
          "SliceThickness": "1.250000",
          "instances": [
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "23.750"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122144812.67150581479849961656",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/16/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "2",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "22.500"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412223327.715064009569544986469",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/17/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "3",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "21.250"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122151882.45167682355874425452",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/18/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "4",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.703125",
                  "0.703125"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "1.000000",
                  "0.000000"
                ],
                "ImagePositionPatient": [
                  "-163.700",
                  "-118.200",
                  "20.000"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "AXIAL"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412233466.713186822987280938805",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/19/download"
            },
            {
              "metadata": {
                "Columns": 888,
                "Rows": 1595,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": [
                  "0.545455",
                  "0.596847"
                ],
                "HighBit": 15,
                "ImageOrientationPatient": [
                  "1.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "0.000000",
                  "-1.000000"
                ],
                "ImagePositionPatient": [
                  "-265.000",
                  "-0.000",
                  "70.000"
                ],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": [
                  "ORIGINAL",
                  "PRIMARY",
                  "LOCALIZER"
                ],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704114429740.76421139551157313203",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "50",
                "WindowWidth": "500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/1/download"
            }
          ]
        },
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
          "SeriesDescription": "scout",
          "SeriesNumber": "1",
          "Modality": "CT",
          "SliceThickness": "870.000000",
          "instances": []
        }
      ]
    }
  ]
}

Is it possible to fix this??? If so, how??? Or Do you think it is normal ???

The Viewer button that you clicked on launches an OHIF mode. That mode defaults to a hanging protocol which arranges the images in a 1x1 view. If you try launching a different mode (say Segmentation) you’d get a different hanging protocol.

So to answer one of your questions: yes it is expected and normal for that mode.

It sounds to me from all the questions you have presented that you might want to consider writing your own custom mode (for at least choosing a specific hanging protocol) and furthermore a custom extension. The extension could be where you customize the DICOM JSON data source to your liking to allow for the study list to do paging, filtering and sorting. Of course these are just suggestions.

Regardless please familiarize yourself with OHIF modes and extensions. There is lots of documentation to help you customize OHIF in almost whatever way you desire.

Hello again @jbocce and thank you very much.

As you can see on this following screenshot of my own DICOM viewer (on the parts circled in red) :

All the DICOM images are grouped under a single thumbnail on the left, and furthermore, when I click on “Layout Tool” hoping to switch to “2x2”, “3x3”, etc., as shown in the OHIF Viewer Website , for example, the other spaces in the Viewer are empty, and all the images are grouped in the first space, whereas I expected each image to have its own space.

EVERYTHING SHOULD NOT BE GROUPED BUT SEPARATELY DISPLAYED IN DIFFERENT BOXES WHEN I CHANGE THE LAYOUT, AS WITH THE HIF VIEWER LINK, INSTEAD OF ALL DISPLAYING IN THE SAME SINGLE BOX AND ALSO INSTEAD OF HIDING IN THE LEFT COLUMN IN A SINGLE THUMBNAIL.

However, here is how I edited the file Modules\Insurance\advanced-dicomviewer\modes\longitudinal\src\index.ts before recompiling with Yarn:

import i18n from 'i18next';
import { id } from './id';
import {
  initToolGroups, toolbarButtons, cornerstone,
  ohif,
  dicomsr,
  dicomvideo,
  basicLayout,
  basicRoute,
  extensionDependencies as basicDependencies,
  mode as basicMode,
  modeInstance as basicModeInstance,
} from '@ohif/mode-basic';

export const tracked = {
  measurements: '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements',
  thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList',
  viewport: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked',
};

export const extensionDependencies = {
  // Can derive the versions at least process.env.from npm_package_version
  ...basicDependencies,
  '@ohif/extension-measurement-tracking': '^3.0.0',
};

export const longitudinalInstance = {
  ...basicLayout,
  id: ohif.layout,
  props: {
    ...basicLayout.props,
    leftPanels: [tracked.thumbnailList],
    rightPanels: [cornerstone.segmentation, tracked.measurements],
    viewports: [
      {
        namespace: tracked.viewport,
        // Re-use the display sets from basic
        displaySetsToDisplay: basicLayout.props.viewports[0].displaySetsToDisplay,
      },
      ...basicLayout.props.viewports,
    ],
  }
};


export const longitudinalRoute =
{
  ...basicRoute,
  path: 'longitudinal',
  /*init: ({ servicesManager, extensionManager }) => {
    //defaultViewerRouteInit
  },*/
  layoutInstance: longitudinalInstance,
};

export const modeInstance = {
  ...basicModeInstance,
  // TODO: We're using this as a route segment
  // We should not be.
  id,
  routeName: 'viewer',
  displayName: i18n.t('Modes:Basic Viewer'),
  hangingProtocol: ['@ohif/mnGrid', '@ohif/mnGrid8', 'default'],
  routes: [
    longitudinalRoute
  ],
  extensions: extensionDependencies,
};

const mode = {
  ...basicMode,
  id,
  modeInstance,
  extensionDependencies,
};

export default mode;
export { initToolGroups, toolbarButtons };

AND IN PGP CODE CONTROLER, I HAVE:

/**
     * Display the DICOM viewer for a specific document.
     *
     * @param int $documentId
     * @return \Illuminate\Http\Response
     */
    public function show($documentId, $any = null)
    {
        $user = auth()->user();
        $business_id = $user->business_id ?? request()->session()->get('user.business_id');
        
        // SMART CONTACT RESOLUTION - but ONLY for policy holders (user_customer)
        $contact_id = null;
        if ($user->user_type === 'user_customer') {
            $contact_id = $user->contact_id ?? $user->crm_contact_id ?? ($user->contact ? $user->contact->id : null);
            
            // If no direct link, try matching by Email
            if (!$contact_id && $user->email) {
                $linkedContact = \App\Contact::where('business_id', $business_id)
                    ->where('email', $user->email)
                    ->where('type', 'customer')
                    ->first();
                    
                if ($linkedContact) {
                    $contact_id = $linkedContact->id;
                }
            }
        }
        
        // DEBUG: Log the request
        \Log::info('DicomViewerController@show called', [
            'documentId' => $documentId,
            'any' => $any,
            'url' => request()->fullUrl(),
            'user_id' => auth()->id(),
            'user_type' => $user->user_type,
            'contact_id' => $contact_id,
            'business_id' => $business_id,
            'is_policy_holder' => (bool)$contact_id
        ]);
        
        // Determine role and validate access
        $role = 'admin';
        try {
            if ($contact_id) {
                $role = 'policy_holder';
                $document = MedicalImagingDocument::with('procedure')
                    ->whereHas('procedure', function($q) use ($contact_id) {
                        $q->where('patient_id', $contact_id);
                    })
                    ->findOrFail($documentId);
                $backUrl = route('insurance.policy-holder-portal.medical-imaging.show', $document->procedure_id);
            } else {
                $document = MedicalImagingDocument::where('business_id', $business_id)->findOrFail($documentId);
                $backUrl = route('insurance.medical-imaging.show', $document->procedure_id);
            }
        } catch (\Exception $e) {
            \Log::error('DicomViewerController@show - Document access denied or not found', [
                'documentId' => $documentId,
                'error' => $e->getMessage()
            ]);
            return redirect()->back()->with('error', __('insurance::lang.document_not_found_or_access_denied'));
        }
        
        \Log::info('DicomViewerController@show - Role determined', [
            'role' => $role,
            'document_id' => $document->id,
            'backUrl' => $backUrl
        ]);

        // Check file type
        $extension = strtolower(pathinfo($document->file_name, PATHINFO_EXTENSION));
        if (!in_array($extension, ['dcm', 'dicom'])) {
            return redirect()->back()->with('error', 'Not a DICOM file.');
        }

        // I18n: Load translation file based on Laravel locale with fallback
        $laravelLocale = app()->getLocale();
        
        // Define mapping between Laravel locales and Viewer locales (Nextcloud style)
        $localeMapping = [
            'en' => 'en_GB', // Viewer uses en_GB as default English
            'fr' => 'fr',
            'es' => 'es',
            'de' => 'de',
            'it' => 'it',
            // Add more as needed
        ];
        
        $viewerLocale = $localeMapping[$laravelLocale] ?? 'en_GB';
        
        // Path to l10n files in the module
        $l10nPath = module_path('Insurance', 'dicomviewer/l10n/' . $viewerLocale . '.json');
        
        // Fallback to en_GB if specific locale file doesn't exist
        if (!file_exists($l10nPath)) {
             $l10nPath = module_path('Insurance', 'dicomviewer/l10n/en_GB.json');
        }
        
        $translations = [];
        if (file_exists($l10nPath)) {
            $jsonContent = file_get_contents($l10nPath);
            $decoded = json_decode($jsonContent, true);
            $translations = $decoded['translations'] ?? [];
        } else {
            // Absolute fallback if no files found
            \Log::warning("DICOM Viewer localization file not found: $l10nPath");
        }

        // Use OHIF viewer (more complete and better design)
        return view('insurance::medical-imaging.dicom-viewer', compact('document', 'role', 'backUrl', 'translations'));
    }

    /**
     * Show DICOM viewer by Procedure ID (Loads first available DICOM).
     *
     * @param int $procedureId
     * @param string|null $any
     * @return \Illuminate\Http\Response
     */
    public function showByProcedure($procedureId, $any = null)
    {
        $user = auth()->user();
        $business_id = $user->business_id ?? request()->session()->get('user.business_id');
        
        // SMART CONTACT RESOLUTION
        $contact_id = null;
        if ($user->user_type === 'user_customer') {
            $contact_id = $user->contact_id ?? $user->crm_contact_id ?? ($user->contact ? $user->contact->id : null);
            if (!$contact_id && $user->email) {
                $linkedContact = \App\Contact::where('business_id', $business_id)
                    ->where('email', $user->email)
                    ->where('type', 'customer')
                    ->first();
                if ($linkedContact) {
                    $contact_id = $linkedContact->id;
                }
            }
        }

        // Find Procedure
        if ($contact_id) {
            $procedure = MedicalImagingProcedure::where('business_id', $business_id)
                ->where('patient_id', $contact_id)
                ->find($procedureId);
        } else {
            $procedure = MedicalImagingProcedure::where('business_id', $business_id)->find($procedureId);
        }

        if (!$procedure) {
            return redirect()->back()->with('error', __('insurance::lang.procedure_not_found'));
        }

        // Find first DICOM document
        $firstDicom = $procedure->documents()
            ->whereIn('file_type', ['dicom', 'dcm'])
            ->orderBy('id')
            ->first();

        // If no explicit file_type, try extension
        if (!$firstDicom) {
            $documents = $procedure->documents()->get();
            foreach ($documents as $doc) {
                $ext = strtolower(pathinfo($doc->file_name, PATHINFO_EXTENSION));
                if (in_array($ext, ['dcm', 'dicom'])) {
                    $firstDicom = $doc;
                    break;
                }
            }
        }

        if (!$firstDicom) {
             return redirect()->back()->with('error', __('insurance::lang.no_dicom_images_found_for_procedure'));
        }

        // Redirect to show method logic by calling it directly 
        // to avoid browser redirect and allow keeping the URL structure if we wanted
        // But since we want to leverage the 'show' logic which uses $documentId,
        // we can just call $this->show($firstDicom->id, $any);
        return $this->show($firstDicom->id, $any);
    }

    /**
     * Get all DICOM JSON metadata for documents list (OHIF Study List compatible).
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function getAllDicomJson(Request $request)
    {
        $user = auth()->user();
        if (!$user) {
            \Log::error('getAllDicomJson - Unauthorized');
            return response()->json(['error' => 'Unauthorized'], 403);
        }

        $business_id = $user->business_id ?? request()->session()->get('user.business_id');
        
        // SMART CONTACT RESOLUTION - but ONLY for policy holders (user_customer)
        $contact_id = null;
        if ($user->user_type === 'user_customer') {
            $contact_id = $user->contact_id ?? $user->crm_contact_id ?? ($user->contact ? $user->contact->id : null);
            
            // If no direct link, try matching by Email
            if (!$contact_id && $user->email) {
                $linkedContact = \App\Contact::where('business_id', $business_id)
                    ->where('email', $user->email)
                    ->where('type', 'customer')
                    ->first();
                    
                if ($linkedContact) {
                    $contact_id = $linkedContact->id;
                }
            }
        }

        // Get all DICOM documents for the business or user's company
        $query = MedicalImagingDocument::where('medical_imaging_documents.business_id', $business_id)
            ->whereIn('file_type', ['dcm', 'dicom'])
            ->with(['procedure.patient', 'procedure.examType']);

        // If user is a healthcare agent (not super admin), filter by their company
        if (!$user->can('superadmin') && $user->can('insurance.healthcare.view_prestations')) {
            // Filter documents from procedures where the provider matches the user's company
            $query->whereHas('procedure', function($q) use ($user) {
                if ($user->crm_contact_id) {
                    $q->where('provider_id', $user->crm_contact_id);
                }
            });
        }

        // If policy holder, filter by patient_id
        if ($contact_id) {
            $query->whereHas('procedure', function($q) use ($contact_id) {
                $q->where('patient_id', $contact_id);
            });
        }

        $documents = $query->orderBy('created_at', 'desc')->get();

        // Filter to only include documents that exist on disk
        $documents = $documents->filter(function($doc) {
            $path = storage_path('app/public/' . $doc->file_path);
            return file_exists($path);
        });

        \Log::info('getAllDicomJson - Processing all documents', [
            'user_id' => $user->id,
            'business_id' => $business_id,
            'total_documents' => $documents->count()
        ]);

        // Ensure all documents have procedure_id set
        foreach ($documents as $doc) {
            if (!$doc->procedure_id && $doc->relationLoaded('procedure')) {
                $doc->procedure_id = $doc->procedure->id;
            }
        }

        // Generate DICOM JSON for all documents
        $dicomJson = $this->generateDICOMJson($documents);

        \Log::info('getAllDicomJson - Generated JSON', [
            'studies_count' => count($dicomJson['studies'] ?? []),
            'json_size' => strlen(json_encode($dicomJson))
        ]);

        return response()->json($dicomJson)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, OPTIONS')
            ->header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
            ->header('Content-Type', 'application/json');
    }

AND MY HTML - JAVASCRIPT;

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/>
    <meta name="theme-color" content="#000000"/>
    <title>{{ __('insurance::lang.documents_list') }} - {{ config('app.name') }}</title>
    <link href="{{ asset('modules/insurance/advanced-dicomviewer/app.bundle.css') }}" rel="stylesheet">
</head>
<body>
    <noscript>{{ __('insurance::lang.javascript_required') }}</noscript>
    
    <div id="root"></div>

    <div class="back-btn-overlay">
        <button onclick="closeViewer()" class="btn-back">← {{ __('insurance::lang.back') }}</button>
    </div>

    <!-- Configuration & Scripts -->
    <script>
        // Set Public URL for webpack chunks
        window.PUBLIC_URL = '{{ asset("modules/insurance/advanced-dicomviewer") }}/';
        
        console.log('📄 Documents count:', {{ $documents->count() }});
        console.log('🌐 Current URL:', window.location.href);

        // Load embedded DICOM data directly
        const dicomData = {!! json_encode($dicomJsonData) !!};
        
        console.log('✅ DICOM data loaded from embedded script');
        console.log('📊 Studies count:', dicomData.studies ? dicomData.studies.length : 0);
        console.log('📋 Full data:', dicomData);
        
        // Store globally for OHIF and debugging
        window.dicomData = dicomData;
        
        // Create a fake URL for OHIF to "fetch" from
        const dataUrl = 'dicomjson://embedded-data.json';
        
        // Override OHIF's data fetching to use our embedded data
        // This intercepts the fetch call and returns our data directly
        const originalFetch = window.fetch;
        window.fetch = function(url, options) {
            console.log('🔍 Fetch intercepted:', url, typeof url);
            
            // Handle null or undefined URLs
            if (!url || url === 'null' || url === 'undefined') {
                console.log('⚠️ Invalid URL detected, returning embedded data');
                return Promise.resolve(new Response(JSON.stringify(dicomData), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                }));
            }
            
            // If OHIF is trying to fetch DICOM JSON data, return our embedded data
            if (typeof url === 'string' && (
                url.includes('dicomjson') || 
                url.includes('.json') || 
                url.startsWith('blob:') ||
                url.includes('embedded-data')
            )) {
                console.log('✅ Returning embedded DICOM data instead of fetching');
                console.log('📊 Data structure:', {
                    hasStudies: !!dicomData.studies,
                    studiesCount: dicomData.studies ? dicomData.studies.length : 0,
                    firstStudy: dicomData.studies && dicomData.studies[0] ? {
                        StudyInstanceUID: dicomData.studies[0].StudyInstanceUID,
                        PatientName: dicomData.studies[0].PatientName,
                        seriesCount: dicomData.studies[0].series ? dicomData.studies[0].series.length : 0,
                        firstSeries: dicomData.studies[0].series && dicomData.studies[0].series[0] ? {
                            SeriesInstanceUID: dicomData.studies[0].series[0].SeriesInstanceUID,
                            instancesCount: dicomData.studies[0].series[0].instances ? dicomData.studies[0].series[0].instances.length : 0
                        } : null
                    } : null
                });
                
                const response = new Response(JSON.stringify(dicomData), {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' }
                });
                
                // Log when response is consumed
                const originalJson = response.json.bind(response);
                response.json = async function() {
                    console.log('📥 OHIF is parsing the JSON response');
                    const data = await originalJson();
                    console.log('✅ JSON parsed successfully:', data);
                    return data;
                };
                
                return Promise.resolve(response);
            }
            
            // For all other requests, use the original fetch
            return originalFetch.apply(this, arguments);
        };
        
        console.log('✅ Fetch interceptor installed');
        
        // OHIF Configuration - Disable Study List and load study directly
        const studyUIDs = dicomData.studies.map(s => s.StudyInstanceUID).join(',');
        
        window.config = {
            routerBasename: '/insurance/medical-imaging/documents-list',
            showStudyList: true, // Disable study list - load directly
            extensions: [],
            modes: [],
            dataSources: [
                {
                    namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
                    sourceName: 'dicomjson',
                    configuration: {
                        friendlyName: 'Laravel DICOM JSON - Documents List',
                        name: 'json',
                    },
                }
            ],
            defaultDataSourceName: 'dicomjson',
        };
        
        // CRITICAL FIX: OHIF Study List expects data in a specific format
        // We need to ensure the data is available BEFORE OHIF initializes
        // Store data in a way OHIF can access it
        window.__OHIF_DICOM_JSON_DATA__ = dicomData;
        
        // Set URL parameters to load the study directly
        const currentUrl = new URL(window.location.href);
        if (!currentUrl.searchParams.has('url')) {
            currentUrl.searchParams.set('url', dataUrl);
            currentUrl.searchParams.set('StudyInstanceUIDs', studyUIDs);
            window.history.replaceState({}, '', currentUrl);
            console.log('✅ URL parameters set:', {
                url: dataUrl,
                StudyInstanceUIDs: studyUIDs
            });
            console.log('✅ Data stored in window.__OHIF_DICOM_JSON_DATA__');
        }

        // Close Function
        function closeViewer() {
            // Return to medical imaging list
            window.location.href = "{{ route('insurance.medical-imaging.index') }}";
        }

        // Use MutationObserver to handle dynamically loaded content
        let mutationTimeout;
        const observer = new MutationObserver(function(mutations) {
            clearTimeout(mutationTimeout);
            mutationTimeout = setTimeout(customizeDicomViewer, 50);
        });

        // Start observing when document is ready
        window.addEventListener('load', function() {
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });

        // Log documents info for debugging
        console.log('🔵 DICOM Documents List Mode Initialized');
        console.log('📄 Total Documents:', {{ $documents->count() }});
        console.log('🌐 Current URL:', window.location.href);
        console.log('⚙️ showStudyList:', window.config.showStudyList);
        console.log('🔗 URL params:', new URL(window.location.href).searchParams.getAll('url'));
        
        // Don't restore URL - let OHIF manage it
        // The URL disappearing is normal OHIF behavior after reading the parameter
    </script>

    <!-- Bundle Scripts -->
    <script defer="defer" src="{{ asset('modules/insurance/advanced-dicomviewer/app.bundle.fe7d3f023f734e9ebfbf.js') }}"></script>
</body>
</html>

How can I properly arrange both the thumbnails and layouts separately, as with the OHIF Viewer website, instead of always cramming them together?

EVERYTHING SHOULD NOT BE GROUPED BUT SEPARATELY DISPLAYED IN DIFFERENT BOXES WHEN I CHANGE THE LAYOUT, AS WITH THE HIF VIEWER LINK, INSTEAD OF ALL DISPLAYING IN THE SAME SINGLE BOX AND ALSO INSTEAD OF HIDING IN THE LEFT COLUMN IN A SINGLE THUMBNAIL.

The reason why only 1 display set is created is with the data. The JSON you provided lists all 5 images under that same series and thus only a single display set is created by OHIF. Each viewport (box as you call it) typically only displays a single display set in OHIF. Likewise the series tray on the left shows a single thumbnail per display set and since your data lists ALL images under one series the series tray only shows one thumbnail.

You could change your DICOM JSON as below to get two series, but if the original data is this way it would be difficult to justify this change.

{
  "studies": [
    {
      "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
      "StudyDate": "20151207",
      "StudyTime": "073153",
      "StudyDescription": "KUNAS",
      "PatientName": "Anonymized^^",
      "PatientID": "0",
      "PatientBirthDate": "",
      "AccessionNumber": "",
      "PatientAge": "062Y",
      "PatientSex": "M",
      "NumInstances": 5,
      "Modalities": "CT",
      "series": [
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
          "SeriesDescription": "Recon 2: natyve",
          "SeriesNumber": "3",
          "Modality": "CT",
          "SliceThickness": "1.250000",
          "instances": [
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": ["0.703125", "0.703125"],
                "HighBit": 15,
                "ImageOrientationPatient": ["1.000000", "0.000000", "0.000000", "0.000000", "1.000000", "0.000000"],
                "ImagePositionPatient": ["-163.700", "-118.200", "23.750"],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122144812.67150581479849961656",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/16/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "2",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": ["0.703125", "0.703125"],
                "HighBit": 15,
                "ImageOrientationPatient": ["1.000000", "0.000000", "0.000000", "0.000000", "1.000000", "0.000000"],
                "ImagePositionPatient": ["-163.700", "-118.200", "22.500"],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412223327.715064009569544986469",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/17/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "3",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": ["0.703125", "0.703125"],
                "HighBit": 15,
                "ImageOrientationPatient": ["1.000000", "0.000000", "0.000000", "0.000000", "1.000000", "0.000000"],
                "ImagePositionPatient": ["-163.700", "-118.200", "21.250"],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704122151882.45167682355874425452",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/18/download"
            },
            {
              "metadata": {
                "Columns": 512,
                "Rows": 512,
                "InstanceNumber": "4",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": ["0.703125", "0.703125"],
                "HighBit": 15,
                "ImageOrientationPatient": ["1.000000", "0.000000", "0.000000", "0.000000", "1.000000", "0.000000"],
                "ImagePositionPatient": ["-163.700", "-118.200", "20.000"],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.2025070412233466.713186822987280938805",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704121912287.36535028632655733227",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "-500",
                "WindowWidth": "1500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/19/download"
            }
          ]
        },
        {
          "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
          "SeriesDescription": "scout",
          "SeriesNumber": "1",
          "Modality": "CT",
          "SliceThickness": "870.000000",
          "instances": [
            {
              "metadata": {
                "Columns": 888,
                "Rows": 1595,
                "InstanceNumber": "1",
                "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2",
                "PhotometricInterpretation": "MONOCHROME2",
                "BitsAllocated": 16,
                "BitsStored": 16,
                "PixelRepresentation": 1,
                "SamplesPerPixel": 1,
                "PixelSpacing": ["0.545455", "0.596847"],
                "HighBit": 15,
                "ImageOrientationPatient": ["1.000000", "0.000000", "0.000000", "0.000000", "0.000000", "-1.000000"],
                "ImagePositionPatient": ["-265.000", "-0.000", "70.000"],
                "FrameOfReferenceUID": "1.2.840.113619.2.55.3.4271045733.996.1449464144.597.17720.5",
                "ImageType": ["ORIGINAL", "PRIMARY", "LOCALIZER"],
                "Modality": "CT",
                "SOPInstanceUID": "1.3.6.1.4.1.44316.6.102.3.20250704114429740.76421139551157313203",
                "SeriesInstanceUID": "1.3.6.1.4.1.44316.6.102.2.20250704114429771.63133867861500115690",
                "StudyInstanceUID": "1.3.6.1.4.1.44316.6.102.1.20250704114423696.61158672119535771932",
                "WindowCenter": "50",
                "WindowWidth": "500",
                "SeriesDate": "20151207",
                "NumberOfFrames": false
              },
              "url": "wadouri:http://127.0.0.1:8000/insurance/medical-imaging/document/1/download"
            }
          ]
        }
      ]
    }
  ]
}

So in summary everything I see is expected based on your data and code.

This all said, frame view might be something you could try if you want to see as many frames of a display set as possible on screen.

Hello @jbocce and thanks once again for your answer.

Does that mean it is correct or normal and if series was different, that could be display in “2x2”, 3x3", etc… layout ???

So, is everything okay do you think ???

Yes and yes. :blush: Thanks.

Thank you very much dear @jbocce for all your help you