Repository: conan-io/conan-center-index

Issue:
[request] gdcm/3.0.7
### Package Details
  * Package Name/Version: **gdcm/3.0.7**
  * Website: **https://http://gdcm.sourceforge.net/**
  * Source code: **https://github.com/malaterre/GDCM**


### Description Of The Library / Tool
A library for loading / manipulating DICOM images, similar to DCMTK. I've created a rule already over here: https://github.com/feliwir/conan-gdcm
@Croydon suggested that this is a better place than the bincrafters repository.

With best regards



Discussion:
Maybe this is useful: https://git.ircad.fr/conan/conan-gdcm
| Library | In CCI |
| --- | --- |
| [poppler](https://github.com/freedesktop/poppler) | :x: (Optional) |
| libjpeg | :heavy_check_mark: |
| openjpeg | :heavy_check_mark: |
| charls | :heavy_check_mark: |
| zlib | :heavy_check_mark: |
| openssl | :heavy_check_mark: |
| uuid | :heavy_check_mark: |
| expat | :heavy_check_mark: |
| papyrus3 | :x: (Optional) |
| json-c | :heavy_check_mark: |
| [socketxx](https://github.com/hstraub/socketxx) | :x: |
@intelligide i think all of these are optional dependencies
socketxx is required by MessageExchangeDefinition (gdcmMEXD).
poppler and papyrus3 are optional, you're right.
@intelligide well it does also compile without socketxx, i tested that for my rule.
git.ircad.fr link to conan-gdcm requires logging in.

I've tried creating recipe for gdcm/3.0.9 from scratch. Unfinished recipe can be found here if anyone would like to use it as starting point https://github.com/Nekto89/conan-center-index/commit/3fe0097af36b40ce7da4f27fea7a344b322ba541

One of the problems: conan-center-index has recipe only for charls/2.1.0. gdcm currently doesn't support any versions except 2.0.0 - it seems to be using deprecated methods so I don't know what will be the consequences even if compilation will be fixed through patches.

Another problem: I don't think that libjpeg can be reused from cci. gdcm has patched version that allows building multiple versions of libjpeg (with 8/12/16-bit support) and using them simultaneously with mangled prefixes for all methods. If I understood correctly, version 6b was used for this.


Resolving patch:
diff --git a/recipes/gdcm/all/conanfile.py b/recipes/gdcm/all/conanfile.py
new file mode 100644
--- /dev/null
+++ b/recipes/gdcm/all/conanfile.py
@@ -0,0 +1,220 @@
+from conans import ConanFile, CMake, tools
+from conans.errors import ConanInvalidConfiguration
+import os
+import textwrap
+
+required_conan_version = ">=1.33.0"
+
+class GDCMConan(ConanFile):
+    name = "gdcm"
+    topics = ("dicom", "images")
+    homepage = "http://gdcm.sourceforge.net/"
+    url = "https://github.com/conan-io/conan-center-index"
+    license = "BSD-3-Clause"
+    description = "C++ library for DICOM medical files"
+    exports_sources = "CMakeLists.txt", "patches/**"
+    settings = "os", "arch", "compiler", "build_type"
+    options = {
+        "shared": [True, False],
+        "fPIC": [True, False],
+    }
+    default_options = {
+        "shared": False,
+        "fPIC": True,
+    }
+    generators = "cmake", "cmake_find_package"
+
+    _cmake = None
+
+    @property
+    def _source_subfolder(self):
+        return "source_subfolder"
+
+    @property
+    def _build_subfolder(self):
+        return "build_subfolder"
+
+    def config_options(self):
+        if self.settings.os == "Windows":
+            del self.options.fPIC
+
+    def configure(self):
+        if self.options.shared:
+            del self.options.fPIC
+
+    def validate(self):
+        if (self.settings.compiler == "Visual Studio"
+                and self.options.shared
+                and str(self.settings.compiler.runtime).startswith("MT")):
+            raise ConanInvalidConfiguration("shared gdcm can't be built with MT or MTd")
+        if self.settings.compiler.cppstd:
+            tools.check_min_cppstd(self, "11")
+
+    def requirements(self):
+        self.requires("expat/2.4.1")
+        self.requires("openjpeg/2.4.0")
+        self.requires("zlib/1.2.11")
+
+    def source(self):
+        tools.get(**self.conan_data["sources"][self.version],
+                  destination=self._source_subfolder, strip_root=True)
+
+    def _patch_sources(self):
+        for patch in self.conan_data.get("patches", {}).get(self.version, []):
+            tools.patch(**patch)
+
+    def _configure_cmake(self):
+        if self._cmake:
+            return self._cmake
+
+        self._cmake = CMake(self)
+        self._cmake.definitions["GDCM_BUILD_DOCBOOK_MANPAGES"] = False
+        self._cmake.definitions["GDCM_BUILD_SHARED_LIBS"] = self.options.shared
+        # FIXME: unvendor deps https://github.com/conan-io/conan-center-index/pull/5705#discussion_r647224146
+        self._cmake.definitions["GDCM_USE_SYSTEM_EXPAT"] = True
+        self._cmake.definitions["GDCM_USE_SYSTEM_OPENJPEG"] = True
+        self._cmake.definitions["GDCM_USE_SYSTEM_ZLIB"] = True
+
+        self._cmake.configure(build_folder=self._build_subfolder)
+        return self._cmake
+
+    def build(self):
+        self._patch_sources()
+        cmake = self._configure_cmake()
+        cmake.build()
+
+    def package(self):
+        self.copy("Copyright.txt", dst="licenses", src=self._source_subfolder)
+        cmake = self._configure_cmake()
+        cmake.install()
+        if self.settings.os == "Windows":
+            bin_dir = os.path.join(self.package_folder, "bin")
+            tools.remove_files_by_mask(bin_dir, "[!gs]*.dll")
+            tools.remove_files_by_mask(bin_dir, "*.pdb")
+        lib_dir = os.path.join(self.package_folder, "lib")
+        tools.rmdir(os.path.join(self.package_folder, "share"))
+        tools.remove_files_by_mask(os.path.join(lib_dir, self._gdcm_subdir), "[!U]*.cmake") #leave UseGDCM.cmake untouched
+        self._create_cmake_module_alias_targets(
+            os.path.join(self.package_folder, self._gdcm_cmake_module_aliases_path),
+            self._gdcm_libraries
+        )
+        self._create_cmake_variables(os.path.join(self.package_folder, self._gdcm_cmake_variables_path))
+
+    def _create_cmake_variables(self, variables_file):
+        v = tools.Version(self.version)
+        content = textwrap.dedent("""\
+            # The GDCM version number.
+            set(GDCM_MAJOR_VERSION "{v_major}")
+            set(GDCM_MINOR_VERSION "{v_minor}")
+            set(GDCM_BUILD_VERSION "{v_patch}")
+
+            get_filename_component(SELF_DIR "${{CMAKE_CURRENT_LIST_FILE}}" PATH)
+
+            # The libraries.
+            set(GDCM_LIBRARIES "")
+
+            # The CMake macros dir.
+            set(GDCM_CMAKE_DIR "")
+
+            # The configuration options.
+            set(GDCM_BUILD_SHARED_LIBS "{build_shared_libs}")
+
+            set(GDCM_USE_VTK "OFF")
+
+            # The "use" file.
+            set(GDCM_USE_FILE ${{SELF_DIR}}/UseGDCM.cmake)
+
+            # The VTK options.
+            set(GDCM_VTK_DIR "")
+
+            get_filename_component(GDCM_INCLUDE_ROOT "${{SELF_DIR}}/../../include/{gdcm_subdir}" ABSOLUTE)
+            set(GDCM_INCLUDE_DIRS ${{GDCM_INCLUDE_ROOT}})
+            get_filename_component(GDCM_LIB_ROOT "${{SELF_DIR}}/../../lib" ABSOLUTE)
+            set(GDCM_LIBRARY_DIRS ${{GDCM_LIB_ROOT}})
+        """.format(v_major=v.major,
+                   v_minor=v.minor,
+                   v_patch=v.patch,
+                   build_shared_libs="ON" if self.options.shared else "OFF",
+                   gdcm_subdir=self._gdcm_subdir))
+        tools.save(variables_file, content)
+
+    @staticmethod
+    def _create_cmake_module_alias_targets(targets_file, libraries):
+        content = "\n".join([textwrap.dedent("""\
+            if(TARGET {aliased} AND NOT TARGET {alias})
+                add_library({alias} INTERFACE IMPORTED)
+                set_property(TARGET {alias} PROPERTY INTERFACE_LINK_LIBRARIES {aliased})
+            endif()
+            """.format(alias=library, aliased="GDCM::"+library)) for library in libraries])
+        tools.save(targets_file, content)
+
+    @property 
+    def _gdcm_subdir(self):
+        v = tools.Version(self.version)
+        return "gdcm-{}.{}".format(v.major, v.minor)
+
+    @property
+    def _gdcm_builddir(self):
+        return os.path.join("lib", self._gdcm_subdir)
+
+    @property
+    def _gdcm_cmake_module_aliases_path(self):
+        return os.path.join("lib", self._gdcm_subdir, "conan-official-gdcm-targets.cmake")
+
+    @property
+    def _gdcm_cmake_variables_path(self):
+        return os.path.join("lib", self._gdcm_subdir, "conan-official-gdcm-variables.cmake")
+
+    @property
+    def _gdcm_build_modules(self):
+        return [self._gdcm_cmake_module_aliases_path, self._gdcm_cmake_variables_path]
+
+    @property
+    def _gdcm_libraries(self):
+        gdcm_libs = ["gdcmcharls",
+                     "gdcmCommon",
+                     "gdcmDICT",
+                     "gdcmDSED",
+                     "gdcmIOD",
+                     "gdcmjpeg12",
+                     "gdcmjpeg16",
+                     "gdcmjpeg8",
+                     "gdcmMEXD",
+                     "gdcmMSFF",
+                     "socketxx"]
+        if self.settings.os == "Windows":
+            gdcm_libs.append("gdcmgetopt")
+        else:
+            gdcm_libs.append("gdcmuuid")
+        return gdcm_libs
+
+    def package_info(self):
+        self.cpp_info.names["cmake_find_package"] = "GDCM"
+        self.cpp_info.names["cmake_find_multi_package"] = "GDCM"
+        gdcm_libs = self._gdcm_libraries
+        for lib in gdcm_libs:
+            self.cpp_info.components[lib].libs = [lib]
+            self.cpp_info.components[lib].includedirs = [os.path.join("include", self._gdcm_subdir)]
+            self.cpp_info.components[lib].builddirs = [self._gdcm_builddir] 
+            self.cpp_info.components[lib].build_modules["cmake"] = self._gdcm_build_modules
+            self.cpp_info.components[lib].build_modules["cmake_find_package"] = self._gdcm_build_modules
+            self.cpp_info.components[lib].build_modules["cmake_find_multi_package"] = self._gdcm_build_modules
+
+        self.cpp_info.components["gdcmDSED"].requires.extend(["gdcmCommon", "zlib::zlib"])
+        self.cpp_info.components["gdcmIOD"].requires.extend(["gdcmDSED", "gdcmCommon", "expat::expat"])
+        self.cpp_info.components["gdcmMSFF"].requires.extend(["gdcmIOD", "gdcmDSED", "gdcmDICT", "openjpeg::openjpeg"])
+        if not self.options.shared:
+            self.cpp_info.components["gdcmDICT"].requires.extend(["gdcmDSED", "gdcmIOD"])
+            self.cpp_info.components["gdcmMEXD"].requires.extend(["gdcmMSFF", "gdcmDICT", "gdcmDSED", "gdcmIOD", "socketxx"])
+            self.cpp_info.components["gdcmMSFF"].requires.extend(["gdcmjpeg8", "gdcmjpeg12", "gdcmjpeg16", "gdcmcharls"])
+
+            if self.settings.os == "Windows":
+                self.cpp_info.components["gdcmCommon"].system_libs = ["ws2_32", "crypt32"]
+                self.cpp_info.components["gdcmMSFF"].system_libs = ["rpcrt4"]
+                self.cpp_info.components["socketxx"].system_libs = ["ws2_32"]
+            else:
+                self.cpp_info.components["gdcmMSFF"].requires.append("gdcmuuid")
+
+                self.cpp_info.components["gdcmCommon"].system_libs = ["dl"]
+                if tools.is_apple_os(self.settings.os):
+                    self.cpp_info.components["gdcmCommon"].frameworks = ["CoreFoundation"]


Test patch:
diff --git a/recipes/gdcm/all/test_package/CMakeLists.txt b/recipes/gdcm/all/test_package/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/recipes/gdcm/all/test_package/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 3.9.2)
+project(test_package)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD)
+    set(CMAKE_CXX_STANDARD 11)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup(TARGETS)
+
+find_package(GDCM REQUIRED)
+
+add_executable(${PROJECT_NAME} test_package.cpp)
+include(${GDCM_USE_FILE})
+target_link_libraries(${PROJECT_NAME} gdcmMSFF)
diff --git a/recipes/gdcm/all/test_package/DCMTK_JPEGExt_12Bits.dcm b/recipes/gdcm/all/test_package/DCMTK_JPEGExt_12Bits.dcm
new file mode 100644
Binary files /dev/null and b/recipes/gdcm/all/test_package/DCMTK_JPEGExt_12Bits.dcm differ
diff --git a/recipes/gdcm/all/test_package/conanfile.py b/recipes/gdcm/all/test_package/conanfile.py
new file mode 100644
--- /dev/null
+++ b/recipes/gdcm/all/test_package/conanfile.py
@@ -0,0 +1,20 @@
+from conans import ConanFile, CMake, tools
+import os
+
+class GDCMTestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "cmake", "cmake_find_package"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def test(self):
+        if not tools.cross_building(self.settings):
+            bin_path = os.path.join("bin", "test_package")
+            input_file = os.path.join(self.source_folder, "DCMTK_JPEGExt_12Bits.dcm")
+            test_dir = "test_dir"
+            tools.mkdir(test_dir)
+            output_file = os.path.join(test_dir, "output.dcm")
+            self.run([bin_path, input_file, output_file], run_environment=True)
diff --git a/recipes/gdcm/all/test_package/test_package.cpp b/recipes/gdcm/all/test_package/test_package.cpp
new file mode 100644
--- /dev/null
+++ b/recipes/gdcm/all/test_package/test_package.cpp
@@ -0,0 +1,78 @@
+/*=========================================================================
+
+  Program: GDCM (Grassroots DICOM). A DICOM library
+
+  Copyright (c) 2006-2011 Mathieu Malaterre
+  All rights reserved.
+  See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+/*
+ * This example is ... guess what this is for :)
+ */
+
+#include "gdcmReader.h"
+#include "gdcmWriter.h"
+#include "gdcmAttribute.h"
+
+#include <iostream>
+
+int main(int argc, char* argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << argv[0] << " input.dcm output.dcm" << std::endl;
+    return 1;
+  }
+  const char* filename = argv[1];
+  const char* outfilename = argv[2];
+
+  // Instanciate the reader:
+  gdcm::Reader reader;
+  reader.SetFileName(filename);
+  if (!reader.Read())
+  {
+    std::cerr << "Could not read: " << filename << std::endl;
+    return 1;
+  }
+
+  // If we reach here, we know for sure only 1 thing:
+  // It is a valid DICOM file (potentially an old ACR-NEMA 1.0/2.0 file)
+  // (Maybe, it's NOT a Dicom image -could be a DICOMDIR, a RTSTRUCT, etc-)
+
+  // The output of gdcm::Reader is a gdcm::File
+  gdcm::File& file = reader.GetFile();
+
+  // the dataset is the the set of element we are interested in:
+  gdcm::DataSet& ds = file.GetDataSet();
+
+  // Contruct a static(*) type for Image Comments :
+  gdcm::Attribute<0x0020, 0x4000> imagecomments;
+  imagecomments.SetValue("Hello, World !");
+
+  // Now replace the Image Comments from the dataset with our:
+  ds.Replace(imagecomments.GetAsDataElement());
+
+  // Write the modified DataSet back to disk
+  gdcm::Writer writer;
+  writer.CheckFileMetaInformationOff(); // Do not attempt to reconstruct the file meta to preserve the file
+                                        // as close to the original as possible.
+  writer.SetFileName(outfilename);
+  writer.SetFile(file);
+  if (!writer.Write())
+  {
+    std::cerr << "Could not write: " << outfilename << std::endl;
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+ * (*) static type, means that extra DICOM information VR & VM are computed at compilation time.
+ * The compiler is deducing those values from the template arguments of the class.
+ */