From 7b0712f842ecf795b2ec4ce9e80d0d4478f48a97 Mon Sep 17 00:00:00 2001 From: Shreeraj Jadhav Date: Thu, 6 Oct 2022 12:50:11 -0400 Subject: [PATCH] feat(pstate): Add dcmp2pgm to itk-wasm as apply-presentation-state-to-image operation Add DCMTK/dcmpstat/apps/dcmp2pgm.cc as apply-presentation-state-to-image.cxx operation in itk-wasm. Fix indentation to match itk-wasm style. Add itk-wasm license header to the new file. --- .../internal/pipelines/dicom/CMakeLists.txt | 3 + .../apply-presentation-state-to-image.cxx | 595 ++++++++++++++++++ 2 files changed, 598 insertions(+) create mode 100644 src/io/internal/pipelines/dicom/apply-presentation-state-to-image.cxx diff --git a/src/io/internal/pipelines/dicom/CMakeLists.txt b/src/io/internal/pipelines/dicom/CMakeLists.txt index e8462c463..a778fdebc 100644 --- a/src/io/internal/pipelines/dicom/CMakeLists.txt +++ b/src/io/internal/pipelines/dicom/CMakeLists.txt @@ -16,12 +16,15 @@ set(wasm_modules ) add_executable(structured-report-to-text structured-report-to-text.cxx diquant.cc) add_executable(structured-report-to-html structured-report-to-html.cxx diquant.cc) add_executable(read-dicom-encapsulated-pdf read-dicom-encapsulated-pdf.cxx) +add_executable(apply-presentation-state-to-image apply-presentation-state-to-image.cxx) target_link_libraries(structured-report-to-text PUBLIC ${ITK_LIBRARIES}) target_link_libraries(structured-report-to-html PUBLIC ${ITK_LIBRARIES}) target_link_libraries(read-dicom-encapsulated-pdf PUBLIC ${ITK_LIBRARIES}) +target_link_libraries(apply-presentation-state-to-image PUBLIC ${ITK_LIBRARIES}) list(APPEND wasm_modules "structured-report-to-text") list(APPEND wasm_modules "structured-report-to-html") list(APPEND wasm_modules "read-dicom-encapsulated-pdf") +list(APPEND wasm_modules "apply-presentation-state-to-image") if (WASI AND DEFINED WebAssemblyInterface_BINARY_DIR) diff --git a/src/io/internal/pipelines/dicom/apply-presentation-state-to-image.cxx b/src/io/internal/pipelines/dicom/apply-presentation-state-to-image.cxx new file mode 100644 index 000000000..b5d51e086 --- /dev/null +++ b/src/io/internal/pipelines/dicom/apply-presentation-state-to-image.cxx @@ -0,0 +1,595 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ +/* + * + * Copyright (C) 1998-2018, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by + * + * OFFIS e.V. + * R&D Division Health + * Escherweg 2 + * D-26121 Oldenburg, Germany + * + * + * Module: dcmpstat + * + * Authors: Joerg Riesmeier, Marco Eichelberg + * + * Purpose + * sample application that reads a DICOM image and (optionally) + * a presentation state and creates a PGM bitmap using the settings + * of the presentation state. Non-grayscale transformations are + * ignored. If no presentation state is loaded, a default is created. + * + */ + +#include "itkPipeline.h" +#include "itkOutputTextStream.h" + +#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ + +#include "dcmtk/ofstd/ofstream.h" +#include "dcmtk/dcmpstat/dviface.h" +#include "dcmtk/dcmpstat/dvpstx.h" /* for DVPSTextObject */ +#include "dcmtk/dcmpstat/dvpsgr.h" /* for DVPSGraphicObject */ +#include "dcmtk/dcmpstat/dvpscu.h" /* for DVPSCurve */ +#include "dcmtk/dcmimgle/dcmimage.h" +#include "dcmtk/dcmdata/cmdlnarg.h" +#include "dcmtk/ofstd/ofcmdln.h" +#include "dcmtk/ofstd/ofconapp.h" +#include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */ + +#ifdef WITH_ZLIB +#include "itk_zlib.h" /* for zlibVersion() */ +#endif + +#define OFFIS_CONSOLE_APPLICATION "dcmp2pgm" + +static OFLogger dcmp2pgmLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION); + +static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v" + OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $"; + + +static void dumpPresentationState(DVPresentationState &ps) +{ + size_t i, j, max; + const char *c; + + OFOStringStream oss; + + oss << "DUMPING PRESENTATION STATE" << OFendl + << "--------------------------" << OFendl << OFendl; + + c = ps.getPresentationLabel(); + oss << "Presentation Label: "; if (c) oss << c << OFendl; else oss << "none" << OFendl; + c = ps.getPresentationDescription(); + oss << "Presentation Description: "; if (c) oss << c << OFendl; else oss << "none" << OFendl; + c = ps.getPresentationCreatorsName(); + oss << "Presentation Creator's Name: "; if (c) oss << c << OFendl; else oss << "none" << OFendl; + + oss << "VOI transformation: "; + if (ps.haveActiveVOIWindow()) + { + double width=0.0, center=0.0; + ps.getCurrentWindowWidth(width); + ps.getCurrentWindowCenter(center); + oss << "window center=" << center << " width=" << width << " description=\""; + c = ps.getCurrentVOIDescription(); + if (c) oss << c << "\"" << OFendl; else oss << "(none)\"" << OFendl; + } + else if (ps.haveActiveVOILUT()) + { + oss << "lut description=\""; + c = ps.getCurrentVOIDescription(); + if (c) oss << c << "\"" << OFendl; else oss << "(none)\"" << OFendl; + } + else oss << "none" << OFendl; + + oss << "Rotation: "; + switch (ps.getRotation()) + { + case DVPSR_0_deg: + oss << "none"; + break; + case DVPSR_90_deg: + oss << "90 degrees"; + break; + case DVPSR_180_deg: + oss << "180 degrees"; + break; + case DVPSR_270_deg: + oss << "270 degrees"; + break; + } + oss << OFendl; + oss << "Flip: "; + if (ps.getFlip()) oss << "yes" << OFendl; else oss << "no" << OFendl; + + Sint32 tlhcX=0; + Sint32 tlhcY=0; + Sint32 brhcX=0; + Sint32 brhcY=0; + oss << "Displayed area:" << OFendl; + + DVPSPresentationSizeMode sizemode = ps.getDisplayedAreaPresentationSizeMode(); + double factor=1.0; + switch (sizemode) + { + case DVPSD_scaleToFit: + oss << " presentation size mode: SCALE TO FIT" << OFendl; + break; + case DVPSD_trueSize: + oss << " presentation size mode: TRUE SIZE" << OFendl; + break; + case DVPSD_magnify: + ps.getDisplayedAreaPresentationPixelMagnificationRatio(factor); + oss << " presentation size mode: MAGNIFY factor=" << factor << OFendl; + break; + } + ps.getStandardDisplayedArea(tlhcX, tlhcY, brhcX, brhcY); + oss << " displayed area TLHC=" << tlhcX << "\\" << tlhcY << " BRHC=" << brhcX << "\\" << brhcY << OFendl; + + double x, y; + if (EC_Normal == ps.getDisplayedAreaPresentationPixelSpacing(x,y)) + { + oss << " presentation pixel spacing: X=" << x << "mm Y=" << y << " mm" << OFendl; + } else { + oss << " presentation pixel aspect ratio: " << ps.getDisplayedAreaPresentationPixelAspectRatio() << OFendl; + } + + oss << "Rectangular shutter: "; + if (ps.haveShutter(DVPSU_rectangular)) + { + oss << "LV=" << ps.getRectShutterLV() + << " RV=" << ps.getRectShutterRV() + << " UH=" << ps.getRectShutterUH() + << " LH=" << ps.getRectShutterLH() << OFendl; + + } else oss << "none" << OFendl; + oss << "Circular shutter: "; + if (ps.haveShutter(DVPSU_circular)) + { + oss << "center=" << ps.getCenterOfCircularShutter_x() + << "\\" << ps.getCenterOfCircularShutter_y() + << " radius=" << ps.getRadiusOfCircularShutter() << OFendl; + } else oss << "none" << OFendl; + oss << "Polygonal shutter: "; + if (ps.haveShutter(DVPSU_polygonal)) + { + oss << "points=" << ps.getNumberOfPolyShutterVertices() << " coordinates="; + j = ps.getNumberOfPolyShutterVertices(); + Sint32 polyX, polyY; + for (i=0; igetText() << "\"" << OFendl; + oss << " anchor point: "; + if (ptext->haveAnchorPoint()) + { + oss << ptext->getAnchorPoint_x() << "\\" << ptext->getAnchorPoint_y() << " units="; + if (ptext->getAnchorPointAnnotationUnits()==DVPSA_display) oss << "display"; else oss << "pixel"; + oss << " visible="; + if (ptext->anchorPointIsVisible()) oss << "yes"; else oss << "no"; + oss << OFendl; + } else oss << "none" << OFendl; + oss << " bounding box: "; + if (ptext->haveBoundingBox()) + { + oss << "TLHC="; + oss << ptext->getBoundingBoxTLHC_x() << "\\" << ptext->getBoundingBoxTLHC_y() + << " BRHC=" << ptext->getBoundingBoxBRHC_x() << "\\" << ptext->getBoundingBoxBRHC_y() + << " units="; + if (ptext->getBoundingBoxAnnotationUnits()==DVPSA_display) oss << "display"; else oss << "pixel"; + + DVPSTextJustification justification = ptext->getBoundingBoxHorizontalJustification(); + oss << " justification="; + switch (justification) + { + case DVPSX_left: + oss << "left"; + break; + case DVPSX_right: + oss << "right"; + break; + case DVPSX_center: + oss << "center"; + break; + } + oss << OFendl; + } else oss << "none" << OFendl; + } + } + + // graphic objects + max = ps.getNumberOfGraphicObjects(layer); + oss << " Number of graphic objects: " << max << OFendl; + DVPSGraphicObject *pgraphic = NULL; + for (size_t graphicidx=0; graphicidxgetNumberOfPoints(); + Float32 fx=0.0, fy=0.0; + for (i=0; igetPoint(i,fx,fy)) + { + oss << fx << "\\" << fy << ", "; + } else oss << "???\\???, "; + } + oss << OFendl; + } + } + + // curve objects + max = ps.getNumberOfCurves(layer); + oss << " Number of activated curves: " << max << OFendl; + DVPSCurve *pcurve = NULL; + for (size_t curveidx=0; curveidxgetCurveAxisUnitsY(); + if (c && (strlen(c)>0)) oss << c << OFendl; else oss << "(none)" << OFendl; + oss << " label="; + c = pcurve->getCurveLabel(); + if (c && (strlen(c)>0)) oss << c << " description="; else oss << "(none) description="; + c = pcurve->getCurveDescription(); + if (c && (strlen(c)>0)) oss << c << OFendl; else oss << "(none)" << OFendl; + oss << " coordinates: "; + j = pcurve->getNumberOfPoints(); + double dx=0.0, dy=0.0; + for (i=0; igetPoint(i,dx,dy)) + { + oss << dx << "\\" << dy << ", "; + } else oss << "???\\???, "; + } + oss << OFendl; + } else oss << " curve " << curveidx+1 << " not present in image." << OFendl; + } + + // overlay objects + const void *overlayData=NULL; + unsigned int overlayWidth=0, overlayHeight=0, overlayLeft=0, overlayTop=0; + OFBool overlayROI=OFFalse; + Uint16 overlayTransp=0; + char overlayfile[100]; + FILE *ofile=NULL; + + max = ps.getNumberOfActiveOverlays(layer); + oss << " Number of activated overlays: " << max << OFendl; + for (size_t ovlidx=0; ovlidx 1) + cmd.getParam(2, opt_pgmName); + + OFLog::configureFromCommandLine(cmd, app); + + opt_dump_pstate = dcmp2pgmLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL); + + if (cmd.findOption("--pstate")) app.checkValue(cmd.getValue(opt_pstName)); + if (cmd.findOption("--config")) app.checkValue(cmd.getValue(opt_cfgName)); + if (cmd.findOption("--frame")) app.checkValue(cmd.getValueAndCheckMin(opt_frame, 1)); + if (cmd.findOption("--pgm")) opt_dicom_mode = OFFalse; + if (cmd.findOption("--dicom")) opt_dicom_mode = OFTrue; + if (cmd.findOption("--save-pstate")) app.checkValue(cmd.getValue(opt_savName)); + } + + /* print resource identifier */ + OFLOG_DEBUG(dcmp2pgmLogger, rcsid << OFendl); + + if (opt_cfgName) + { + FILE *cfgfile = fopen(opt_cfgName, "rb"); + if (cfgfile) fclose(cfgfile); else + { + OFLOG_FATAL(dcmp2pgmLogger, "can't open configuration file '" << opt_cfgName << "'"); + return 10; + } + } + DVInterface dvi(opt_cfgName); + OFCondition status = EC_Normal; + + if (opt_pstName == NULL) + { + OFLOG_DEBUG(dcmp2pgmLogger, "reading DICOM image file: " << opt_imgName); + status = dvi.loadImage(opt_imgName); + } else { + OFLOG_DEBUG(dcmp2pgmLogger, "reading DICOM pstate file: " << opt_pstName); + OFLOG_DEBUG(dcmp2pgmLogger, "reading DICOM image file: " << opt_imgName); + status = dvi.loadPState(opt_pstName, opt_imgName); + } + + if (status == EC_Normal) + { + if (opt_dump_pstate) dumpPresentationState(dvi.getCurrentPState()); + if (opt_pgmName != NULL) + { + const void *pixelData = NULL; + unsigned long width = 0; + unsigned long height = 0; + OFLOG_DEBUG(dcmp2pgmLogger, "creating pixel data"); + if ((opt_frame > 0) && (dvi.getCurrentPState().selectImageFrameNumber(opt_frame) != EC_Normal)) + OFLOG_ERROR(dcmp2pgmLogger, "cannot select frame " << opt_frame); + if ((dvi.getCurrentPState().getPixelData(pixelData, width, height) == EC_Normal) && (pixelData != NULL)) + { + if (opt_dicom_mode) + { + double pixelAspectRatio = dvi.getCurrentPState().getPrintBitmapPixelAspectRatio(); // works for rotated images + OFLOG_DEBUG(dcmp2pgmLogger, "writing DICOM SC file: " << opt_pgmName); + if (EC_Normal != dvi.saveDICOMImage(opt_pgmName, pixelData, width, height, pixelAspectRatio)) + { + OFLOG_ERROR(dcmp2pgmLogger, "error during creation of DICOM file"); + } + } else { + FILE *outfile = fopen(opt_pgmName, "wb"); + if (outfile) + { + OFLOG_DEBUG(dcmp2pgmLogger, "writing PGM file: " << opt_pgmName); + fprintf(outfile, "P5\n%ld %ld 255\n", width, height); + if (fwrite(pixelData, OFstatic_cast(size_t, width), OFstatic_cast(size_t, height), outfile) != OFstatic_cast(size_t, height)) + OFLOG_FATAL(dcmp2pgmLogger, "Can't write output data to file."); + fclose(outfile); + } else { + OFLOG_FATAL(dcmp2pgmLogger, "Can't create output file."); + return 10; + } + } + } else { + OFLOG_FATAL(dcmp2pgmLogger, "Can't create output data."); + return 10; + } + } + if (opt_savName != NULL) + { + OFLOG_DEBUG(dcmp2pgmLogger, "writing pstate file: " << opt_savName); + if (dvi.savePState(opt_savName, OFFalse) != EC_Normal) + { + OFLOG_FATAL(dcmp2pgmLogger, "Can't write pstate file."); + return 10; + } + } + } else { + OFLOG_FATAL(dcmp2pgmLogger, "Can't open input file(s)."); + return 10; + } + +#ifdef DEBUG + dcmDataDict.clear(); /* useful for debugging with dmalloc */ +#endif + + return (status != EC_Normal); +}