Skip to content

Commit 1dc07a0

Browse files
fabioacledoardob90
andauthored
Image Classification: Add docstrings to exercises (#369)
* Add docstrings to the exercises in order to make them more clear * Small fixes --------- Co-authored-by: Edoardo Baldi <edoardob90@gmail.com>
1 parent ddebde6 commit 1dc07a0

2 files changed

Lines changed: 197 additions & 11 deletions

File tree

31_image_classification.ipynb

Lines changed: 186 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,20 @@
749749
"%%ipytest\n",
750750
"\n",
751751
"import cv2\n",
752-
"def solution_scale_image(img, scale_factor: float):\n",
752+
"import numpy as np\n",
753+
"def solution_scale_image(img: np.ndarray, scale_factor: float):\n",
754+
" \"\"\"\n",
755+
" The function takes an image as input and rescales it to a new dimension.\n",
756+
" For that, you need to compute the new dimensions of the image using the scale factor \n",
757+
" and then use OpenCV's `cv2.resize` function to resize the image, e.g., `cv2.resize(img, (new_width, new_height))`.\n",
758+
"\n",
759+
" Args:\n",
760+
" img (np.ndarray): The input image.\n",
761+
" scale_factor (float): The factor by which to scale the image.\n",
762+
"\n",
763+
" Returns:\n",
764+
" np.ndarray: The scaled image.\n",
765+
" \"\"\"\n",
753766
" # Start your code here\n",
754767
" return\n",
755768
" # End your code here"
@@ -798,7 +811,26 @@
798811
"%%ipytest\n",
799812
"\n",
800813
"import cv2\n",
801-
"def solution_crop_image(img, x: int, y: int, width: int, height: int):\n",
814+
"import numpy as np\n",
815+
"def solution_crop_image(img: np.ndarray, x: int, y: int, width: int, height: int):\n",
816+
" \"\"\"\n",
817+
" The function takes an image as input and crops it to a specified rectangular region.\n",
818+
" In OpenCV, images are represented as NumPy arrays. You can crop the image by \n",
819+
" using array slicing, keeping in mind that the first dimension is the y-axis (rows) \n",
820+
" and the second dimension is the x-axis (columns): `img[y_start:y_end, x_start:x_end]`. \n",
821+
" You will need to calculate these start and end coordinates using the provided \n",
822+
" x, y, width, and height parameters.\n",
823+
"\n",
824+
" Args:\n",
825+
" img (np.ndarray): The input image.\n",
826+
" x (int): The starting x-coordinate (column) of the crop.\n",
827+
" y (int): The starting y-coordinate (row) of the crop.\n",
828+
" width (int): The width of the cropped region.\n",
829+
" height (int): The height of the cropped region.\n",
830+
"\n",
831+
" Returns:\n",
832+
" np.ndarray: The cropped image.\n",
833+
" \"\"\"\n",
802834
" # Start your code here\n",
803835
" return\n",
804836
" # End your code here"
@@ -847,7 +879,22 @@
847879
"%%ipytest\n",
848880
"\n",
849881
"import cv2\n",
850-
"def solution_horizontal_flip_image(img):\n",
882+
"import numpy as np\n",
883+
"def solution_horizontal_flip_image(img: np.ndarray):\n",
884+
" \"\"\"\n",
885+
" The function takes an image as input and flips it horizontally (creating a mirror image).\n",
886+
" To do this, use OpenCV's `cv2.flip(src, flipCode)` function. The `flipCode` integer \n",
887+
" determines the axis to flip around: \n",
888+
" - Use 1 for a horizontal flip (around the y-axis).\n",
889+
" - Use 0 for a vertical flip (around the x-axis).\n",
890+
" - Use -1 for both axes.\n",
891+
"\n",
892+
" Args:\n",
893+
" img (np.ndarray): The input image.\n",
894+
"\n",
895+
" Returns:\n",
896+
" np.ndarray: The horizontally flipped image.\n",
897+
" \"\"\"\n",
851898
" # Start your code here\n",
852899
" return\n",
853900
" # End your code here"
@@ -896,7 +943,22 @@
896943
"%%ipytest\n",
897944
"\n",
898945
"import cv2\n",
899-
"def solution_vertical_flip_image(img):\n",
946+
"import numpy as np\n",
947+
"def solution_vertical_flip_image(img: np.ndarray):\n",
948+
" \"\"\"\n",
949+
" The function takes an image as input and flips it vertically.\n",
950+
" To do this, use OpenCV's `cv2.flip(src, flipCode)` function. The `flipCode` integer \n",
951+
" determines the axis to flip around: \n",
952+
" - Use 1 for a horizontal flip (around the y-axis).\n",
953+
" - Use 0 for a vertical flip (around the x-axis).\n",
954+
" - Use -1 for both axes.\n",
955+
"\n",
956+
" Args:\n",
957+
" img (np.ndarray): The input image.\n",
958+
"\n",
959+
" Returns:\n",
960+
" np.ndarray: The vertically flipped image.\n",
961+
" \"\"\"\n",
900962
" # Start your code here\n",
901963
" return\n",
902964
" # End your code here"
@@ -945,7 +1007,37 @@
9451007
"%%ipytest\n",
9461008
"\n",
9471009
"import cv2\n",
948-
"def solution_rotate_image(img, angle: float):\n",
1010+
"import numpy as np\n",
1011+
"def solution_rotate_image(img: np.ndarray, angle: float):\n",
1012+
" \"\"\"\n",
1013+
" The function takes an image as input and rotates it by a specified angle. \n",
1014+
" To ensure the corners of the image are not cropped after rotation, you must \n",
1015+
" calculate a new bounding box and adjust the rotation matrix. \n",
1016+
" \n",
1017+
" Follow these steps:\n",
1018+
" 1. Get the height and width.\n",
1019+
" 2. Find the center point of the image.\n",
1020+
" 3. Get the starting matrix: `mat = cv2.getRotationMatrix2D(center, angle, 1.0)`\n",
1021+
" \n",
1022+
" 4. Calculate the new canvas size using this trigonometry:\n",
1023+
" cos = np.abs(mat[0, 0])\n",
1024+
" sin = np.abs(mat[0, 1])\n",
1025+
" new_w = int((h * sin) + (w * cos))\n",
1026+
" new_h = int((h * cos) + (w * sin))\n",
1027+
" \n",
1028+
" 5. Adjust the matrix so the image is shifted to the middle of the new canvas:\n",
1029+
" mat[0, 2] += (new_w / 2) - center[0]\n",
1030+
" mat[1, 2] += (new_h / 2) - center[1]\n",
1031+
" \n",
1032+
" 6. Return the final rotated image using: `cv2.warpAffine(img, mat, (new_w, new_h))`\n",
1033+
"\n",
1034+
" Args:\n",
1035+
" img (np.ndarray): The input image.\n",
1036+
" angle (float): The angle of rotation in degrees.\n",
1037+
"\n",
1038+
" Returns:\n",
1039+
" np.ndarray: The rotated image on a properly sized canvas.\n",
1040+
" \"\"\"\n",
9491041
" # Start your code here\n",
9501042
" return\n",
9511043
" # End your code here"
@@ -1009,7 +1101,20 @@
10091101
"%%ipytest\n",
10101102
"\n",
10111103
"import cv2\n",
1012-
"def solution_average_filter(img, kernel_size = (5, 5)):\n",
1104+
"import numpy as np\n",
1105+
"def solution_average_filter(img: np.ndarray, kernel_size: tuple = (5, 5)):\n",
1106+
" \"\"\"\n",
1107+
" Applies an average filter to blur the image using a specific kernel size.\n",
1108+
" For that, you need to use OpenCV's `cv2.blur()` function, which takes the image and the kernel size as arguments.\n",
1109+
" `cv2.blur()` requires two arguments: the image, and the size of the kernel.\n",
1110+
" \n",
1111+
" Args:\n",
1112+
" img (np.ndarray): The input image.\n",
1113+
" kernel_size (tuple): The width and height of the blurring window. Default is (5, 5).\n",
1114+
"\n",
1115+
" Returns:\n",
1116+
" np.ndarray: The blurred image.\n",
1117+
" \"\"\"\n",
10131118
" # Start your code here\n",
10141119
" return\n",
10151120
" # End your code here"
@@ -1058,7 +1163,20 @@
10581163
"%%ipytest\n",
10591164
"\n",
10601165
"import cv2\n",
1061-
"def solution_median_filter(img, ksize):\n",
1166+
"import numpy as np\n",
1167+
"def solution_median_filter(img: np.ndarray, ksize: int):\n",
1168+
" \"\"\"\n",
1169+
" Applies a median filter to the image using a specific kernel size.\n",
1170+
" For that, you need to use OpenCV's `cv2.medianBlur()` function, which takes the image and the kernel size as arguments.\n",
1171+
" `cv2.medianBlur()` requires two arguments: the image, and the size of the kernel.\n",
1172+
" \n",
1173+
" Args:\n",
1174+
" img (np.ndarray): The input image.\n",
1175+
" ksize (int): The size of the median filter kernel. Must be a positive odd integer.\n",
1176+
"\n",
1177+
" Returns:\n",
1178+
" np.ndarray: The blurred image.\n",
1179+
" \"\"\"\n",
10621180
" # Start your code here\n",
10631181
" return\n",
10641182
" # End your code here"
@@ -1107,7 +1225,21 @@
11071225
"%%ipytest\n",
11081226
"\n",
11091227
"import cv2\n",
1110-
"def solution_gaussian_filter(img, kernel_size = (5, 5), sigma = 0):\n",
1228+
"import numpy as np\n",
1229+
"def solution_gaussian_filter(img: np.ndarray, kernel_size: tuple = (5, 5), sigma: float = 0):\n",
1230+
" \"\"\"\n",
1231+
" Applies a Gaussian filter to the image using a specific kernel size and sigma value.\n",
1232+
" For that, you need to use OpenCV's `cv2.GaussianBlur()` function, which takes the image, kernel size, and sigma as arguments.\n",
1233+
" `cv2.GaussianBlur()` requires three arguments: the image, the size of the kernel, and the sigma value.\n",
1234+
"\n",
1235+
" Args:\n",
1236+
" img (np.ndarray): The input image.\n",
1237+
" kernel_size (tuple): The width and height of the Gaussian kernel. Default is (5, 5).\n",
1238+
" sigma (float): The standard deviation of the Gaussian kernel. Default is 0.\n",
1239+
"\n",
1240+
" Returns:\n",
1241+
" np.ndarray: The blurred image.\n",
1242+
" \"\"\"\n",
11111243
" # Start your code here\n",
11121244
" return\n",
11131245
" # End your code here"
@@ -1170,7 +1302,18 @@
11701302
"%%ipytest\n",
11711303
"\n",
11721304
"import cv2\n",
1173-
"def solution_adjust_brightness(img, brightness_value):\n",
1305+
"import numpy as np\n",
1306+
"def solution_adjust_brightness(img: np.ndarray, brightness_value: float):\n",
1307+
" \"\"\"\n",
1308+
" Adjusts the brightness of the image by adding a specified value to all pixel intensities.\n",
1309+
" To adjust the brightness, you can use OpenCV's `cv2.convertScaleAbs()` function, which scales, calculates absolute values, and converts the result to 8-bit.\n",
1310+
" `cv2.convertScaleAbs()` requires three arguments: the image, the alpha value (which is 1 for no scaling), and the beta value (which is the brightness adjustment value).\n",
1311+
" Args:\n",
1312+
" img (np.ndarray): The input image.\n",
1313+
" brightness_value (float): The value to add to the pixel intensities. Positive values increase brightness, while negative values decrease it.\n",
1314+
" Returns:\n",
1315+
" np.ndarray: The brightness-adjusted image.\n",
1316+
" \"\"\"\n",
11741317
" # Start your code here\n",
11751318
" return\n",
11761319
" # End your code here"
@@ -1222,7 +1365,18 @@
12221365
"%%ipytest\n",
12231366
"\n",
12241367
"import cv2\n",
1225-
"def solution_adjust_contrast(img, contrast_value):\n",
1368+
"import numpy as np\n",
1369+
"def solution_adjust_contrast(img: np.ndarray, contrast_value: float):\n",
1370+
" \"\"\"\n",
1371+
" Adjusts the contrast of the image by scaling the pixel intensities.\n",
1372+
" To adjust the contrast, you can use OpenCV's `cv2.convertScaleAbs()` function, which scales, calculates absolute values, and converts the result to 8-bit.\n",
1373+
" `cv2.convertScaleAbs()` requires three arguments: the image, the alpha value (which is the contrast adjustment value), and the beta value (which is 0 for no additional brightness adjustment).\n",
1374+
" Args:\n",
1375+
" img (np.ndarray): The input image.\n",
1376+
" contrast_value (float): The value to scale the pixel intensities. Values greater than 1 increase contrast, while values between 0 and 1 decrease it.\n",
1377+
" Returns:\n",
1378+
" np.ndarray: The contrast-adjusted image.\n",
1379+
" \"\"\"\n",
12261380
" # Start your code here\n",
12271381
" return\n",
12281382
" # End your code here"
@@ -1274,7 +1428,28 @@
12741428
"%%ipytest\n",
12751429
"\n",
12761430
"import cv2\n",
1277-
"def solution_adjust_saturation(img, saturation_factor):\n",
1431+
"import numpy as np\n",
1432+
"def solution_adjust_saturation(img: np.ndarray, saturation_factor: float):\n",
1433+
" \"\"\"\n",
1434+
" Adjusts the saturation of the image by modifying the saturation channel in the HSV color space.\n",
1435+
" To do that you need to convert the image from RGB to HSV, adjust the saturation channel, and then convert it back to RGB.\n",
1436+
" Follow these steps:\n",
1437+
" 1. It is very hard to change saturation in standard RGB format because the colors are mixed. \n",
1438+
" First, convert the image to HSV format (`cv2.COLOR_RGB2HSV`) format using: `hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)`.\n",
1439+
" 2. Now that the image is in HSV, you can isolate the Saturation. Split the image into its three separate channels using: `cv2.split(hsv_img)`.\n",
1440+
" 3. Multiply the saturation channel by the `saturation_factor`. \n",
1441+
" 4. Pixel values cannot go above 255 or below 0. Use NumPy's clip function (`np.clip`) to enforce this limit.\n",
1442+
" 5. Math operations can change the data type. Force the new saturation channel back into standard image format `uint8`.\n",
1443+
" 6. Put the three channels back together in the correct order using `cv2.merge()`.\n",
1444+
" 7. Finally, convert the image back to normal RGB format (`cv2.COLOR_HSV2RGB`).\n",
1445+
"\n",
1446+
" Args:\n",
1447+
" img (np.ndarray): The input image in RGB format.\n",
1448+
" saturation_factor (float): The multiplier for the saturation channel. Values greater than 1 increase saturation, while values between 0 and 1 decrease it.\n",
1449+
"\n",
1450+
" Returns:\n",
1451+
" np.ndarray: The saturation-adjusted image.\n",
1452+
" \"\"\"\n",
12781453
" # Start your code here\n",
12791454
" return\n",
12801455
" # End your code here"

tutorial/tests/test_31_image_classification.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def test_scale_image(scale_factor, function_to_test):
2121
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
2222
image_test = function_to_test(image, scale_factor)
2323
image_reference = reference_scale_image(image, scale_factor)
24+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
2425
assert image_test.shape == image_reference.shape
2526

2627

@@ -36,6 +37,7 @@ def test_crop_image(x, y, width, height, function_to_test):
3637
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
3738
image_test = function_to_test(image, x, y, width, height)
3839
image_reference = reference_crop_image(image, x, y, width, height)
40+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
3941
assert image_test.shape == image_reference.shape
4042

4143

@@ -47,6 +49,7 @@ def test_horizontal_flip_image(function_to_test):
4749
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
4850
image_test = function_to_test(image)
4951
image_reference = reference_horizontal_flip_image(image)
52+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
5053
assert np.allclose(image_test, image_reference)
5154

5255

@@ -58,6 +61,7 @@ def test_vertical_flip_image(function_to_test):
5861
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
5962
image_test = function_to_test(image)
6063
image_reference = reference_vertical_flip_image(image)
64+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
6165
assert np.allclose(image_test, image_reference)
6266

6367

@@ -85,6 +89,7 @@ def test_rotate_image(angle, function_to_test):
8589
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
8690
image_test = function_to_test(image, angle)
8791
image_reference = reference_rotate_image(image, angle)
92+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
8893
assert np.allclose(image_test, image_reference)
8994

9095

@@ -97,6 +102,7 @@ def test_average_filter(kernel_size, function_to_test):
97102
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
98103
image_test = function_to_test(image, kernel_size)
99104
image_reference = reference_average_filter(image, kernel_size)
105+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
100106
assert np.allclose(image_test, image_reference)
101107

102108

@@ -109,6 +115,7 @@ def test_median_filter(ksize, function_to_test):
109115
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
110116
image_test = function_to_test(image, ksize)
111117
image_reference = reference_median_filter(image, ksize)
118+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
112119
assert np.allclose(image_test, image_reference)
113120

114121

@@ -123,6 +130,7 @@ def test_gaussian_filter(kernel_size, sigma, function_to_test):
123130
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
124131
image_test = function_to_test(image, kernel_size, sigma)
125132
image_reference = reference_gaussian_filter(image, kernel_size, sigma)
133+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
126134
assert np.allclose(image_test, image_reference)
127135

128136

@@ -135,6 +143,7 @@ def test_adjust_brightness(brightness_value, function_to_test):
135143
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
136144
image_test = function_to_test(image, brightness_value)
137145
image_reference = reference_adjust_brightness(image, brightness_value)
146+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
138147
assert np.allclose(image_test, image_reference)
139148

140149

@@ -147,6 +156,7 @@ def test_adjust_contrast(contrast_value, function_to_test):
147156
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
148157
image_test = function_to_test(image, contrast_value)
149158
image_reference = reference_adjust_contrast(image, contrast_value)
159+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
150160
assert np.allclose(image_test, image_reference)
151161

152162

@@ -172,4 +182,5 @@ def test_adjust_saturation(saturation_factor, function_to_test):
172182
image = np.ones((32, 32, 3), dtype=np.uint8) * 255
173183
image_test = function_to_test(image, saturation_factor)
174184
image_reference = reference_adjust_saturation(image, saturation_factor)
185+
assert isinstance(image_test, np.ndarray), "Your function should return an image."
175186
assert np.allclose(image_test, image_reference)

0 commit comments

Comments
 (0)