Skip to content

Commit 0175c88

Browse files
committed
ENH: Add black style pre-commit hook
Black is installed by the Superbuild and run on all changed Python files during commit. A system version can also be used or configured with the `black.executable` git configuration value.
1 parent 8bcc38b commit 0175c88

File tree

6 files changed

+147
-61
lines changed

6 files changed

+147
-61
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*.txx our-c-style
99
*.txt whitespace=tab-in-indent,no-lf-at-eof
1010
*.cmake whitespace=tab-in-indent,no-lf-at-eof
11+
*.py hooks.style=black
1112

1213
# ExternalData content links must have LF newlines
1314
*.md5 crlf=input

Superbuild/External-Python.cmake

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ set(_itk_venv "${CMAKE_CURRENT_BINARY_DIR}/itkpython")
44
if(WIN32)
55
set(ITKPYTHON_EXECUTABLE "${_itk_venv}/python.exe" CACHE FILEPATH "Python executable with the ITK package installed" FORCE)
66
set(SPHINX_EXECUTABLE "${_itk_venv}/Scripts/sphinx-build.exe" CACHE FILEPATH "Sphinx executable" FORCE)
7+
set(BLACK_EXECUTABLE "${_itk_venv}/Scripts/black.exe" CACHE FILEPATH "black executable" FORCE)
78
else()
89
set(ITKPYTHON_EXECUTABLE "${_itk_venv}/bin/python" CACHE FILEPATH "Python executable with the ITK package installed" FORCE)
910
set(SPHINX_EXECUTABLE "${_itk_venv}/bin/sphinx-build" CACHE FILEPATH "Sphinx executable" FORCE)
11+
set(BLACK_EXECUTABLE "${_itk_venv}/bin/black" CACHE FILEPATH "black executable" FORCE)
1012
endif()
13+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ITKBlackConfig.cmake.in"
14+
"${CMAKE_CURRENT_BINARY_DIR}/ITKBlackConfig.cmake" @ONLY)
1115

1216
ExternalProject_Add(ITKPython
1317
DOWNLOAD_COMMAND ""
1418
CONFIGURE_COMMAND ${PYTHON_EXECUTABLE} -m venv "${_itk_venv}"
1519
BUILD_COMMAND ${ITKPYTHON_EXECUTABLE} -m pip install --upgrade pip
16-
INSTALL_COMMAND ${ITKPYTHON_EXECUTABLE} -m pip install --ignore-installed itk>=5.2rc3 sphinx==3.0.4 six
20+
INSTALL_COMMAND ${ITKPYTHON_EXECUTABLE} -m pip install --ignore-installed itk>=5.2rc3 sphinx==3.0.4 six black
21+
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/ITKBlackConfig.cmake
1722
)

Superbuild/ITKBlackConfig.cmake.in

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
set(ITKExamples_SOURCE_DIR "@ITKExamples_SOURCE_DIR@")
2+
set(CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@")
3+
set(BLACK_EXECUTABLE "@BLACK_EXECUTABLE@")
4+
5+
set(WORKING_DIR "")
6+
if(ITKExamples_SOURCE_DIR)
7+
set(WORKING_DIR "${ITKExamples_SOURCE_DIR}/..")
8+
else()
9+
set(WORKING_DIR "${CMAKE_SOURCE_DIR}/..")
10+
endif()
11+
12+
find_package(Git)
13+
if(GIT_FOUND AND EXISTS "${WORKING_DIR}/.git/config")
14+
execute_process(COMMAND ${GIT_EXECUTABLE} config black.executable
15+
"${BLACK_EXECUTABLE}"
16+
WORKING_DIRECTORY ${WORKING_DIR})
17+
endif()

Utilities/Hooks/pre-commit-style.bash

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
#=============================================================================
1616

17-
# Run clangformat and KWStyle pre-commit hooks.
17+
# Run clangformat, KWStyle, black pre-commit hooks.
1818
#
1919
# 'git config' is used to enable the hooks and set their configuration files.
2020
# The repository .gitattributes must also enable the hooks on the targeted
@@ -32,6 +32,8 @@ do_KWStyle=$(git config --bool hooks.KWStyle) || do_KWStyle=false
3232

3333
do_clangformat=$(git config --bool hooks.clangformat) || do_clangformat=true
3434

35+
do_black=$(git config --bool hooks.blackformat) || do_blackformat=true
36+
3537
#-----------------------------------------------------------------------------
3638
# Check if we want to run the style on a given file. Uses git attributes. If
3739
# the hook.style attribute is set, then all styles are executed. If the
@@ -164,8 +166,8 @@ check_for_clangformat() {
164166
clangformat_path=$(type -p "$system_clang_format" >/dev/null) ||
165167
die "clang-format executable was not found.
166168
167-
A clang-format binary will be downloaded and configured when ITK
168-
is built with the BUILD_TESTING CMake configuration option enabled.
169+
A clang-format binary will be downloaded and configured when the ITKExamples
170+
Superbuild is built with the BUILD_TESTING CMake configuration option enabled.
169171
170172
Alternatively, install clang-format version $clangformat_required_version or set the executable location with
171173
@@ -292,25 +294,81 @@ For more information, see
292294
done # end for changed files
293295
}
294296

297+
#-----------------------------------------------------------------------------
298+
# black.
299+
check_for_black() {
300+
system_black=""
301+
if type -p "black" >/dev/null; then
302+
system_black="black"
303+
fi
304+
black_path=$(git config black.executable) ||
305+
black_path=$(type -p "$system_black" >/dev/null) ||
306+
die "black executable was not found.
307+
308+
A black Python formatting tool will be downloaded and configured when the ITKExamples
309+
Superbuild is built with the BUILD_TESTING CMake configuration option enabled.
310+
311+
Alternatively, install black with:
312+
313+
python -m pip install black
314+
315+
and set the executable location with
316+
317+
git config black.executable /path/to/black
318+
319+
if it is not in the system's PATH.
320+
"
321+
}
322+
323+
run_black_on_file() {
324+
"$black_path" "$1"
325+
326+
if test $? -ne 0; then
327+
die "black style application failed."
328+
fi
329+
return 0
330+
}
331+
332+
run_black() {
333+
$do_black && check_for_black
334+
if test $?; then
335+
have_black=true
336+
else
337+
have_black=false
338+
fi
339+
340+
git diff-index --cached --diff-filter=ACMR --name-only HEAD -- |
341+
while read f; do
342+
if run_style_on_file "$f" "black"; then
343+
run_black_on_file "$f"
344+
fi || return
345+
done
346+
}
347+
295348
# Do not run during merge commits for now.
296349
if test -f "$GIT_DIR/MERGE_HEAD"; then
297350
:
298-
elif $do_clangformat; then
299-
# We use git-mergetool settings to review the clangformat changes.
300-
TOOL_MODE=merge
301-
. "$(git --exec-path)/git-mergetool--lib"
302-
# Redefine check_unchanged because we do not need to check if the merge was
303-
# successful.
304-
check_unchanged() {
305-
status=0
306-
}
307-
check_for_clangformat
308-
run_clangformat || exit 1
309-
# do_clangformat will run KWStyle on the files incrementally so excessive
310-
# clangformat merges do not have to occur.
311-
elif $do_KWStyle; then
312-
if check_for_KWStyle; then
313-
run_KWStyle || exit 1
351+
else
352+
if $do_clangformat; then
353+
# We use git-mergetool settings to review the clangformat changes.
354+
TOOL_MODE=merge
355+
. "$(git --exec-path)/git-mergetool--lib"
356+
# Redefine check_unchanged because we do not need to check if the merge was
357+
# successful.
358+
check_unchanged() {
359+
status=0
360+
}
361+
check_for_clangformat
362+
run_clangformat || exit 1
363+
# do_clangformat will run KWStyle on the files incrementally so excessive
364+
# clangformat merges do not have to occur.
365+
elif $do_KWStyle; then
366+
if check_for_KWStyle; then
367+
run_KWStyle || exit 1
368+
fi
369+
fi
370+
if $do_black; then
371+
run_black || exit 1
314372
fi
315373
fi
316374

src/Segmentation/LevelSets/SegmentWithGeodesicActiveContourLevelSet/Code.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
print(
2222
"Usage: " + sys.argv[0] + "<InputFileName> <OutputFileName>"
2323
" <seedX> <seedY> <InitialDistance> <Sigma> <SigmoidAlpha> "
24-
"<SigmoidBeta> <PropagationScaling> <NumberOfIterations>")
24+
"<SigmoidBeta> <PropagationScaling> <NumberOfIterations>"
25+
)
2526
sys.exit(1)
2627

2728
inputFileName = sys.argv[1]
29+
30+
2831
outputFileName = sys.argv[2]
2932
seedPosX = int(sys.argv[3])
3033
seedPosY = int(sys.argv[4])
@@ -52,15 +55,17 @@
5255
reader.SetFileName(inputFileName)
5356

5457
SmoothingFilterType = itk.CurvatureAnisotropicDiffusionImageFilter[
55-
InputImageType, InputImageType]
58+
InputImageType, InputImageType
59+
]
5660
smoothing = SmoothingFilterType.New()
5761
smoothing.SetTimeStep(0.125)
5862
smoothing.SetNumberOfIterations(5)
5963
smoothing.SetConductanceParameter(9.0)
6064
smoothing.SetInput(reader.GetOutput())
6165

6266
GradientFilterType = itk.GradientMagnitudeRecursiveGaussianImageFilter[
63-
InputImageType, InputImageType]
67+
InputImageType, InputImageType
68+
]
6469
gradientMagnitude = GradientFilterType.New()
6570
gradientMagnitude.SetSigma(sigma)
6671
gradientMagnitude.SetInput(smoothing.GetOutput())
@@ -73,12 +78,12 @@
7378
sigmoid.SetBeta(beta)
7479
sigmoid.SetInput(gradientMagnitude.GetOutput())
7580

76-
FastMarchingFilterType = itk.FastMarchingImageFilter[
77-
InputImageType, InputImageType]
81+
FastMarchingFilterType = itk.FastMarchingImageFilter[InputImageType, InputImageType]
7882
fastMarching = FastMarchingFilterType.New()
7983

8084
GeoActiveContourFilterType = itk.GeodesicActiveContourLevelSetImageFilter[
81-
InputImageType, InputImageType, InputPixelType]
85+
InputImageType, InputImageType, InputPixelType
86+
]
8287
geodesicActiveContour = GeoActiveContourFilterType.New()
8388
geodesicActiveContour.SetPropagationScaling(propagationScaling)
8489
geodesicActiveContour.SetCurvatureScaling(1.0)
@@ -88,8 +93,7 @@
8893
geodesicActiveContour.SetInput(fastMarching.GetOutput())
8994
geodesicActiveContour.SetFeatureImage(sigmoid.GetOutput())
9095

91-
ThresholdingFilterType = itk.BinaryThresholdImageFilter[
92-
InputImageType, OutputImageType]
96+
ThresholdingFilterType = itk.BinaryThresholdImageFilter[InputImageType, OutputImageType]
9397
thresholder = ThresholdingFilterType.New()
9498
thresholder.SetLowerThreshold(-1000.0)
9599
thresholder.SetUpperThreshold(0.0)
@@ -105,16 +109,14 @@
105109
node.SetValue(seedValue)
106110
node.SetIndex(seedPosition)
107111

108-
seeds = itk.VectorContainer[
109-
itk.UI, itk.LevelSetNode[InputPixelType, Dimension]].New()
112+
seeds = itk.VectorContainer[itk.UI, itk.LevelSetNode[InputPixelType, Dimension]].New()
110113
seeds.Initialize()
111114
seeds.InsertElement(0, node)
112115

113116
fastMarching.SetTrialPoints(seeds)
114117
fastMarching.SetSpeedConstant(1.0)
115118

116-
CastFilterType = itk.RescaleIntensityImageFilter[
117-
InputImageType, OutputImageType]
119+
CastFilterType = itk.RescaleIntensityImageFilter[InputImageType, OutputImageType]
118120

119121
caster1 = CastFilterType.New()
120122
caster2 = CastFilterType.New()
@@ -153,23 +155,22 @@
153155
caster4.SetOutputMinimum(itk.NumericTraits[OutputPixelType].min())
154156
caster4.SetOutputMaximum(itk.NumericTraits[OutputPixelType].max())
155157

156-
fastMarching.SetOutputSize(
157-
reader.GetOutput().GetBufferedRegion().GetSize())
158+
fastMarching.SetOutputSize(reader.GetOutput().GetBufferedRegion().GetSize())
158159

159160
writer = WriterType.New()
160161
writer.SetFileName(outputFileName)
161162
writer.SetInput(thresholder.GetOutput())
162163
writer.Update()
163164

164165
print(
165-
"Max. no. iterations: " +
166-
str(geodesicActiveContour.GetNumberOfIterations()) + "\n")
167-
print(
168-
"Max. RMS error: " +
169-
str(geodesicActiveContour.GetMaximumRMSError()) + "\n")
166+
"Max. no. iterations: " + str(geodesicActiveContour.GetNumberOfIterations()) + "\n"
167+
)
168+
print("Max. RMS error: " + str(geodesicActiveContour.GetMaximumRMSError()) + "\n")
170169
print(
171-
"No. elpased iterations: " +
172-
str(geodesicActiveContour.GetElapsedIterations()) + "\n")
170+
"No. elpased iterations: "
171+
+ str(geodesicActiveContour.GetElapsedIterations())
172+
+ "\n"
173+
)
173174
print("RMS change: " + str(geodesicActiveContour.GetRMSChange()) + "\n")
174175

175176
writer4.Update()

src/Segmentation/Watersheds/SegmentWithWatershedImageFilter/Code.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,43 +21,44 @@
2121
# (A rule of thumb is to set the Threshold to be about 1 / 100 of the Level.)
2222
#
2323
# threshold: absolute minimum height value used during processing.
24-
# Raising this threshold percentage effectively decreases the number of local minima in the input,
25-
# resulting in an initial segmentation with fewer regions.
24+
# Raising this threshold percentage effectively decreases the number of local minima in the input,
25+
# resulting in an initial segmentation with fewer regions.
2626
# The assumption is that the shallow regions that thresholding removes are of of less interest.
27-
# level: parameter controls the depth of metaphorical flooding of the image.
28-
# That is, it sets the maximum saliency value of interest in the result.
29-
# Raising and lowering the Level influences the number of segments
30-
# in the basic segmentation that are merged to produce the final output.
31-
# A level of 1.0 is analogous to flooding the image up to a
32-
# depth that is 100 percent of the maximum value in the image.
33-
# A level of 0.0 produces the basic segmentation, which will typically be very oversegmented.
27+
# level: parameter controls the depth of metaphorical flooding of the image.
28+
# That is, it sets the maximum saliency value of interest in the result.
29+
# Raising and lowering the Level influences the number of segments
30+
# in the basic segmentation that are merged to produce the final output.
31+
# A level of 1.0 is analogous to flooding the image up to a
32+
# depth that is 100 percent of the maximum value in the image.
33+
# A level of 0.0 produces the basic segmentation, which will typically be very oversegmented.
3434
# Level values of interest are typically low (i.e. less than about 0.40 or 40%),
3535
# since higher values quickly start to undersegment the image.
3636

3737
import sys
3838
import itk
3939

4040
if len(sys.argv) != 5:
41-
print('Usage: ' + sys.argv[0] +
42-
' <InputFileName> <OutputFileName> <Threshold> <Level>')
41+
print(
42+
"Usage: "
43+
+ sys.argv[0]
44+
+ " <InputFileName> <OutputFileName> <Threshold> <Level>"
45+
)
4346
sys.exit(1)
4447

4548
inputFileName = sys.argv[1]
4649
outputFileName = sys.argv[2]
4750

4851
Dimension = 2
4952

50-
FloatPixelType = itk.ctype('float')
53+
FloatPixelType = itk.ctype("float")
5154
FloatImageType = itk.Image[FloatPixelType, Dimension]
5255

5356
reader = itk.ImageFileReader[FloatImageType].New()
5457
reader.SetFileName(inputFileName)
5558

56-
gradientMagnitude = \
57-
itk.GradientMagnitudeImageFilter.New(Input=reader.GetOutput())
59+
gradientMagnitude = itk.GradientMagnitudeImageFilter.New(Input=reader.GetOutput())
5860

59-
watershed = \
60-
itk.WatershedImageFilter.New(Input=gradientMagnitude.GetOutput())
61+
watershed = itk.WatershedImageFilter.New(Input=gradientMagnitude.GetOutput())
6162

6263
threshold = float(sys.argv[3])
6364
level = float(sys.argv[4])
@@ -66,14 +67,17 @@
6667

6768
LabeledImageType = type(watershed.GetOutput())
6869

69-
PixelType = itk.ctype('unsigned char')
70+
PixelType = itk.ctype("unsigned char")
7071
RGBPixelType = itk.RGBPixel[PixelType]
7172
RGBImageType = itk.Image[RGBPixelType, Dimension]
7273

73-
ScalarToRGBColormapFilterType = \
74-
itk.ScalarToRGBColormapImageFilter[LabeledImageType, RGBImageType]
74+
ScalarToRGBColormapFilterType = itk.ScalarToRGBColormapImageFilter[
75+
LabeledImageType, RGBImageType
76+
]
7577
colormapImageFilter = ScalarToRGBColormapFilterType.New()
76-
colormapImageFilter.SetColormap(itk.ScalarToRGBColormapImageFilterEnums.RGBColormapFilter_Jet)
78+
colormapImageFilter.SetColormap(
79+
itk.ScalarToRGBColormapImageFilterEnums.RGBColormapFilter_Jet
80+
)
7781
colormapImageFilter.SetInput(watershed.GetOutput())
7882
colormapImageFilter.Update()
7983

0 commit comments

Comments
 (0)