PyColorSpace 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyColorSpace
3
+ Version: 0.0.1
4
+ Summary: Color space conversion library
5
+ Requires-Python: >=3.9
6
+ License-File: LICENSE
7
+ Requires-Dist: numpy
8
+ Requires-Dist: opencv-python
9
+ Dynamic: license-file
@@ -0,0 +1,9 @@
1
+ [project]
2
+ name = "PyColorSpace"
3
+ version = "0.0.1"
4
+ description = "Color space conversion library"
5
+ requires-python = ">=3.9"
6
+ dependencies = [
7
+ "numpy",
8
+ "opencv-python",
9
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+
2
+
3
+ from .yuv import yuv444_to_rgb
4
+ from .yuv import yuv420_to_rgb
File without changes
@@ -0,0 +1,8 @@
1
+ import numpy as np
2
+
3
+
4
+ YUV_TO_RGB_BT2020 = np.array([
5
+ [1.0, 0.0, 1.4746],
6
+ [1.0, -0.16455312684366, -0.57135312684366],
7
+ [1.0, 1.8814, 0.0]
8
+ ], dtype=np.float32)
@@ -0,0 +1,7 @@
1
+ import numpy as np
2
+
3
+ YUV_TO_RGB_BT601 = np.array([
4
+ [1.0, 0.0, 1.5748],
5
+ [1.0, -0.1873, -0.4681],
6
+ [1.0, 1.8556, 0.0],
7
+ ], dtype=np.float32)
@@ -0,0 +1,7 @@
1
+ import numpy as np
2
+
3
+ YUV_TO_RGB_BT709 = np.array([
4
+ [1.0, 0.0, 1.5748],
5
+ [1.0, -0.1873, -0.4681],
6
+ [1.0, 1.8556, 0.0],
7
+ ], dtype=np.float32)
@@ -0,0 +1,174 @@
1
+
2
+
3
+ import numpy as np
4
+ import cv2
5
+ from .utils import get_rgb_to_yuv_matrix
6
+
7
+
8
+ def rgb_to_yuv444(
9
+ rgb: np.ndarray,
10
+ *,
11
+ standard: str = "bt709",
12
+ bit_depth: int = 8,
13
+ normalized: bool = False,
14
+ input_layout: str = "HWC",
15
+ output_layout: str = "HWC",
16
+ output_range: str = "normalized",
17
+ output_dtype=None,
18
+ ) -> np.ndarray:
19
+ """
20
+ Convert an RGB image to YUV444.
21
+
22
+ Parameters
23
+ ----------
24
+ rgb : numpy.ndarray
25
+ Input RGB image.
26
+
27
+ Accepted layouts
28
+ ----------------
29
+ - "HWC" : (H, W, 3)
30
+ - "CHW" : (3, H, W)
31
+
32
+ standard : {"bt601", "bt709", "bt2020"}, default="bt709"
33
+ Color conversion standard.
34
+
35
+ bit_depth : int, default=8
36
+ Bit depth used when normalized=False.
37
+
38
+ normalized : bool, default=False
39
+ If True, input RGB is assumed to be in [0,1].
40
+
41
+ If False, RGB is assumed to be integer-scaled according
42
+ to bit_depth.
43
+
44
+ input_layout : {"HWC","CHW"}, default="HWC"
45
+
46
+ output_layout : {"HWC","CHW"}, default="HWC"
47
+
48
+ output_range : {"normalized","integer"}, default="normalized"
49
+
50
+ output_dtype : numpy.dtype or None
51
+
52
+ Returns
53
+ -------
54
+ numpy.ndarray
55
+ YUV444 image.
56
+ """
57
+
58
+ # Validate
59
+ if not isinstance(rgb, np.ndarray):
60
+ raise TypeError("rgb must be a NumPy array.")
61
+
62
+ if input_layout not in ("HWC", "CHW"):
63
+ raise ValueError("input_layout must be 'HWC' or 'CHW'.")
64
+
65
+ if output_layout not in ("HWC", "CHW"):
66
+ raise ValueError("output_layout must be 'HWC' or 'CHW'.")
67
+
68
+ if output_range not in ("normalized", "integer"):
69
+ raise ValueError(
70
+ "output_range must be 'normalized' or 'integer'."
71
+ )
72
+
73
+ # Convert CHW -> HWC
74
+ if input_layout == "CHW":
75
+ rgb = np.transpose(rgb, (1, 2, 0))
76
+
77
+ if rgb.ndim != 3 or rgb.shape[-1] != 3:
78
+ raise ValueError(
79
+ "Expected RGB image with shape (H, W, 3)."
80
+ )
81
+
82
+ # Normalize
83
+ rgb = rgb.astype(np.float32)
84
+
85
+ if not normalized:
86
+ max_value = (1 << bit_depth) - 1
87
+ rgb /= max_value
88
+
89
+ # RGB -> YUV
90
+ matrix = get_rgb_to_yuv_matrix(standard)
91
+
92
+ yuv = rgb @ matrix.T
93
+
94
+ # Shift chroma to [0,1]
95
+ yuv[..., 1:] += 0.5
96
+
97
+ # Clip
98
+ yuv = np.clip(yuv, 0.0, 1.0)
99
+
100
+ # Scale if requested
101
+ if output_range == "integer":
102
+ yuv *= (1 << bit_depth) - 1
103
+
104
+ # Cast
105
+ if output_dtype is not None:
106
+ yuv = yuv.astype(output_dtype)
107
+
108
+ # Layout
109
+ if output_layout == "CHW":
110
+ yuv = np.transpose(yuv, (2, 0, 1))
111
+
112
+ return yuv
113
+
114
+
115
+
116
+ def rgb_to_yuv420(
117
+ rgb: np.ndarray,
118
+ *,
119
+ standard: str = "bt709",
120
+ bit_depth: int = 8,
121
+ normalized: bool = False,
122
+ input_layout: str = "HWC",
123
+ output_layout: str = "HWC",
124
+ output_range: str = "normalized",
125
+ output_dtype=None,
126
+ ):
127
+ """
128
+ Convert an RGB image to YUV420.
129
+
130
+ Returns
131
+ -------
132
+ tuple
133
+ (Y, U420, V420)
134
+ """
135
+
136
+ # Convert RGB -> YUV444
137
+ yuv444 = rgb_to_yuv444(
138
+ rgb,
139
+ standard=standard,
140
+ bit_depth=bit_depth,
141
+ normalized=normalized,
142
+ input_layout=input_layout,
143
+ output_layout="HWC",
144
+ output_range=output_range,
145
+ output_dtype=output_dtype,
146
+ )
147
+
148
+ # Split channels
149
+ Y = yuv444[..., 0]
150
+ U = yuv444[..., 1]
151
+ V = yuv444[..., 2]
152
+
153
+ height, width = Y.shape
154
+
155
+ # Bicubic chroma downsampling
156
+ U420 = cv2.resize(
157
+ U,
158
+ (width // 2, height // 2),
159
+ interpolation=cv2.INTER_CUBIC,
160
+ )
161
+
162
+ V420 = cv2.resize(
163
+ V,
164
+ (width // 2, height // 2),
165
+ interpolation=cv2.INTER_CUBIC,
166
+ )
167
+
168
+ # Return requested layout
169
+ if output_layout == "CHW":
170
+ Y = Y[np.newaxis, ...]
171
+ U420 = U420[np.newaxis, ...]
172
+ V420 = V420[np.newaxis, ...]
173
+
174
+ return Y, U420, V420
@@ -0,0 +1,2 @@
1
+
2
+ from .get_yuv_to_rgb_matrix import get_yuv_to_rgb_matrix
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+
3
+ from ..matrices.bt601 import YUV_TO_RGB_BT601
4
+ from ..matrices.bt709 import YUV_TO_RGB_BT709
5
+ from ..matrices.bt2020 import YUV_TO_RGB_BT2020
6
+
7
+
8
+ RGB_TO_YUV_MATRICES = {
9
+ "bt601": np.linalg.inv(YUV_TO_RGB_BT601),
10
+ "bt709": np.linalg.inv(YUV_TO_RGB_BT709),
11
+ "bt2020": np.linalg.inv(YUV_TO_RGB_BT2020),
12
+ }
13
+
14
+
15
+ def get_rgb_to_yuv_matrix(standard: str):
16
+ """
17
+ Return the RGB -> YUV conversion matrix.
18
+
19
+ Parameters
20
+ ----------
21
+ standard : str
22
+ Color conversion standard.
23
+
24
+ Supported values
25
+ ----------------
26
+ - "bt601"
27
+ - "bt709"
28
+ - "bt2020"
29
+
30
+ Returns
31
+ -------
32
+ numpy.ndarray
33
+ 3x3 RGB→YUV conversion matrix.
34
+
35
+ Raises
36
+ ------
37
+ ValueError
38
+ If the requested standard is unsupported.
39
+ """
40
+
41
+ if not isinstance(standard, str):
42
+ raise TypeError("standard must be a string.")
43
+
44
+ standard = standard.lower()
45
+
46
+ try:
47
+ return RGB_TO_YUV_MATRICES[standard]
48
+ except KeyError:
49
+ supported = ", ".join(RGB_TO_YUV_MATRICES.keys())
50
+ raise ValueError(
51
+ f"Unsupported standard '{standard}'. "
52
+ f"Supported standards are: {supported}."
53
+ )
@@ -0,0 +1,52 @@
1
+
2
+ from ..matrices.bt601 import YUV_TO_RGB_BT601
3
+ from ..matrices.bt709 import YUV_TO_RGB_BT709
4
+ from ..matrices.bt2020 import YUV_TO_RGB_BT2020
5
+
6
+
7
+ YUV_TO_RGB_MATRICES = {
8
+ "bt601": YUV_TO_RGB_BT601,
9
+ "bt709": YUV_TO_RGB_BT709,
10
+ "bt2020": YUV_TO_RGB_BT2020,
11
+ }
12
+
13
+
14
+ def get_yuv_to_rgb_matrix(standard: str):
15
+ """
16
+ Return the YUV→RGB conversion matrix.
17
+
18
+ Parameters
19
+ ----------
20
+ standard : str
21
+ Color conversion standard.
22
+
23
+ Supported values
24
+ ----------------
25
+ - "bt601"
26
+ - "bt709"
27
+ - "bt2020"
28
+
29
+ Returns
30
+ -------
31
+ numpy.ndarray
32
+ 3x3 YUV→RGB conversion matrix.
33
+
34
+ Raises
35
+ ------
36
+ ValueError
37
+ If the requested standard is unsupported.
38
+ """
39
+
40
+ if not isinstance(standard, str):
41
+ raise TypeError("standard must be a string.")
42
+
43
+ standard = standard.lower()
44
+
45
+ try:
46
+ return YUV_TO_RGB_MATRICES[standard]
47
+ except KeyError:
48
+ supported = ", ".join(YUV_TO_RGB_MATRICES.keys())
49
+ raise ValueError(
50
+ f"Unsupported standard '{standard}'. "
51
+ f"Supported standards are: {supported}."
52
+ )
@@ -0,0 +1,232 @@
1
+
2
+ import numpy as np
3
+ from .utils import get_yuv_to_rgb_matrix
4
+
5
+
6
+ def yuv444_to_rgb(
7
+ yuv: np.ndarray,
8
+ *,
9
+ standard: str = "bt709",
10
+ bit_depth: int = 8,
11
+ normalized: bool = False,
12
+ input_layout: str = "HWC",
13
+ output_layout: str = "HWC",
14
+ output_range: str = "normalized",
15
+ output_dtype=None,
16
+ ) -> np.ndarray:
17
+ """
18
+ Convert a YUV444 image to RGB.
19
+
20
+ Parameters
21
+ ----------
22
+ yuv : numpy.ndarray
23
+ Input YUV444 image.
24
+
25
+ Accepted layouts:
26
+ - "HWC" : (H, W, 3)
27
+ - "CHW" : (3, H, W)
28
+
29
+ standard : str, default="bt709"
30
+ Color conversion standard.
31
+
32
+ Supported values include:
33
+ - "bt601"
34
+ - "bt709"
35
+ - "bt2020"
36
+
37
+ bit_depth : int, default=8
38
+ Bit depth of the Y channel and chroma channels.
39
+ Used only when ``normalized=False``.
40
+
41
+ Examples
42
+ --------
43
+ 8 -> values in [0, 255]
44
+
45
+ 10 -> values in [0, 1023]
46
+
47
+ 12 -> values in [0, 4095]
48
+
49
+ normalized : bool, default=False
50
+ If True, the input image is assumed to be normalized
51
+ to the range [0, 1].
52
+
53
+ If False, values are assumed to be integer-scaled
54
+ according to ``bit_depth``.
55
+
56
+ input_layout : {"HWC", "CHW"}, default="HWC"
57
+ Memory layout of the input image.
58
+
59
+ output_layout : {"HWC", "CHW"}, default="HWC"
60
+ Desired layout of the returned RGB image.
61
+
62
+ output_range : {"normalized", "integer"}, default="normalized"
63
+
64
+ "normalized"
65
+ Returns RGB in [0,1].
66
+
67
+ "integer"
68
+ Returns RGB scaled to the selected bit depth.
69
+
70
+ output_dtype : numpy.dtype or None, default=None
71
+ Desired output dtype.
72
+
73
+ Examples
74
+ --------
75
+ np.float32
76
+
77
+ np.float64
78
+
79
+ np.uint8
80
+
81
+ np.uint16
82
+
83
+ If None, an appropriate dtype is chosen automatically.
84
+
85
+ Returns
86
+ -------
87
+ numpy.ndarray
88
+ RGB image.
89
+
90
+ Raises
91
+ ------
92
+ ValueError
93
+ If an invalid layout, range, or standard is supplied.
94
+ """
95
+
96
+ # Validate image
97
+ if not isinstance(yuv, np.ndarray):
98
+ raise TypeError("yuv must be a NumPy array.")
99
+
100
+ if input_layout not in ("HWC", "CHW"):
101
+ raise ValueError("input_layout must be 'HWC' or 'CHW'.")
102
+
103
+ if output_layout not in ("HWC", "CHW"):
104
+ raise ValueError("output_layout must be 'HWC' or 'CHW'.")
105
+
106
+ if output_range not in ("normalized", "integer"):
107
+ raise ValueError(
108
+ "output_range must be 'normalized' or 'integer'."
109
+ )
110
+
111
+ # Convert CHW -> HWC
112
+ if input_layout == "CHW":
113
+ yuv = np.transpose(yuv, (1, 2, 0))
114
+
115
+ if yuv.ndim != 3 or yuv.shape[-1] != 3:
116
+ raise ValueError(
117
+ "Expected YUV image with shape (H, W, 3)."
118
+ )
119
+
120
+ # Normalize input
121
+ yuv = yuv.astype(np.float32)
122
+
123
+ if not normalized:
124
+ max_value = (1 << bit_depth) - 1
125
+ yuv /= max_value
126
+
127
+ # Split channels and center the chroma
128
+ Y = yuv[..., 0]
129
+ U = yuv[..., 1] - 0.5
130
+ V = yuv[..., 2] - 0.5
131
+
132
+ # Load conversion matrix
133
+ matrix = get_yuv_to_rgb_matrix(standard)
134
+
135
+ # matrix should be 3x3
136
+ rgb = np.stack((Y, U, V), axis=-1)
137
+ rgb = rgb @ matrix.T
138
+
139
+ # Clip
140
+ rgb = np.clip(rgb, 0.0, 1.0)
141
+
142
+ # Output scaling
143
+ if output_range == "integer":
144
+ rgb *= (1 << bit_depth) - 1
145
+
146
+ # Output dtype
147
+ if output_dtype is not None:
148
+ rgb = rgb.astype(output_dtype)
149
+
150
+ # Convert back to CHW if requested
151
+ if output_layout == "CHW":
152
+ rgb = np.transpose(rgb, (2, 0, 1))
153
+
154
+ return rgb
155
+
156
+
157
+
158
+
159
+
160
+ def yuv420_to_rgb(
161
+ y: np.ndarray,
162
+ u: np.ndarray,
163
+ v: np.ndarray,
164
+ *,
165
+ standard: str = "bt709",
166
+ bit_depth: int = 8,
167
+ normalized: bool = False,
168
+ input_layout: str = "HWC",
169
+ output_layout: str = "HWC",
170
+ output_range: str = "normalized",
171
+ output_dtype=None,
172
+ ) -> np.ndarray:
173
+ """
174
+ Convert a YUV420 image to RGB.
175
+
176
+ Parameters
177
+ ----------
178
+ y : numpy.ndarray
179
+ Luma plane.
180
+
181
+ u : numpy.ndarray
182
+ Chroma-U plane (subsampled 2x).
183
+
184
+ v : numpy.ndarray
185
+ Chroma-V plane (subsampled 2x).
186
+
187
+ Other Parameters
188
+ ----------------
189
+ Same as yuv444_to_rgb().
190
+
191
+ Returns
192
+ -------
193
+ numpy.ndarray
194
+ RGB image.
195
+ """
196
+
197
+ # Convert CHW -> HWC if necessary
198
+ if input_layout == "CHW":
199
+ if y.ndim == 3:
200
+ y = y[0]
201
+
202
+ if u.ndim == 3:
203
+ u = u[0]
204
+
205
+ if v.ndim == 3:
206
+ v = v[0]
207
+
208
+ # Validate
209
+ if y.shape[0] != u.shape[0] * 2 or y.shape[1] != u.shape[1] * 2:
210
+ raise ValueError("U plane size is inconsistent with Y plane.")
211
+
212
+ if y.shape[0] != v.shape[0] * 2 or y.shape[1] != v.shape[1] * 2:
213
+ raise ValueError("V plane size is inconsistent with Y plane.")
214
+
215
+ # Upsample U and V (nearest neighbour)
216
+ u = np.repeat(np.repeat(u, 2, axis=0), 2, axis=1)
217
+ v = np.repeat(np.repeat(v, 2, axis=0), 2, axis=1)
218
+
219
+ # Build YUV444
220
+ yuv444 = np.stack((y, u, v), axis=-1)
221
+
222
+ # Reuse the YUV444 conversion
223
+ return yuv444_to_rgb(
224
+ yuv444,
225
+ standard=standard,
226
+ bit_depth=bit_depth,
227
+ normalized=normalized,
228
+ input_layout="HWC",
229
+ output_layout=output_layout,
230
+ output_range=output_range,
231
+ output_dtype=output_dtype,
232
+ )
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyColorSpace
3
+ Version: 0.0.1
4
+ Summary: Color space conversion library
5
+ Requires-Python: >=3.9
6
+ License-File: LICENSE
7
+ Requires-Dist: numpy
8
+ Requires-Dist: opencv-python
9
+ Dynamic: license-file
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ pyproject.toml
3
+ src/PyColorSpace/__init__.py
4
+ src/PyColorSpace/hsv.py
5
+ src/PyColorSpace/rgb.py
6
+ src/PyColorSpace/yuv.py
7
+ src/PyColorSpace.egg-info/PKG-INFO
8
+ src/PyColorSpace.egg-info/SOURCES.txt
9
+ src/PyColorSpace.egg-info/dependency_links.txt
10
+ src/PyColorSpace.egg-info/requires.txt
11
+ src/PyColorSpace.egg-info/top_level.txt
12
+ src/PyColorSpace/matrices/__init__.py
13
+ src/PyColorSpace/matrices/bt2020.py
14
+ src/PyColorSpace/matrices/bt601.py
15
+ src/PyColorSpace/matrices/bt709.py
16
+ src/PyColorSpace/utils/__init__.py
17
+ src/PyColorSpace/utils/get_rgb_to_yuv_matrix.py
18
+ src/PyColorSpace/utils/get_yuv_to_rgb_matrix.py
19
+ tests/test.py
@@ -0,0 +1,2 @@
1
+ numpy
2
+ opencv-python
@@ -0,0 +1 @@
1
+ PyColorSpace
@@ -0,0 +1,9 @@
1
+
2
+ import PyColorSpace as pcs
3
+ import numpy as np
4
+
5
+ patch_path = "/home/anukulmaity/nvme1n/Frame_Extraction/Milestone 2/M2 Patch Extractions/Datasets/New_Image_Dataset/Div2k/Div2k_patches/Patches/DIV2K__036_001.npy"
6
+ yuv = np.load(patch_path)
7
+
8
+ rgb = pcs.yuv444_to_rgb(yuv, input_layout="CHW", output_layout="CHW")
9
+ print(rgb.shape)