ENH: Add static ImageFileWriter<>::WriteImage(image, fileName) member function#2102
Conversation
Added an `itk::ImageFileWriter<>` specialization that has a static `WriteImage(image, fileName)` member function. Allows writing an image more easily.
Reduced boilerplate code by calling the static `itk::ImageFileWriter<>` member function to write an image.
4f5ab88 to
7c0cd30
Compare
| */ | ||
| template <typename TInputImage> | ||
| static void | ||
| WriteImage(const TInputImage & image, const std::string & fileName) |
There was a problem hiding this comment.
?? Should we expose other conditional behaviors as default options?
UseCompression = false, CompressionLevel = -1, UseINputMetaDataDictionary=true)
There was a problem hiding this comment.
Good question. The aim of this PR is to make the most common use case a simple "one-liner".
There was a problem hiding this comment.
My solution does not change the most common use case, but does allow for more flexibility easily for those who need it. The UseCompression is the issue I am most interested in maintaining access to.
There was a problem hiding this comment.
itk::ImageFileWriter<ImageType> is being used by ITK itself more than 500 times. I wonder how many times (%) they just assume the default values for those options.
There was a problem hiding this comment.
Internal to ITK defaults are used almost 100% of the time.
In my applications, and many of the applications that I work on, defaults are used near 0% of the time because I explicitly want my applications to use compression for cost storage reasons.
I love you code simplification solution, and would implement it's used in my applications as long as I don't lose the ability to compress data.
There was a problem hiding this comment.
Of course, extra options could still be added as function parameters with default argument values. (Possibly afterwards.)
Another redesign could be to add those options as data members to the new itk::ImageFileWriter<void>, very much like the original ImageFileWriter<ImageType>, but then still support a "one-liner" like:
itk::ImageFileWriter<>{}.WriteImage(image, fileName);
Which would default-construct a writer (containing default values for its options), and then use it directly, calling a non-static WriteImage member function that takes the options into account. Or classic ITK New() style:
itk::ImageFileWriter<>::New()->WriteImage(image, fileName);
For the moment I still prefer the current PR (just a static member function), possible with some optional default arguments. What do you think?
There was a problem hiding this comment.
If the proposed option is implemented UseCompression = false, CompressionLevel = -1, UseINputMetaDataDictionary=true then we will have calls like:
itk::ImageFileWriter<void>::WriteImage(image, "something.ext", true, 9, true);
In C++ the unnamed parameters are not clear what the argument position means.
Perhaps adding a WriteImageWithCompression function would satisfy this added requirements?
I prefer to have two overloads, one for raw pointer one for smart pointer.
There should be implicit conversions from smart pointers to raw pointers, therefor just a method with a raw pointer should work.
This is a new style for ITK, we have not had function like calls before for process object. An alternative for the void template parameters would be to place the template function(s) into a namespace.
There was a problem hiding this comment.
There should be implicit conversions from smart pointers to raw pointers
Maybe, but if there is no exact match template arguments cannot be deduced.
place the template function(s) into a namespace
I like it! So we could have:
#include "ImageFileWriter.h"
...
itk::WriteImage(image, fileName);
itk::WriteImage(filter->GetOutput(), fileName2, true); // compressedI like default UseCompression = false better than WriteImageWithCompression, but I could live with the latter too.
There was a problem hiding this comment.
I like the idea of having a WriteImage in itk namespace, rather than as a static member of itk::ImageFileWriter<void>. The only (minor) drawback could be that it might be less obvious where to define itk::WriteImage. Should it then be in its own "itkWriteImage.h"?
There was a problem hiding this comment.
It would have to include itkImageFileWriter.h anyway, so a separate include file would reduce its convenience somewhat. @blowekamp what is your preference?
| */ | ||
| template <typename TInputImage> | ||
| static void | ||
| WriteImage(const TInputImage & image, const std::string & fileName) |
There was a problem hiding this comment.
I prefer to have two overloads, one for raw pointer one for smart pointer. That way we can just provide image variable name at the point of invocation, see dzenanz/ITKMorphologicalContourInterpolation@a9ac128#diff-2c8d1766ca920beada092bae39ef4684594b715a9eb835bcc35ad15998b2c86aR1339.
Note that itk::Image<bool, dim> needs a distinct overload with my approach. As that is not commonly used, I am not sure whether we want to support it here.
| writer->SetInput(dupImage); | ||
| writer->SetFileName(argv[2]); | ||
| writer->Update(); | ||
| itk::ImageFileWriter<>::WriteImage(*dupImage, argv[2]); |
There was a problem hiding this comment.
I don't like having to write *varName, I prefer just varName. See declaration suggestion on how to accomplish this.
There was a problem hiding this comment.
My proposal was to use an image reference, rather than a pointer, because a pointer in C++ is used to indicate "might be null". So I wonder, why would someone want to call WriteImage with a (possible) null pointer as image?
A smart pointer is specifically used to indicate that the ownership on an object is shared between entities. It does make sense to share with an ("old style") ImageFileWriter<ImageType> object, but I wonder if it makes sense to share with a static member function call.
That's why I'm reluctant to declare the image parameter as a pointer. But I do see your point that it is cumbersome to always add a *.
There was a problem hiding this comment.
@N-Dekker If a user has pointer which might be null, and wants to print it if it is not null, he will have to write an if. And if we require a reference, he will have to write an if before converting it to a reference. As all ITK images are held through pointers or smart pointers, accepting those is more natural than expecting a reference. I don't see much practical difference between the two, except that we will always have to add * during invocation with the reference approach.
There was a problem hiding this comment.
@dzenanz Just a few links to the C++ Core Guidelines to clarify my reluctance to declare the image as a pointer (smart or raw pointer):
"Take smart pointers as parameters only to explicitly express lifetime semantics"
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#Rr-smartptrparam
"For general use, take T* or T& arguments rather than smart pointers"
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#f7-for-general-use-take-t-or-t-arguments-rather-than-smart-pointers
"For “in” parameters, pass cheaply-copied types by value and others by reference to const"
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#f7-for-general-use-take-t-or-t-arguments-rather-than-smart-pointers
"Prefer T* over T& when “no argument” is a valid option"
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#f60-prefer-t-over-t-when-no-argument-is-a-valid-option
Now I know that there are a lot of old ITK (member) functions that have pointers as argument types. But for a newly added function maybe we could move forward to use a reference as argument type instead...
There was a problem hiding this comment.
We would be explicitly taking itk::SmartPointer<Image<...>> in order to aid template parameter deduction. And primary specialization would be taking Image *. And if you want to be super-correct, you can check for null to avoid crashing.
As we can only hold a (smart) pointer to an ITK image in a variable, this is what we should be passing around. Entire ITK API takes either Image * or Image::Pointer (aka itk::SmartPointer<Image<...>>). Trying to switch one function to taking an Image & seems unnecessary and would make its use harder (specifically having to dereference image at each place of invocation).
There was a problem hiding this comment.
And as you might guess, refactoring ITK to change convention from passing Image* to passing Image& is a huge task which breaks backwards compatibility with little tangible gain.
There was a problem hiding this comment.
And if you want to be super-correct, you can check for null to avoid crashing.
This function would likely be used for debugging, so it would be good add a null check and a check that the image has been allocated.
There are other issues with the wrapping to use raw pointers instead of references.
|
Excellent idea Niels! After this is in, I won't have to make this convenience function in almost each project I write! |
Thanks @dzenanz. Indeed it wouldn't be the first effort to get a simple |
|
Should we add a convenience |
Sounds very useful. Moreover, I would very much like to have a convenience |
|
Maybe we should have this for v5.2.0? There are already similar convenience functions in Python. Would the new C++ functions interfere with that? |
I don't think they would interfere. But, we should use the raw pointer in the interface to avoid wrapping issues. |
|
Please feel free to make a new pull request that has an interface that you prefer. |
|
My preferred API is in #2160. Please review. |
|
Hans merged it. Yes, another review is still welcome. I could make changes in a new PR. |
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: InsightSoftwareConsortium#2160 Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: InsightSoftwareConsortium#2160 Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: InsightSoftwareConsortium#2160 Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: InsightSoftwareConsortium#2160 Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at #2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: #2160 Commit: 60b0984 Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added `itk::ReadImage<TOutputImage>(const std::string & filename)`, to allows simplifying the C++ code for reading an image. Suggested by Dženan Zukić at InsightSoftwareConsortium#2102 (comment) Follow-up to: "ENH: add a convenience function WriteImage" Pull request: InsightSoftwareConsortium#2160 Commit: add59ae Replaced manual creation of `ImageFileReader` objects in Common tests by calls to the new `itk::ReadImage` function. Also removed related redundant try-catch blocks from SymmetricSecondRankTensorImage tests.
Added
ImageFileWriter<>specialization that has a staticWriteImage(image, fileName)member function. Introduced in various tests in "Modules/Core/Common/test"