Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
vision
Commits
9dca76df
Unverified
Commit
9dca76df
authored
Jun 08, 2021
by
Ishan Kumar
Committed by
GitHub
Jun 08, 2021
Browse files
Port affine transform tests in test_tranforms to pytest (#3984)
parent
27bdb8cf
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
195 additions
and
167 deletions
+195
-167
test/test_transforms.py
test/test_transforms.py
+195
-167
No files found.
test/test_transforms.py
View file @
9dca76df
...
...
@@ -381,173 +381,6 @@ class Tester(unittest.TestCase):
# Checking if LinearTransformation can be printed as string
whitening
.
__repr__
()
def
test_affine
(
self
):
input_img
=
np
.
zeros
((
40
,
40
,
3
),
dtype
=
np
.
uint8
)
cnt
=
[
20
,
20
]
for
pt
in
[(
16
,
16
),
(
20
,
16
),
(
20
,
20
)]:
for
i
in
range
(
-
5
,
5
):
for
j
in
range
(
-
5
,
5
):
input_img
[
pt
[
0
]
+
i
,
pt
[
1
]
+
j
,
:]
=
[
255
,
155
,
55
]
with
self
.
assertRaises
(
TypeError
,
msg
=
"Argument translate should be a sequence"
):
F
.
affine
(
input_img
,
10
,
translate
=
0
,
scale
=
1
,
shear
=
1
)
pil_img
=
F
.
to_pil_image
(
input_img
)
def
_to_3x3_inv
(
inv_result_matrix
):
result_matrix
=
np
.
zeros
((
3
,
3
))
result_matrix
[:
2
,
:]
=
np
.
array
(
inv_result_matrix
).
reshape
((
2
,
3
))
result_matrix
[
2
,
2
]
=
1
return
np
.
linalg
.
inv
(
result_matrix
)
def
_test_transformation
(
a
,
t
,
s
,
sh
):
a_rad
=
math
.
radians
(
a
)
s_rad
=
[
math
.
radians
(
sh_
)
for
sh_
in
sh
]
cx
,
cy
=
cnt
tx
,
ty
=
t
sx
,
sy
=
s_rad
rot
=
a_rad
# 1) Check transformation matrix:
C
=
np
.
array
([[
1
,
0
,
cx
],
[
0
,
1
,
cy
],
[
0
,
0
,
1
]])
T
=
np
.
array
([[
1
,
0
,
tx
],
[
0
,
1
,
ty
],
[
0
,
0
,
1
]])
Cinv
=
np
.
linalg
.
inv
(
C
)
RS
=
np
.
array
(
[[
s
*
math
.
cos
(
rot
),
-
s
*
math
.
sin
(
rot
),
0
],
[
s
*
math
.
sin
(
rot
),
s
*
math
.
cos
(
rot
),
0
],
[
0
,
0
,
1
]])
SHx
=
np
.
array
([[
1
,
-
math
.
tan
(
sx
),
0
],
[
0
,
1
,
0
],
[
0
,
0
,
1
]])
SHy
=
np
.
array
([[
1
,
0
,
0
],
[
-
math
.
tan
(
sy
),
1
,
0
],
[
0
,
0
,
1
]])
RSS
=
np
.
matmul
(
RS
,
np
.
matmul
(
SHy
,
SHx
))
true_matrix
=
np
.
matmul
(
T
,
np
.
matmul
(
C
,
np
.
matmul
(
RSS
,
Cinv
)))
result_matrix
=
_to_3x3_inv
(
F
.
_get_inverse_affine_matrix
(
center
=
cnt
,
angle
=
a
,
translate
=
t
,
scale
=
s
,
shear
=
sh
))
self
.
assertLess
(
np
.
sum
(
np
.
abs
(
true_matrix
-
result_matrix
)),
1e-10
)
# 2) Perform inverse mapping:
true_result
=
np
.
zeros
((
40
,
40
,
3
),
dtype
=
np
.
uint8
)
inv_true_matrix
=
np
.
linalg
.
inv
(
true_matrix
)
for
y
in
range
(
true_result
.
shape
[
0
]):
for
x
in
range
(
true_result
.
shape
[
1
]):
# Same as for PIL:
# https://github.com/python-pillow/Pillow/blob/71f8ec6a0cfc1008076a023c0756542539d057ab/
# src/libImaging/Geometry.c#L1060
input_pt
=
np
.
array
([
x
+
0.5
,
y
+
0.5
,
1.0
])
res
=
np
.
floor
(
np
.
dot
(
inv_true_matrix
,
input_pt
)).
astype
(
np
.
int
)
_x
,
_y
=
res
[:
2
]
if
0
<=
_x
<
input_img
.
shape
[
1
]
and
0
<=
_y
<
input_img
.
shape
[
0
]:
true_result
[
y
,
x
,
:]
=
input_img
[
_y
,
_x
,
:]
result
=
F
.
affine
(
pil_img
,
angle
=
a
,
translate
=
t
,
scale
=
s
,
shear
=
sh
)
self
.
assertEqual
(
result
.
size
,
pil_img
.
size
)
# Compute number of different pixels:
np_result
=
np
.
array
(
result
)
n_diff_pixels
=
np
.
sum
(
np_result
!=
true_result
)
/
3
# Accept 3 wrong pixels
self
.
assertLess
(
n_diff_pixels
,
3
,
"a={}, t={}, s={}, sh={}
\n
"
.
format
(
a
,
t
,
s
,
sh
)
+
"n diff pixels={}
\n
"
.
format
(
n_diff_pixels
))
# Test rotation
a
=
45
_test_transformation
(
a
=
a
,
t
=
(
0
,
0
),
s
=
1.0
,
sh
=
(
0.0
,
0.0
))
# Test translation
t
=
[
10
,
15
]
_test_transformation
(
a
=
0.0
,
t
=
t
,
s
=
1.0
,
sh
=
(
0.0
,
0.0
))
# Test scale
s
=
1.2
_test_transformation
(
a
=
0.0
,
t
=
(
0.0
,
0.0
),
s
=
s
,
sh
=
(
0.0
,
0.0
))
# Test shear
sh
=
[
45.0
,
25.0
]
_test_transformation
(
a
=
0.0
,
t
=
(
0.0
,
0.0
),
s
=
1.0
,
sh
=
sh
)
# Test rotation, scale, translation, shear
for
a
in
range
(
-
90
,
90
,
36
):
for
t1
in
range
(
-
10
,
10
,
5
):
for
s
in
[
0.77
,
1.0
,
1.27
]:
for
sh
in
range
(
-
15
,
15
,
5
):
_test_transformation
(
a
=
a
,
t
=
(
t1
,
t1
),
s
=
s
,
sh
=
(
sh
,
sh
))
def
test_random_affine
(
self
):
with
self
.
assertRaises
(
ValueError
):
transforms
.
RandomAffine
(
-
0.7
)
transforms
.
RandomAffine
([
-
0.7
])
transforms
.
RandomAffine
([
-
0.7
,
0
,
0.7
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
2.0
)
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
-
1.0
,
1.0
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
-
1.0
,
0.0
,
1.0
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.0
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
-
1.0
,
1.0
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
-
0.5
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
3.0
,
-
0.5
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=-
7
)
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
,
0
,
10
])
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
,
0
,
10
,
0
,
10
])
# assert fill being either a Sequence or a Number
with
self
.
assertRaises
(
TypeError
):
transforms
.
RandomAffine
(
0
,
fill
=
{})
t
=
transforms
.
RandomAffine
(
0
,
fill
=
None
)
self
.
assertTrue
(
t
.
fill
==
0
)
x
=
np
.
zeros
((
100
,
100
,
3
),
dtype
=
np
.
uint8
)
img
=
F
.
to_pil_image
(
x
)
t
=
transforms
.
RandomAffine
(
10
,
translate
=
[
0.5
,
0.3
],
scale
=
[
0.7
,
1.3
],
shear
=
[
-
10
,
10
,
20
,
40
])
for
_
in
range
(
100
):
angle
,
translations
,
scale
,
shear
=
t
.
get_params
(
t
.
degrees
,
t
.
translate
,
t
.
scale
,
t
.
shear
,
img_size
=
img
.
size
)
self
.
assertTrue
(
-
10
<
angle
<
10
)
self
.
assertTrue
(
-
img
.
size
[
0
]
*
0.5
<=
translations
[
0
]
<=
img
.
size
[
0
]
*
0.5
,
"{} vs {}"
.
format
(
translations
[
0
],
img
.
size
[
0
]
*
0.5
))
self
.
assertTrue
(
-
img
.
size
[
1
]
*
0.5
<=
translations
[
1
]
<=
img
.
size
[
1
]
*
0.5
,
"{} vs {}"
.
format
(
translations
[
1
],
img
.
size
[
1
]
*
0.5
))
self
.
assertTrue
(
0.7
<
scale
<
1.3
)
self
.
assertTrue
(
-
10
<
shear
[
0
]
<
10
)
self
.
assertTrue
(
-
20
<
shear
[
1
]
<
40
)
# Checking if RandomAffine can be printed as string
t
.
__repr__
()
t
=
transforms
.
RandomAffine
(
10
,
interpolation
=
transforms
.
InterpolationMode
.
BILINEAR
)
self
.
assertIn
(
"bilinear"
,
t
.
__repr__
())
# assert deprecation warning and non-BC
with
self
.
assertWarnsRegex
(
UserWarning
,
r
"Argument resample is deprecated and will be removed"
):
t
=
transforms
.
RandomAffine
(
10
,
resample
=
2
)
self
.
assertEqual
(
t
.
interpolation
,
transforms
.
InterpolationMode
.
BILINEAR
)
with
self
.
assertWarnsRegex
(
UserWarning
,
r
"Argument fillcolor is deprecated and will be removed"
):
t
=
transforms
.
RandomAffine
(
10
,
fillcolor
=
10
)
self
.
assertEqual
(
t
.
fill
,
10
)
# assert changed type warning
with
self
.
assertWarnsRegex
(
UserWarning
,
r
"Argument interpolation should be of type InterpolationMode"
):
t
=
transforms
.
RandomAffine
(
10
,
interpolation
=
2
)
self
.
assertEqual
(
t
.
interpolation
,
transforms
.
InterpolationMode
.
BILINEAR
)
def
test_autoaugment
(
self
):
for
policy
in
transforms
.
AutoAugmentPolicy
:
for
fill
in
[
None
,
85
,
(
128
,
128
,
128
)]:
...
...
@@ -2090,5 +1923,200 @@ def test_normalize_3d_tensor():
torch
.
testing
.
assert_close
(
target
,
result2
)
class
TestAffine
:
@
pytest
.
fixture
(
scope
=
'class'
)
def
input_img
(
self
):
input_img
=
np
.
zeros
((
40
,
40
,
3
),
dtype
=
np
.
uint8
)
for
pt
in
[(
16
,
16
),
(
20
,
16
),
(
20
,
20
)]:
for
i
in
range
(
-
5
,
5
):
for
j
in
range
(
-
5
,
5
):
input_img
[
pt
[
0
]
+
i
,
pt
[
1
]
+
j
,
:]
=
[
255
,
155
,
55
]
return
input_img
def
test_affine_translate_seq
(
self
,
input_img
):
with
pytest
.
raises
(
TypeError
,
match
=
r
"Argument translate should be a sequence"
):
F
.
affine
(
input_img
,
10
,
translate
=
0
,
scale
=
1
,
shear
=
1
)
@
pytest
.
fixture
(
scope
=
'class'
)
def
pil_image
(
self
,
input_img
):
return
F
.
to_pil_image
(
input_img
)
def
_to_3x3_inv
(
self
,
inv_result_matrix
):
result_matrix
=
np
.
zeros
((
3
,
3
))
result_matrix
[:
2
,
:]
=
np
.
array
(
inv_result_matrix
).
reshape
((
2
,
3
))
result_matrix
[
2
,
2
]
=
1
return
np
.
linalg
.
inv
(
result_matrix
)
def
_test_transformation
(
self
,
angle
,
translate
,
scale
,
shear
,
pil_image
,
input_img
):
a_rad
=
math
.
radians
(
angle
)
s_rad
=
[
math
.
radians
(
sh_
)
for
sh_
in
shear
]
cnt
=
[
20
,
20
]
cx
,
cy
=
cnt
tx
,
ty
=
translate
sx
,
sy
=
s_rad
rot
=
a_rad
# 1) Check transformation matrix:
C
=
np
.
array
([[
1
,
0
,
cx
],
[
0
,
1
,
cy
],
[
0
,
0
,
1
]])
T
=
np
.
array
([[
1
,
0
,
tx
],
[
0
,
1
,
ty
],
[
0
,
0
,
1
]])
Cinv
=
np
.
linalg
.
inv
(
C
)
RS
=
np
.
array
(
[[
scale
*
math
.
cos
(
rot
),
-
scale
*
math
.
sin
(
rot
),
0
],
[
scale
*
math
.
sin
(
rot
),
scale
*
math
.
cos
(
rot
),
0
],
[
0
,
0
,
1
]])
SHx
=
np
.
array
([[
1
,
-
math
.
tan
(
sx
),
0
],
[
0
,
1
,
0
],
[
0
,
0
,
1
]])
SHy
=
np
.
array
([[
1
,
0
,
0
],
[
-
math
.
tan
(
sy
),
1
,
0
],
[
0
,
0
,
1
]])
RSS
=
np
.
matmul
(
RS
,
np
.
matmul
(
SHy
,
SHx
))
true_matrix
=
np
.
matmul
(
T
,
np
.
matmul
(
C
,
np
.
matmul
(
RSS
,
Cinv
)))
result_matrix
=
self
.
_to_3x3_inv
(
F
.
_get_inverse_affine_matrix
(
center
=
cnt
,
angle
=
angle
,
translate
=
translate
,
scale
=
scale
,
shear
=
shear
))
assert
np
.
sum
(
np
.
abs
(
true_matrix
-
result_matrix
))
<
1e-10
# 2) Perform inverse mapping:
true_result
=
np
.
zeros
((
40
,
40
,
3
),
dtype
=
np
.
uint8
)
inv_true_matrix
=
np
.
linalg
.
inv
(
true_matrix
)
for
y
in
range
(
true_result
.
shape
[
0
]):
for
x
in
range
(
true_result
.
shape
[
1
]):
# Same as for PIL:
# https://github.com/python-pillow/Pillow/blob/71f8ec6a0cfc1008076a023c0756542539d057ab/
# src/libImaging/Geometry.c#L1060
input_pt
=
np
.
array
([
x
+
0.5
,
y
+
0.5
,
1.0
])
res
=
np
.
floor
(
np
.
dot
(
inv_true_matrix
,
input_pt
)).
astype
(
np
.
int
)
_x
,
_y
=
res
[:
2
]
if
0
<=
_x
<
input_img
.
shape
[
1
]
and
0
<=
_y
<
input_img
.
shape
[
0
]:
true_result
[
y
,
x
,
:]
=
input_img
[
_y
,
_x
,
:]
result
=
F
.
affine
(
pil_image
,
angle
=
angle
,
translate
=
translate
,
scale
=
scale
,
shear
=
shear
)
assert
result
.
size
==
pil_image
.
size
# Compute number of different pixels:
np_result
=
np
.
array
(
result
)
n_diff_pixels
=
np
.
sum
(
np_result
!=
true_result
)
/
3
# Accept 3 wrong pixels
error_msg
=
(
"angle={}, translate={}, scale={}, shear={}
\n
"
.
format
(
angle
,
translate
,
scale
,
shear
)
+
"n diff pixels={}
\n
"
.
format
(
n_diff_pixels
))
assert
n_diff_pixels
<
3
,
error_msg
def
test_transformation_discrete
(
self
,
pil_image
,
input_img
):
# Test rotation
angle
=
45
self
.
_test_transformation
(
angle
=
angle
,
translate
=
(
0
,
0
),
scale
=
1.0
,
shear
=
(
0.0
,
0.0
),
pil_image
=
pil_image
,
input_img
=
input_img
)
# Test translation
translate
=
[
10
,
15
]
self
.
_test_transformation
(
angle
=
0.0
,
translate
=
translate
,
scale
=
1.0
,
shear
=
(
0.0
,
0.0
),
pil_image
=
pil_image
,
input_img
=
input_img
)
# Test scale
scale
=
1.2
self
.
_test_transformation
(
angle
=
0.0
,
translate
=
(
0.0
,
0.0
),
scale
=
scale
,
shear
=
(
0.0
,
0.0
),
pil_image
=
pil_image
,
input_img
=
input_img
)
# Test shear
shear
=
[
45.0
,
25.0
]
self
.
_test_transformation
(
angle
=
0.0
,
translate
=
(
0.0
,
0.0
),
scale
=
1.0
,
shear
=
shear
,
pil_image
=
pil_image
,
input_img
=
input_img
)
@
pytest
.
mark
.
parametrize
(
"angle"
,
range
(
-
90
,
90
,
36
))
@
pytest
.
mark
.
parametrize
(
"translate"
,
range
(
-
10
,
10
,
5
))
@
pytest
.
mark
.
parametrize
(
"scale"
,
[
0.77
,
1.0
,
1.27
])
@
pytest
.
mark
.
parametrize
(
"shear"
,
range
(
-
15
,
15
,
5
))
def
test_transformation_range
(
self
,
angle
,
translate
,
scale
,
shear
,
pil_image
,
input_img
):
self
.
_test_transformation
(
angle
=
angle
,
translate
=
(
translate
,
translate
),
scale
=
scale
,
shear
=
(
shear
,
shear
),
pil_image
=
pil_image
,
input_img
=
input_img
)
def
test_random_affine
():
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
(
-
0.7
)
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
0.7
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
0.7
,
0
,
0.7
])
with
pytest
.
raises
(
TypeError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
2.0
)
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
-
1.0
,
1.0
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
-
1.0
,
0.0
,
1.0
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.0
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
-
1.0
,
1.0
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
-
0.5
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
3.0
,
-
0.5
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=-
7
)
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
,
0
,
10
])
with
pytest
.
raises
(
ValueError
):
transforms
.
RandomAffine
([
-
90
,
90
],
translate
=
[
0.2
,
0.2
],
scale
=
[
0.5
,
0.5
],
shear
=
[
-
10
,
0
,
10
,
0
,
10
])
# assert fill being either a Sequence or a Number
with
pytest
.
raises
(
TypeError
):
transforms
.
RandomAffine
(
0
,
fill
=
{})
t
=
transforms
.
RandomAffine
(
0
,
fill
=
None
)
assert
t
.
fill
==
0
x
=
np
.
zeros
((
100
,
100
,
3
),
dtype
=
np
.
uint8
)
img
=
F
.
to_pil_image
(
x
)
t
=
transforms
.
RandomAffine
(
10
,
translate
=
[
0.5
,
0.3
],
scale
=
[
0.7
,
1.3
],
shear
=
[
-
10
,
10
,
20
,
40
])
for
_
in
range
(
100
):
angle
,
translations
,
scale
,
shear
=
t
.
get_params
(
t
.
degrees
,
t
.
translate
,
t
.
scale
,
t
.
shear
,
img_size
=
img
.
size
)
assert
-
10
<
angle
<
10
assert
-
img
.
size
[
0
]
*
0.5
<=
translations
[
0
]
<=
img
.
size
[
0
]
*
0.5
,
(
"{} vs {}"
.
format
(
translations
[
0
],
img
.
size
[
0
]
*
0.5
))
assert
-
img
.
size
[
1
]
*
0.5
<=
translations
[
1
]
<=
img
.
size
[
1
]
*
0.5
,
(
"{} vs {}"
.
format
(
translations
[
1
],
img
.
size
[
1
]
*
0.5
))
assert
0.7
<
scale
<
1.3
assert
-
10
<
shear
[
0
]
<
10
assert
-
20
<
shear
[
1
]
<
40
# Checking if RandomAffine can be printed as string
t
.
__repr__
()
t
=
transforms
.
RandomAffine
(
10
,
interpolation
=
transforms
.
InterpolationMode
.
BILINEAR
)
assert
"bilinear"
in
t
.
__repr__
()
# assert deprecation warning and non-BC
with
pytest
.
warns
(
UserWarning
,
match
=
r
"Argument resample is deprecated and will be removed"
):
t
=
transforms
.
RandomAffine
(
10
,
resample
=
2
)
assert
t
.
interpolation
==
transforms
.
InterpolationMode
.
BILINEAR
with
pytest
.
warns
(
UserWarning
,
match
=
r
"Argument fillcolor is deprecated and will be removed"
):
t
=
transforms
.
RandomAffine
(
10
,
fillcolor
=
10
)
assert
t
.
fill
==
10
# assert changed type warning
with
pytest
.
warns
(
UserWarning
,
match
=
r
"Argument interpolation should be of type InterpolationMode"
):
t
=
transforms
.
RandomAffine
(
10
,
interpolation
=
2
)
assert
t
.
interpolation
==
transforms
.
InterpolationMode
.
BILINEAR
if
__name__
==
'__main__'
:
unittest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment