Sunday, August 1, 2021

MODALIZER+ V6

Well, I guess it's time to celebrate. MODALIZER+ V6 is being released today. Seriously, it's been a long journey. We started working on this version more than two years ago. This been a team work of many people. Evgenia that was in charge of the DICOMIZER-MODALIZER product line since its beginning, Bezalel that designed the new UI, Bar that finished the work and added the latest features, and also David and Rotem that helped allong the way. Additionally, this release completes the work on all HRZ V6 products: MODAIZER-SDK DICOM Library, ZiKiT PACS Server and now MODALIZER+.

So, what's new in MODALIZER+ V6? The first thing you will notice is of course the new graphic design.

MODALIZER+ V6 Home Screen

The new MODALIZER+ is colorful. You will also notice the colors are following into the different workflows, allowing the user to distinguish between them, as we realized that some of the screens look very similar to one another. Also, there's usability, you will notice larger fonts and wider spaces.

One of the more important features in the new MODALIZER+ is the secure DICOM network protocol. Though this feature was already available in MODALIZER+ 5.5.1, this release completes this effort.

There are many new improvements in this version that you will notice when working with it and you can also check the release notes but what I do want to talk about is the business model.

If you told me back in 2010 when I release DICOMIZER for iPhone that 11 years after I'm still going to play with it, I would have never believed you. 
DICOMIZER
the iOS DICOMIZER Logo from 2010

So, the business model. The new MODAZLIER+ is offered in a subscription model. Just like all other software vendors in the world, I have also came to realize that in order to sustain a long term software product development effort, you have to have a steady and justifying revenue stream out of it. Once in a while I get these emails from customers that are still using version 2 or 3 or 4 and suddenly their computer (sometimes running windows xp or windows 7) died and now they want a new copy. No problem I say, buy a new license. But we have purchased a license in 2010, they answer, and that's true, you also purchased a Windows XP license right? Why you don't call Microsoft? You get the point, right ...

So the new MODALIZER+ is a subscription. Currently offered at $150/year and there's a monthly subscription for $15/month, very similar to office 365 for example. The subscription license is transferable, so if you installed on one computer and after a month you want it on a different computer you can do this. The payments can be made with PayPal using other a paypal account or credit card. We worked on a license system that allows you to activate online as well as offline because sometimes your computer will be behind a firewall which is understandable. One thing, if your computer is disconnected, buy an annual license so you don't have to fill in the code manually every month. And if you want to purchase multiple copies that's of course most welcome, just contact us on the web site.

One last thing, like before there's a 30 days free evaluation so go ahead, download, install and see if it suits your needs. I hope it does.





Monday, January 18, 2021

Move all studies of patients with ID in CSV file from PACS-A to PACS-B

OK, so here's another PowerShell DICOM script that came handy recently. So my friend, lets call him Benny, got a request from his manager to export all the studies of patients in a list provided as excel sheet and deliver them to a research group (after de-identification and everything of course) but when he called me he just needed to get them out quickly from the PACS. The list was long and there was no chance to do this one by one. So we wrote together this little PowerShell script that reads the patient id's from the excel (after saving it as csv) and use MODALIZER-SDK to do the DICOM work. here it is. I think it explains itself pretty well. We've set the destination PACS-B to be our lightweight PACS server with no plugin so the DICOM files were saved on the disk and from there he picked them up.

$my_ae_title="MY-AE-TITLE"


$pacs_a_ae_title="PACS-A"

$pacs_a_ip_address="10.0.0.10"

$pacs_a_dicom_port=104


$pacs_b_ae_title="PACS-B"


function MoveStudyByPatientID

param($pat)

$r = New-Object -ComObject rzdcx.DCXREQ

    

    # Lets echo to be sure

$r.Echo($my_ae_title, $pacs_a_ae_title, $pacs_a_ip_address, $pacs_a_dicom_port)

$o = New-Object -ComObject rzdcx.DCXOBJ

$e = New-Object -ComObject rzdcx.DCXELM

# Query Level - STUDY

$e.Init(0x00080052)

$e.Value = "STUDY"

$o.insertElement($e)

# Patient ID

$e.Init(0x00100020)

$e.Value = $pat

$o.insertElement($e)

# Study Instance UID

$e.Init(0x0020000d)

    $e.Value = ""

$o.insertElement($e)

$i = $r.Query($my_ae_title, $pacs_a_ae_title, $pacs_a_ip_address, $pacs_a_dicom_port, "1.2.840.10008.5.1.4.1.2.1.1", $o)

    if ($i.AtEnd()) {

        "No Studies for patient id: " + $pat

    }

    else

    {

        while ($i.AtEnd() -ne $true)

        {

            $res = $i.Get()

    "LOG: Study Instance UID: " + $res.GetElement(0x0020000d).Value

    $o.insertElement($res.GetElement(0x0020000d))

    $r.Move($my_ae_title, $pacs_a_ae_title, $pacs_a_ip_address, $pacs_a_dicom_port, 104, $pacs_b_ae_title, $o)

            $i.Next();

    } 

    }

}


# A log never hurts

$a = New-Object -ComObject rzdcx.DCXAPP

$a.StartLogging("log.log")

$a.LogLevel = 5


$patients = Import-Csv -Path .\patients.csv

foreach ($pat in $patients.patient_id) 

{

    "LOG: Patient ID: " + $pat

MoveStudyByPatientID $pat

}


$a.StopLogging()


# That's it for now 




Thursday, August 27, 2020

Sending DICOM files indexed in a DICOMDIR on a DICOM CD to PACS using PowerShell and MODALIZER-SDK

 I'm posting this PowerShell script in response to a question I got about sending DICOM files on a CD to PACS. 

This functionality is available in MODALIZER+ from the View menu simply by right clicking the Study item at the top study panel and selecting "Send to". The PACS AE title has to be configured first in the settings.

But, for the automation enthusiasts, here's a PowerShell script that do the same. Recalling on DICOMDIR, a DICOM CD or DVD has a file called DICOMDIR in the root path that references all DICOM files on the media. This script iterates through the DICOMDIR records to collect all referenced files, then it send them using DCXREQ.Send command to the PACS.

Here it is. Enjoy!

 # Send DICOMDIR Patients to PACS


param($rootFolder);

Wednesday, January 8, 2020

Indexing DICOM files in a directory into a CSV file using PowerShell and MODALIZER-SDK

This post is continues my series on PowerShell DICOM scripting. It covers a very common task that repeats itself in all kind of forms where I have a directory full of DICOM files that piled up somewhere and needs to be indexed and processed. PowerShell scripting comes handy in these cases where there's a need to do something fast and probably change it on the fly. Of course, you will need our MODALIZER-SDK for this one.

#!PowerShell ... jeje ... this doesn't work on widows ;-) 

# Well, this is something very useful (I think) for anyone managing a PACS.
# Lets say you have a directory full with DICOM files you have no idea what's in them (you do, right?) and you want to scan through them.
# So, here's a little powershell script for this:

# Ok, so first we get the directory to scan as a command line parameter
param ($ScanPath)

# This is a function. It extract one element from a DICOM object and return an empty string if the element doesn't exist or doesn't have a value. We use it later on.
function Get-Value($obj, $tag
{
  $e = $obj.GetElement($tag)
  if ($e -ne $null)
  {
    if ($e.Value -ne $null)
    { 
      return $e.Value
    }
    else
    {
      return ""
    }
  }
  else
  {
    return "";
  }
}

# Modify this array to add more tags. The pair items are used for headers (and readabity).
$tags = @(
    0x00080016, "sop class uid",
    0x00080018, "sop instance uid",
    0x00080020, "study date",
    0x00080030, "study time",
    0x00080050, "accession number",
    0x00100010, "patient name",
    0x00100020, "patient id",
    0x00100030, "patient birth date",
    0x00100040, "patient sex",
    0x0020000d, "study Instance UID",
    0x0020000e, "$series Instance UID")

# This prints a nice CSV header line. 
# Note all the '","' to enclose values in double quotes and separate them with commas. 
function Print-Header
{
    $line = '"Filename","Status'
    for (($i=1); ($i -lt $tags.Count); ($i+=2))
    {
        $line += '","'
        $line += $tags[$i]
    }
    $line += '"'
    $line
}

# This is where the element values are extracted and one line is returned. 
# The filename is in the first column and a status is in the second. Then the values from the tags array follow.
# The status  can be "OK" or "ERROR". You will also see errors in stderr.
function Parse-DicomFile($filename)
{
    $line = '"'
    $line += $filename
    $line += '","OK'
    try {
        $obj = New-Object -ComObject rzdcx.DCXOBJ
        $obj.openFile($filename)
        for (($i=0); ($i -lt $tags.Count); ($i+=2))
        {
            $line += '","'
            $line += Get-Value $obj $tags[$i]
        }
        $line+='"'
    } catch { $line = '"' + $filename+ '","ERROR"' }
    return $line
}

# Here it starts. We first print the header and then scan through the folder and extract the data from every DICOM file. 
# Note ignoring directories
Print-Header
Get-ChildItem -Recurse -Path $ScanPath | Foreach-Object {    
    if ($_.Attributes -ne "Directory") {
        Parse-DicomFile $_.FullName
    }

# Enjoy!!!



Wednesday, May 8, 2019

More PowerShell DICOM

# PowerShell DICOM

# This little script becomes handy for example for a daily test of your PACS
# It generates a 1000x1000 image with date and key attributes (Study, Series and Instance UID) burned in
# This script uses HRZ's RZDCX.DLL.

# Create DICOM Object
$DCM = New-Object -com rzdcx.DCXOBJ

# UID Generator
$UID = New-Object -com rzdcx.DCXUID

# Create new Key UID's
$studyInstanceUID = $UID.CreateUID(1)
$seriesInstanceUID = $UID.CreateUID(2)
$sopInstanceUID = $UID.CreateUID(3)

# Add minimal elements to the dataset
$tagValues = @(
0x00080016, "1.2.840.10008.5.1.4.1.1.7", 
0x00080018, $sopInstanceUID,
0x00080020, ($studyDate = Get-Date -UFormat "%Y%m%d"),
0x00080030, "",
0x00080050, "ACCNUMHRZTEST",
0x00080060, "SC",
0x00080064, "WSD",
0x00080090, "HRZ",
0x00100010, "HRZ^TEST",
0x00100020, "HRZTEST",
0x00100030, "20100101",
0x00100040, "O",
0x0020000d, $studyInstanceUID,
0x0020000e, $seriesInstanceUID,
0x00200010, "123",
0x00200011, "1",
0x00200013, "1")

$E = New-Object -com rzdcx.DCXELM
for (($i=0); ($i -lt $tagValues.Count); ($i+=2))
{
    $E.Init($tagValues[$i])
    $E.Value = $tagValues[$i+1]
    $DCM.insertElement($E)
}

# Create 1000x1000 Image with text printed on it
$rows = 1000
$cols = 1000

# jpeg image
Add-Type -AssemblyName System.Drawing
$imageFormat = "System.Drawing.Imaging.ImageFormat" -as [type]
$format = $imageFormat::jpeg


$uidType = "rzdcx.UID_TYPE" -as [type]

$createDate = Get-Date
$bmp = new-object System.Drawing.Bitmap $cols,$rows
$font = new-object System.Drawing.Font Consolas,24 
$brushBg = [System.Drawing.Brushes]::Yellow 
$brushFg = [System.Drawing.Brushes]::Black 
$graphics = [System.Drawing.Graphics]::FromImage($bmp
$graphics.FillRectangle($brushBg,0,0,$bmp.Width,$bmp.Height) 
$graphics.DrawString($createDate, $font,$brushFg,10,10
$font = new-object System.Drawing.Font Consolas,8 
$graphics.DrawString("StudyInstance UID: " + $studyInstanceUID, $font,$brushFg, 10,110)  
$graphics.DrawString("Series Instance UID: " + $studyInstanceUID, $font,$brushFg,10,210)  
$graphics.DrawString("SOP Instance UID: " + $studyInstanceUID, $font,$brushFg,10,310)  
$graphics.DrawString("TARGET AET: " + "PACS-A", $font, $brushFg, 10, 410)  

$graphics.Dispose() 

# Save the JPEG image
$filename = "$home\HRZTEST.jpg"
$bmp.Save($filename, $format

# Insert it to the DICOM object
$DCM.SetJpegFrames($filename);

# Save the DICOM file
$dcmfile = $filename + ".dcm"
$DCM.saveFile($dcmfile)

# Send it to the PACS
$REQ = New-Object -com rzdcx.DCXREQ

$succeeded = ""
$failed = ""

# Start logging for network comunication and save to log file

$APP = New-Object -com rzdcx.DCXAPP
$APP.LogLevel = 5 # Debug

$logFile = "$home\SendTestImage.log"

$APP.StartLogging($logFile)

$REQ.Echo("HRZ-TEST", "PACS-A", "localhost", 104)
$REQ.Send("HRZ-TEST", "PACS-A", "localhost", 104, $dcmfile, [ref] $succeeded, [ref] $failed)

# Print results
$filename
$dcmfile
"Succeeded: " + $succeeded
"Failed: " + $failed

# Stop logging
$App.StopLogging()

"Done!"






Tuesday, January 15, 2019

PowerShell DICOM Scripting

Disclosure: This post is purely technical!
Assumption: you know your way around PowerShell or how to get there and a bit of DICOM and our RZDCX API.

If your system is x64 (probably) than make sure to regsvr32 radix.dll the x64 version.

Let's PowerShell!!!

# download rzdcx
Invoke-WebRequest -Uri http://downloads.roniza.com/rzdcx/2.0.8.7/RZDCX_2087.zip -OutFile ./rzdcx.zip

# unzip it
Expand-Archive ./rzdcx.zip -DestinationPath ./rzdcx

# regsvr32 win32 version
$rzdcx32 = Resolve-Path .\rzdcx\win32\rzdcx.dll
Start-Process regsvr32 -verb runAs -argumentlist $rzdcx32

$rzdcx64 = Resolve-Path .\rzdcx\x64\rzdcx.dll
Start-Process regsvr32 -verb runAs -argumentlist $rzdcx64

# Create DICOM Object
$DCM = New-Object -com rzdcx.DCXOBJ

# I assume you have a directory called DICOM with DICOM files in it
$files = @(Get-ChildItem ".\DICOM\*")

# Write headers
"filename, Patient Name, Patient ID"

# For each file extract and print patient name and patient id
foreach ($file in $files) {
  $DCM.openFile($file)
  $patname = $DCM.GetElement([int32]::Parse(00100010,'HexNumber')).Value
  $patid   = $DCM.GetElement([int32]::Parse(00100020,'HexNumber')).Value
  Write-Host $file "," $patname "," $patid
}

Try it!