Coverage for src/colorspace/colorlib.py: 98%

1066 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-23 19:54 +0000

1# """ 

2# Copyright 2005, Ross Ihaka. All Rights Reserved. 

3# Ported to Python by Reto Stauffer. 

4#  

5# Redistribution and use in source and binary forms, with or without 

6# modification, are permitted provided that the following conditions 

7# are met: 

8#  

9# 1. Redistributions of source code must retain the above copyright notice, 

10# this list of conditions and the following disclaimer. 

11#  

12# 2. Redistributions in binary form must reproduce the above copyright 

13# notice, this list of conditions and the following disclaimer in the 

14# documentation and/or other materials provided with the distribution. 

15#  

16# 3. The name of the Ross Ihaka may not be used to endorse or promote 

17# products derived from this software without specific prior written 

18# permission. 

19#  

20# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' 

21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 

22# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 

23# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ROSS IHAKA BE LIABLE FOR 

24# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 

25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 

26# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 

27# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 

28# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 

29# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 

30# POSSIBILITY OF SUCH DAMAGE. 

31# """ 

32 

33 

34import sys 

35import numpy as np 

36import inspect 

37 

38class colorlib: 

39 """Color Handling Superclass 

40 

41 The `colorlib` class provides a series of methods methods 

42 which allow to convert colors between different color spaces 

43 and is not intended to be used by end-users (not exported). 

44 

45 Users should use the dedicated classes for the available color spaces which 

46 all extend this class. These are 

47 :py:class:`CIELAB`, :py:class:`CIELUV`, :py:class:`CIEXYZ`, 

48 :py:class:`HLS`, :py:class:`HSV`, :py:class:`RGB`, :py:class:`hexcols`, 

49 :py:class:`polarLAB`, :py:class:`polarLUV`, and :py:class:`sRGB`. 

50 """ 

51 

52 # No initialization method, but some constants are specified here 

53 

54 _KAPPA = 24389.0 / 27.0 

55 """Static constant; required for coordinate transformations. 

56 Often approximated as 903.3.""" 

57 

58 _EPSILON = 216.0 / 24389.0 

59 """Static constant; required for coordinate transformations. 

60 Often approximated as 7.787.""" 

61 

62 # Default white spot 

63 XN = np.asarray([ 95.047]) 

64 """X value for default white spot. Used for coordinate transformations.""" 

65 YN = np.asarray([100.000]) 

66 """Y value for default white spot. Used for coordinate transformations.""" 

67 ZN = np.asarray([108.883]) 

68 """Z value for default white spot. Used for coordinate transformations.""" 

69 

70 # Conversion function 

71 def _DEG2RAD(self, x): 

72 """Convert degrees into radiant 

73 

74 Args: 

75 x (float, array of floats): Value(s) in degrees. 

76 

77 Returns: 

78 float, float array: Returns input `x` in radiant. 

79 """ 

80 return np.pi / 180. * x 

81 

82 

83 # Conversion function 

84 def _RAD2DEG(self, x): 

85 """Convert Radiant to Degrees 

86 

87 Converting degrees to radiants, used to convert to polar 

88 color coordinates. 

89 

90 Args: 

91 x (float, array of floats): Value(s) in radiant. 

92 

93 Returns: 

94 float, array of floats: Returns input `x` in degrees. 

95 """ 

96 return 180. / np.pi * x 

97 

98 

99 def _get_white_(self, __fname__, n, XN = None, YN = None, ZN = None): 

100 """Get Whitepoint 

101 

102 For some color conversion functions the "white" definition (default 

103 white color) has to be specified. This function checks and prepares the 

104 `XN`, `YN`, and `ZN` definition. Defaults are used if the user does not specify a 

105 custom white point. If set, `XN`, `YN`, and `ZN` have to be of type `numpy.ndarray`, 

106 either of length one (will be expanded to length "n"), or of length `n`. 

107 

108 Args: 

109 __fname__ (str): Name of the parent method, only used if errors are dropped. 

110 n (int): Number of colors to which `NX`, `NY`, and `NZ` will be expanded. 

111 XN (None, float, numpy.ndarray): Either `None` (default) or an 

112 `nd.array` of length one or length `n`. White point specification for 

113 dimension `X`, defaults to `None`. 

114 YN (None, float, numpy.ndarray): See `XN`. White point specification for 

115 dimension `Y`, defaults to `None`. 

116 YZ (None, numpy.ndarray): See `XN`. White point specification for 

117 dimension `Z`, defaults to `None`. 

118 

119 Raises: 

120 TypeError: If `XN`, `YN` and `ZN` are invalid (not `None` nor in a format 

121 that can be converted into a `numpy.ndarray`). 

122 ValueError: If the resulting values `XN`, `YN`, and `ZN` are not all 

123 of the same length. 

124 

125 Returns: 

126 list: Returns a list `[XN, YN, ZN]` with three `numpy.ndarrays` 

127 of length `n`. If the inputs `XN`, `YN`, `ZN` (or some) were `None`, 

128 the class defaults are used. 

129 """ 

130 

131 # Take defaults if not further specified 

132 if not XN: XN = self.XN 

133 if not YN: YN = self.YN 

134 if not ZN: ZN = self.ZN 

135 

136 if isinstance(XN, float): XN = np.asarray([XN]) 

137 if isinstance(YN, float): YN = np.asarray([YN]) 

138 if isinstance(ZN, float): ZN = np.asarray([ZN]) 

139 

140 # Expand if required 

141 if len(XN) == 1 and not len(XN) == n: XN = np.repeat(XN, n) 

142 if len(YN) == 1 and not len(YN) == n: YN = np.repeat(YN, n) 

143 if len(ZN) == 1 and not len(ZN) == n: ZN = np.repeat(ZN, n) 

144 

145 # Check if all lengths match 

146 if not np.all([len(x) == n for x in [XN, YN, ZN]]): 

147 raise ValueError(f"arguments XN/YN/ZN to `{__fname__} have to be of the same length") 

148 

149 return [XN, YN, ZN] 

150 

151 

152 def _check_input_arrays_(self, __fname__, **kwargs): 

153 """Check Input Arrays 

154 

155 Checks if all inputs in `kwargs` are of type `numpy.ndarray` and of the 

156 same length. If not, the script will throw an exception and stop. 

157 

158 Args: 

159 __fname__ (str): Name of the method who called this check routine. 

160 Only used to drop a useful error message if required. 

161 **kwargs: Named keywords, objects to be checked. 

162 

163 Returns: 

164 bool: Returns `True` if everything is OK, else an exception will be thrown. 

165 """ 

166 

167 # Message will be dropped if problems occur 

168 msg = "Problem while checking inputs \"{:s}\" to method \"{:s}\":".format( 

169 ", ".join(kwargs.keys()), __fname__) 

170 

171 from numpy import asarray 

172 lengths = [] 

173 for key,val in kwargs.items(): 

174 

175 try: 

176 val = asarray(val) 

177 except Exception as e: 

178 raise ValueError(f"argument `{key}` to {self.__class__.__name__} " + \ 

179 "could not have been converted to numpy.ndarray") 

180 # Else append length and proceed 

181 lengths.append(len(val)) 

182 

183 # Check if all do have the same length 

184 if not np.all([x == lengths[0] for x in lengths]): 

185 tmp = [] 

186 for k,v in kwargs.items(): tmp.append(f"{k} = {v}") 

187 msg = f" Arguments of different lengths: {', '.join(tmp)}." 

188 raise ValueError(msg) 

189 

190 # If all is fine, simply return True 

191 return True 

192 

193 

194 # ------------------------------------------------------------------- 

195 # ------------------------------------------------------------------- 

196 # ------------------------------------------------------------------- 

197 # ------------------------------------------------------------------- 

198 

199 

200 

201 # ----- CIE-XYZ <-> Device dependent RGB ----- 

202 # 

203 # Gamma Correction 

204 # 

205 # The following two functions provide gamma correction which 

206 # can be used to switch between sRGB and linearized sRGB (RGB). 

207 # 

208 # The standard value of gamma for sRGB displays is approximately 2.2, 

209 # but more accurately is a combination of a linear transform and 

210 # a power transform with exponent 2.4 

211 # 

212 # gtrans maps linearized sRGB to sRGB. 

213 # ftrans provides the inverse map. 

214 def gtrans(self, u, gamma): 

215 """Gamma Correction 

216 

217 Function `gtrans` and `ftrans` provide gamma correction which 

218 can be used to switch between sRGB and linearised sRGB (RGB). 

219 

220 The standard value of gamma for sRGB displays is approximately `2.2`, 

221 but more accurately is a combination of a linear transform and 

222 a power transform with exponent `2.4`. 

223 `gtrans` maps linearised sRGB to sRGB, `ftrans` provides the inverse mapping. 

224 

225 Args: 

226 u (numpy.ndarray): Float array of length `N`. 

227 gamma (float, numpy.ndarray): gamma value; if float or 

228 `numpy.ndarray` of length one, `gamma` will be recycled if needed. 

229 

230 Returns: 

231 numpy.ndarray: Gamma corrected values, same length as input `u`. 

232 """ 

233 

234 __fname__ = inspect.stack()[0][3] # Name of this method 

235 

236 # Input check 

237 if isinstance(gamma, float): gamma = np.asarray([gamma]) 

238 if len(gamma) == 1 and not len(gamma) == len(u): 

239 gamma = np.repeat(gamma, len(u)) 

240 

241 # Checking inputs 

242 self._check_input_arrays_(__fname__, u = u, gamma = gamma) 

243 

244 # Transform 

245 for i,val in np.ndenumerate(u): 

246 if val > 0.00304: u[i] = 1.055 * np.power(val, (1. / gamma[i])) - 0.055 

247 else: u[i] = 12.92 * val 

248 

249 return u 

250 

251 def ftrans(self, u, gamma): 

252 """Gamma Correction 

253 

254 Function `gtrans` and `ftrans` provide gamma correction which 

255 can be used to switch between sRGB and linearised sRGB (RGB). 

256 

257 The standard value of gamma for sRGB displays is approximately `2.2`, 

258 but more accurately is a combination of a linear transform and 

259 a power transform with exponent `2.4`. 

260 `gtrans` maps linearised sRGB to sRGB, `ftrans` provides the inverse mapping. 

261 

262 Args: 

263 u (numpy.ndarray): Float array of length `N`. 

264 gamma (float, numpy.ndarray): gamma value; if float or 

265 `numpy.ndarray` of length one, `gamma` will be recycled if needed. 

266 

267 Returns: 

268 numpy.ndarray: Gamma corrected values, same length as input `u`. 

269 """ 

270 

271 __fname__ = inspect.stack()[0][3] # Name of this method 

272 

273 # Input check 

274 if isinstance(gamma, float): gamma = np.asarray([gamma]) 

275 if len(gamma) == 1 and not len(gamma) == len(u): 

276 gamma = np.repeat(gamma, len(u)) 

277 

278 # Checking inputs 

279 self._check_input_arrays_(__fname__, u = u, gamma = gamma) 

280 

281 # Transform  

282 for i,val in np.ndenumerate(u): 

283 if val > 0.03928: u[i] = np.power((val + 0.055) / 1.055, gamma[i]) 

284 else: u[i] = val / 12.92 

285 

286 return u 

287 

288 # Support function qtrans 

289 def _qtrans(self, q1, q2, hue): 

290 if hue > 360.: hue = hue - 360. 

291 if hue < 0: hue = hue + 360. 

292 

293 if hue < 60.: return q1 + (q2 - q1) * hue / 60. 

294 elif hue < 180.: return q2 

295 elif hue < 240.: return q1 + (q2 - q1) * (240. - hue) / 60. 

296 else: return q1 

297 

298 

299 def sRGB_to_RGB(self, R, G, B, gamma = 2.4): 

300 """Convert Standard RGB to RGB 

301 

302 Converting colors from the Standard RGB color space to RGB. 

303 

304 Args: 

305 R (numpy.ndarray): Intensities for red (`[0., 1.]`). 

306 G (numpy.ndarray): Intensities for green (`[0., 1.]`). 

307 B (numpy.ndarray): Intensities for blue (`[0., 1.]`). 

308 gamma (float): gamma adjustment, defaults to `2.4`. 

309 

310 Returns: 

311 list: Returns a list of `numpy.ndarray`s with `R`, `G`, and `B` values. 

312 """ 

313 

314 __fname__ = inspect.stack()[0][3] # Name of this method 

315 

316 # Input check 

317 if isinstance(gamma, float): gamma = np.asarray([gamma]) 

318 if len(gamma) == 1 and not len(gamma) == len(R): 

319 gamma = np.repeat(gamma, len(R)) 

320 

321 # Checking inputs 

322 self._check_input_arrays_(__fname__, R = R, G = G, B = B, gamma = gamma) 

323 

324 # Apply gamma correction 

325 return [self.ftrans(x, gamma) for x in [R, G, B]] 

326 

327 def RGB_to_sRGB(self, R, G, B, gamma = 2.4): 

328 """Convert RGB to Standard RGB 

329 

330 Converts one (or multiple) colors defined by their red, blue, green, 

331 and blue coordinates (`[0.0, 1.0]`) to the Standard RGB color space; 

332 returning a modified list of red, green, blue coordinates. 

333 

334 Args: 

335 R (numpy.ndarray): Intensities for red (`[0., 1.]`). 

336 G (numpy.ndarray): Intensities for green (`[0., 1.]`). 

337 B (numpy.ndarray): Intensities for blue (`[0., 1.]`). 

338 gamma (float): gamma adjustment, defaults to `2.4`. 

339 

340 Returns: 

341 list: Returns a list of `numpy.ndarray`s with `R`, `G`, and `B` values. 

342 """ 

343 

344 __fname__ = inspect.stack()[0][3] # Name of this method 

345 

346 # Input check 

347 if isinstance(gamma, float): gamma = np.asarray([gamma]) 

348 if len(gamma) == 1 and not len(gamma) == len(R): 

349 gamma = np.repeat(gamma, len(R)) 

350 

351 # Checking inputs 

352 self._check_input_arrays_(__fname__, R = R, G = G, B = B, gamma = gamma) 

353 

354 # Apply gamma correction 

355 return [self.gtrans(x, gamma) for x in [R, G, B]] 

356 

357 # ------------------------------------------------------------------- 

358 # ------------------------------------------------------------------- 

359 # ------------------------------------------------------------------- 

360 # ------------------------------------------------------------------- 

361 

362 ## ----- CIE-XYZ <-> Device independent RGB ----- 

363 ## R, G, and B give the levels of red, green and blue as values 

364 ## in the interval [0., 1.]. X, Y and Z give the CIE chromaticies. 

365 ## XN, YN, ZN gives the chromaticity of the white point. 

366 def RGB_to_XYZ(self, R, G, B, XN = None, YN = None, ZN = None): 

367 """Convert RGB to CIEXYZ 

368 

369 `R`, `G`, and `B` give the levels of red, green and blue as values 

370 in the interval `[0., 1.]`. 

371 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

372 specify a specific white point. 

373 

374 Args: 

375 R (numpy.ndarray): Intensities for red (`[0., 1.]`). 

376 G (numpy.ndarray): Intensities for green (`[0., 1.]`). 

377 B (numpy.ndarray): Intensities for blue (`[0., 1.]`). 

378 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

379 length `1`, the white point specification will be recycled if needed. 

380 When not specified (all `None`) a default white point is used. 

381 YN: See `XN`. 

382 ZN: See `XN`. 

383 

384 Returns: 

385 list: Returns corresponding coordinates of CIE chromaticities, a 

386 list of `numpy.ndarray`s of the same length as the inputs (`[X, Y, Z]`). 

387 """ 

388 

389 __fname__ = inspect.stack()[0][3] # Name of this method 

390 n = len(R) # Number of colors 

391 

392 # Loading definition of white 

393 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

394 

395 # Checking input 

396 self._check_input_arrays_(__fname__, R = R, G = G, B = B) 

397 

398 return [YN * (0.412453 * R + 0.357580 * G + 0.180423 * B), # X 

399 YN * (0.212671 * R + 0.715160 * G + 0.072169 * B), # Y 

400 YN * (0.019334 * R + 0.119193 * G + 0.950227 * B)] # Z 

401 

402 def XYZ_to_RGB(self, X, Y, Z, XN = None, YN = None, ZN = None): 

403 """Convert CIEXYZ to RGB 

404 

405 `X`, `Y`, and `Z` specify the values in the three coordinates of the 

406 CIEXYZ color space, 

407 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

408 specify a specific white point. 

409 

410 Args: 

411 X (numpy.ndarray): Values for the `X` dimension. 

412 Y (numpy.ndarray): Values for the `Y` dimension. 

413 Z (numpy.ndarray): Values for the `Z` dimension. 

414 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

415 length `1`, the white point specification will be recycled if needed. 

416 When not specified (all `None`) a default white point is used. 

417 YN: See `XN`. 

418 ZN: See `XN`. 

419 

420 Returns: 

421 list: Returns corresponding coordinates as a list of 

422 `numpy.ndarray`s of the same length as the inputs (`[R, G, B]`). 

423 """ 

424 

425 __fname__ = inspect.stack()[0][3] # Name of this method 

426 n = len(X) # Number of colors 

427 

428 # Loading definition of white 

429 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

430 

431 # Checking input 

432 self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

433 

434 # Only YN is used 

435 return [( 3.240479 * X - 1.537150 * Y - 0.498535 * Z) / YN, # R 

436 (-0.969256 * X + 1.875992 * Y + 0.041556 * Z) / YN, # G 

437 ( 0.055648 * X - 0.204043 * Y + 1.057311 * Z) / YN] # B 

438 

439 

440 # ------------------------------------------------------------------- 

441 # ------------------------------------------------------------------- 

442 # ------------------------------------------------------------------- 

443 # ------------------------------------------------------------------- 

444 

445 

446 ## Unused as we are going CIE-XYZ <-> RGB <-> sRGB 

447 ## 

448 ## ## ----- CIE-XYZ <-> sRGB ----- 

449 ## ## R, G, and B give the levels of red, green and blue as values 

450 ## ## in the interval [0., 1.]. X, Y and Z give the CIE chromaticies. 

451 ## ## XN, YN, ZN gives the chromaticity of the white point. 

452 ## def sRGB_to_XYZ(self, R, G, B, XN = None, YN = None, ZN = None): 

453 ## """sRGB to CIEXYZ. 

454 

455 ## R, G, and B give the levels of red, green and blue as values 

456 ## in the interval `[0., 1.]`. X, Y and Z give the CIE chromaticies. 

457 

458 ## Args: 

459 ## R (numpy.ndarray): Indensities for red (`[0., 1.]`). 

460 ## G (numpy.ndarray): Indensities for green (`[0., 1.]`). 

461 ## B (numpy.ndarray): Indensities for blue (`[0., 1.]`). 

462 ## XN (None or numpy.ndarray): Chromaticity of the white point. If of 

463 ## length 1 the white point specification will be recycled if length of 

464 ## R/G/B is larger than one. If not specified (all three `None`) default 

465 ## values will be used. Defaults to None, see also YN, ZN. 

466 ## YN: See `XN`. 

467 ## ZN: See `XN`. 

468 

469 ## Returns: 

470 ## list: Returns corresponding X/Y/Z coordinates of CIE chromaticies, a list 

471 ## of `numpy.ndarray`'s of the same length as the inputs (`[X, Y, 

472 ## Z]`). 

473 ## """ 

474 

475 ## __fname__ = inspect.stack()[0][3] # Name of this method 

476 ## n = len(R) # Number of colors 

477 

478 ## # Loading definition of white 

479 ## [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

480 

481 ## # Checking input 

482 ## self._check_input_arrays_(__fname__, R = R, G = G, B = B) 

483 

484 ## # Transform R/G/B 

485 ## R = self.ftrans(R, 2.4) 

486 ## G = self.ftrans(G, 2.4) 

487 ## B = self.ftrans(B, 2.4) 

488 

489 ## # Convert to X/Y/Z coordinates 

490 ## return[YN * (0.412453 * R + 0.357580 * G + 0.180423 * B), # X 

491 ## YN * (0.212671 * R + 0.715160 * G + 0.072169 * B), # Y 

492 ## YN * (0.019334 * R + 0.119193 * G + 0.950227 * B)] # Z 

493 

494 ## def XYZ_to_sRGB(self, X, Y, Z, XN = None, YN = None, ZN = None): 

495 ## """CIEXYZ to sRGB. 

496 

497 ## R, G, and B give the levels of red, green and blue as values 

498 ## in the interval `[0., 1.]`. X, Y and Z give the CIE chromaticies. 

499 

500 ## Args: 

501 ## X (numpy.ndarray): Values for the X dimension. 

502 ## Y (numpy.ndarray): Values for the Y dimension. 

503 ## Z (numpy.ndarray): Values for the Z dimension. 

504 ## XN (None or numpy.ndarray): Chromaticity of the white point. If of 

505 ## length 1 the white point specification will be recycled if length of 

506 ## R/G/B is larger than one. If not specified (all three `None`) default 

507 ## values will be used. Defaults to None, see also YN, ZN. 

508 ## YN: See `XN`. 

509 ## ZN: See `XN`. 

510 

511 ## Returns: 

512 ## list: Returns corresponding X/Y/Z coordinates of CIE chromaticies, a list 

513 ## of `numpy.ndarray`'s of the same length as the inputs (`[R, G, B]`). 

514 ## """ 

515 

516 ## __fname__ = inspect.stack()[0][3] # Name of this method 

517 ## n = len(X) # Number of colors 

518 

519 ## # Loading definition of white 

520 ## [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

521 

522 ## # Checking input 

523 ## self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

524 

525 ## # Transform and return 

526 ## return [self.gtrans(( 3.240479 * X - 1.537150 * Y - 0.498535 * Z) / YN, 2.4), # R 

527 ## self.gtrans((-0.969256 * X + 1.875992 * Y + 0.041556 * Z) / YN, 2.4), # G 

528 ## self.gtrans(( 0.055648 * X - 0.204043 * Y + 1.057311 * Z) / YN, 2.4)] # B 

529 

530 

531 # ------------------------------------------------------------------- 

532 # ------------------------------------------------------------------- 

533 # ------------------------------------------------------------------- 

534 # ------------------------------------------------------------------- 

535 

536 ## ----- CIE-XYZ <-> CIE-LAB ----- */ 

537 

538 

539 def LAB_to_XYZ(self, L, A, B, XN = None, YN = None, ZN = None): 

540 """Convert CIELAB to CIEXYZ 

541 

542 `L`, `A`, and `B` specify the values in the three coordinates of the 

543 CIELAB color space, 

544 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

545 specify a specific white point. 

546 

547 Args: 

548 L (numpy.ndarray): Values for the `L` dimension. 

549 A (numpy.ndarray): Values for the `A` dimension. 

550 B (numpy.ndarray): Values for the `B` dimension. 

551 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

552 length `1`, the white point specification will be recycled if needed. 

553 When not specified (all `None`) a default white point is used. 

554 YN: See `XN`. 

555 ZN: See `XN`. 

556 

557 Returns: 

558 list: Returns corresponding coordinates of CIE chromaticities as a 

559 list of `numpy.ndarray`s of the same length as the inputs (`[X, Y, Z]`). 

560 """ 

561 

562 __fname__ = inspect.stack()[0][3] # Name of this method 

563 n = len(L) # Number of colors 

564 

565 # Loading definition of white 

566 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

567 

568 # Checking input 

569 self._check_input_arrays_(__fname__, L = L, A = A, B = B) 

570 

571 # Result arrays 

572 X = np.ndarray(len(L), dtype = "float"); X[:] = 0. 

573 Y = np.ndarray(len(L), dtype = "float"); Y[:] = 0. 

574 Z = np.ndarray(len(L), dtype = "float"); Z[:] = 0. 

575 

576 # Calculate Y 

577 for i,val in np.ndenumerate(L): 

578 if val <= 0: Y[i] = 0. 

579 elif val <= 8.0: Y[i] = val * YN[i] / self._KAPPA 

580 elif val <= 100.: Y[i] = YN[i] * np.power((val + 16.) / 116., 3.) 

581 else: Y[i] = YN[i] 

582 

583 fy = np.ndarray(len(Y), dtype = "float") 

584 for i,val in np.ndenumerate(Y): 

585 if val <= (self._EPSILON * YN[i]): 

586 fy[i] = (self._KAPPA / 116.) * val / YN[i] + 16. / 116. 

587 else: 

588 fy[i] = np.power(val / YN[i], 1. / 3.) 

589 

590 # Calculate X 

591 fx = fy + (A / 500.) 

592 for i,val in np.ndenumerate(fx): 

593 if np.power(val, 3.) <= self._EPSILON: 

594 X[i] = XN[i] * (val - 16. / 116.) / (self._KAPPA / 116.) 

595 else: 

596 X[i] = XN[i] * np.power(val, 3.) 

597 

598 # Calculate Z 

599 fz = fy - (B / 200.) 

600 for i,val in np.ndenumerate(fz): 

601 if np.power(val, 3.) <= self._EPSILON: 

602 Z[i] = ZN[i] * (val - 16. / 116.) / (self._KAPPA / 116.) 

603 else: 

604 Z[i] = ZN[i] * np.power(val, 3) 

605 

606 return [X, Y, Z] 

607 

608 def XYZ_to_LAB(self, X, Y, Z, XN = None, YN = None, ZN = None): 

609 """Convert CIEXYZ to CIELAB 

610 

611 `X`, `Y`, and `Z` specify the values in the three coordinates of the 

612 CIELAB color space, 

613 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

614 specify a specific white point. 

615 

616 Args: 

617 X (numpy.ndarray): Values for the `X` dimension. 

618 Y (numpy.ndarray): Values for the `Y` dimension. 

619 Z (numpy.ndarray): Values for the `Z` dimension. 

620 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

621 length `1`, the white point specification will be recycled if needed. 

622 When not specified (all `None`) a default white point is used. 

623 YN: See `XN`. 

624 ZN: See `XN`. 

625 

626 Returns: 

627 list: Returns corresponding coordinates of CIE chromaticities as 

628 a list of `numpy.ndarray`s of the same length as the inputs (`[L, A, B]`). 

629 """ 

630 

631 __fname__ = inspect.stack()[0][3] # Name of this method 

632 n = len(X) # Number of colors 

633 

634 # Loading definition of white 

635 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

636 

637 # Checking input 

638 self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

639 

640 # Support function 

641 def f(t, _KAPPA, _EPSILON): 

642 for i,val in np.ndenumerate(t): 

643 if val > _EPSILON: 

644 t[i] = np.power(val, 1./3.) 

645 else: 

646 t[i] = (_KAPPA / 116.) * val + 16. / 116. 

647 return t 

648 

649 # Scaling 

650 xr = X / XN; 

651 yr = Y / YN; 

652 zr = Z / ZN; 

653 

654 # Calculate L 

655 L = np.ndarray(len(X), dtype = "float"); L[:] = 0. 

656 for i,val in np.ndenumerate(yr): 

657 if val > self._EPSILON: 

658 L[i] = 116. * np.power(val, 1./3.) - 16. 

659 else: 

660 L[i] = self._KAPPA * val 

661 

662 xt = f(xr, self._KAPPA, self._EPSILON); 

663 yt = f(yr, self._KAPPA, self._EPSILON); 

664 zt = f(zr, self._KAPPA, self._EPSILON); 

665 return [L, 500. * (xt - yt), 200. * (yt - zt)] # [L, A, B] 

666 

667 

668 # ------------------------------------------------------------------- 

669 # ------------------------------------------------------------------- 

670 # ------------------------------------------------------------------- 

671 # ------------------------------------------------------------------- 

672 

673 ## Commented as not yet used 

674 ## 

675 ## def XYZ_to_HLAB(self, X, Y, Z, XN = None, YN = None, ZN = None): 

676 ## """CIE-XYZ to Hunter LAB. 

677 

678 ## .. note:: 

679 ## Note that the Hunter LAB is no longer part of the public API, 

680 ## but the code is still here in case needed. 

681 

682 ## Args: 

683 ## X (numpy.ndarray): Values for the X dimension. 

684 ## Y (numpy.ndarray): Values for the Y dimension. 

685 ## Z (numpy.ndarray): Values for the Z dimension. 

686 ## XN (None or numpy.ndarray): Chromaticity of the white point. If of 

687 ## length 1 the white point specification will be recycled if length of 

688 ## R/G/B is larger than one. If not specified (all three `None`) default 

689 ## values will be used. Defaults to None, see also YN, ZN. 

690 ## YN: See `XN`. 

691 ## ZN: See `XN`. 

692 

693 ## Returns: 

694 ## list: Returns corresponding Hunter LAB chromaticies, a list of 

695 ## `numpy.ndarray`'s of the same length as the inputs (`[L, A, B]`). 

696 ## """ 

697 

698 ## __fname__ = inspect.stack()[0][3] # Name of this method 

699 ## n = len(X) # Number of colors 

700 

701 ## # Loading definition of white 

702 ## [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

703 

704 ## # Checking input 

705 ## self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

706 

707 ## # Transform 

708 ## X = X / XN; Y = Y / YN; Z = Z / ZN; 

709 ## l = np.sqrt(Y); 

710 ## return [10. * l, 17.5 * (((1.02 * X) - Y) / l), 7. * ((Y - (0.847 * Z)) / l)] # [L, A, B] 

711 

712 

713 ## def HLAB_to_XYZ(self, L, A, B, XN = None, YN = None, ZN = None): 

714 ## """Hunter LAB to CIE-XYZ. 

715 

716 ## .. note:: 

717 ## Note that the Hunter LAB is no longer part of the public API, 

718 ## but the code is still here in case needed. 

719 

720 ## Args: 

721 ## L (numpy.ndarray): Values for the L dimension. 

722 ## A (numpy.ndarray): Values for the A dimension. 

723 ## B (numpy.ndarray): Values for the B dimension. 

724 ## XN (None or numpy.ndarray): Chromaticity of the white point. If of 

725 ## length 1 the white point specification will be recycled if length of 

726 ## R/G/B is larger than one. If not specified (all three `None`) default 

727 ## values will be used. Defaults to None, see also YN, ZN. 

728 ## YN: See `XN`. 

729 ## ZN: See `XN`. 

730 

731 ## Returns: 

732 ## list: Returns corresponding CIE-XYZ chromaticies, a list of 

733 ## `numpy.ndarray`'s of the same length as the inputs (`[X, Y, Z]`). 

734 ## """ 

735 

736 ## __fname__ = inspect.stack()[0][3] # Name of this method 

737 ## n = len(L) # Number of colors 

738 

739 ## # Loading definition of white 

740 ## [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

741 

742 ## # Checking input 

743 ## self._check_input_arrays_(__fname__, L = L, A = A, B = B) 

744 

745 ## # Transform 

746 ## vY = L / 10.; 

747 ## vX = (A / 17.5) * (L / 10); 

748 ## vZ = (B / 7) * (L / 10); 

749 ## vY = vY * vY; 

750 

751 ## Y = vY * XN 

752 ## X = (vX + vY) / 1.02 * YN 

753 ## Z = - (vZ - vY) / 0.847 * ZN 

754 

755 ## return [X, Y, Z] 

756 

757 

758 # ------------------------------------------------------------------- 

759 # ------------------------------------------------------------------- 

760 # ------------------------------------------------------------------- 

761 # ------------------------------------------------------------------- 

762 def LAB_to_polarLAB(self, L, A, B): 

763 """Convert CIELAB to the polar representation (polarLAB) 

764 

765 Converts colors from the CIELAB color space into its polar 

766 representation (`polarLAB`). 

767 Inverse function of :py:method:`polarLAB_to_LAB`. 

768 

769 Args: 

770 L (numpy.ndarray): Values for the `L` dimension. 

771 A (numpy.ndarray): Values for the `A` dimension. 

772 B (numpy.ndarray): Values for the `B` dimension. 

773 

774 Returns: 

775 list: Returns corresponding polar LAB chromaticities as a list of 

776 `numpy.ndarray`s of the same length as the inputs (`[L, A, B]`). 

777 """ 

778 

779 __fname__ = inspect.stack()[0][3] # Name of this method 

780 

781 # Checking input 

782 self._check_input_arrays_(__fname__, L = L, A = A, B = B) 

783 

784 # Compute H 

785 H = self._RAD2DEG(np.arctan2(B, A)) 

786 for i,val in np.ndenumerate(H): 

787 while val > 360.: val -= 360. 

788 while val < 0.: val += 360. 

789 H[i] = val 

790 # Compute C 

791 C = np.sqrt(A * A + B * B) 

792 

793 return [L, C, H] 

794 

795 def polarLAB_to_LAB(self, L, C, H): 

796 """Convert polarLAB to CIELAB 

797 

798 Convert colors from the polar representation of the CIELAB 

799 color space into CIELAB coordinates. 

800 Inverse function of :py:method:`LAB_to_polarLAB`. 

801 

802 Args: 

803 L (numpy.ndarray): Values for the polar `L` dimension. 

804 C (numpy.ndarray): Values for the polar `C` dimension. 

805 H (numpy.ndarray): Values for the polar `H` dimension. 

806 

807 Returns: 

808 list: Returns corresponding CIELAB chromaticities as a list of 

809 `numpy.ndarray`s of the same length as the inputs (`[L, A, B]`). 

810 """ 

811 

812 __fname__ = inspect.stack()[0][3] # Name of this method 

813 

814 # Checking input 

815 self._check_input_arrays_(__fname__, L = L, H = H, C = C) 

816 

817 A = np.cos(self._DEG2RAD(H)) * C 

818 B = np.sin(self._DEG2RAD(H)) * C 

819 

820 return [L, A, B] 

821 

822 # ------------------------------------------------------------------- 

823 # ------------------------------------------------------------------- 

824 # ------------------------------------------------------------------- 

825 # ------------------------------------------------------------------- 

826 def sRGB_to_HSV(self, r, g, b): 

827 """Convert RGB to HSV 

828 

829 Convert one (or multiple) rgb colors given their red, blue, and 

830 red coodinates (`[0.0, 1.0]`) to their corresponding hue, saturation, 

831 and value (HSV) coordinates. 

832 

833 Args: 

834 r (numpy.ndarray): Intensities for red (`[0., 1.]`). 

835 g (numpy.ndarray): Intensities for green (`[0., 1.]`). 

836 b (numpy.ndarray): Intensities for blue (`[0., 1.]`). 

837 

838 Returns: 

839 list: Returns a list of `numpy.ndarray`s with the corresponding 

840 coordinates in the HSV color space (`[h, s, v]`). Same length as 

841 the inputs. 

842 """ 

843 

844 __fname__ = inspect.stack()[0][3] # Name of this method 

845 

846 # Checking input 

847 self._check_input_arrays_(__fname__, r = r, g = g, b = b) 

848 

849 # Support function 

850 def gethsv(r, g, b): 

851 x = np.min([r, g, b]) 

852 y = np.max([r, g, b]) 

853 if y != x: 

854 f = g - b if r == x else b - r if g == x else r - g 

855 i = 3. if r == x else 5. if g == x else 1. 

856 h = 60. * (i - f /(y - x)) 

857 s = (y - x)/y 

858 v = y 

859 else: 

860 h = 0. 

861 s = 0. 

862 v = y 

863 return [h, s, v] 

864 

865 # Result arrays 

866 h = np.ndarray(len(r), dtype = "float"); h[:] = 0. 

867 s = np.ndarray(len(r), dtype = "float"); s[:] = 0. 

868 v = np.ndarray(len(r), dtype = "float"); v[:] = 0. 

869 

870 # Calculate h/s/v 

871 for i in range(0, len(r)): 

872 tmp = gethsv(r[i], g[i], b[i]) 

873 h[i] = tmp[0]; s[i] = tmp[1]; v[i] = tmp[2] 

874 

875 return [h, s, v] 

876 

877 

878 def HSV_to_sRGB(self, h, s, v): 

879 """Convert HSV to Standard RGB (sRGB) 

880 

881 Takes a series of HSV coordinates and converts them 

882 to the sRGB color space. 

883 

884 Args: 

885 h (nympy.ndarray): Hue values. 

886 s (numpy.ndarray): Saturation. 

887 v (numpy.ndarray): Value (the value-dimension of HSV). 

888 

889 Returns: 

890 list: Returns a list of `numpy.ndarray`s with the corresponding 

891 coordinates in the sRGB color space (`[r, g, b]`). Same length as 

892 the inputs. 

893 """ 

894 

895 __fname__ = inspect.stack()[0][3] # Name of this method 

896 

897 # Checking input 

898 self._check_input_arrays_(__fname__, h = h, s = s, v = v) 

899 

900 # Support function 

901 def getrgb(h, s, v): 

902 

903 # If Hue is not defined: 

904 if h == np.nan: return np.repeat(v, 3) 

905 

906 # Convert to [0-6] 

907 h = h / 60. 

908 i = np.floor(h) 

909 f = h - i 

910 

911 if (i % 2) == 0: # if i is even  

912 f = 1 - f 

913 

914 m = v * (1 - s) 

915 n = v * (1 - s * f) 

916 if i in [0, 6]: return [v, n, m] 

917 elif i == 1: return [n, v, m] 

918 elif i == 2: return [m, v, n] 

919 elif i == 3: return [m, n, v] 

920 elif i == 4: return [n, m, v] 

921 elif i == 5: return [v, m, n] 

922 else: 

923 raise Exception(f"ended up in a non-defined ifelse with i = {i:d}") 

924 

925 # Result arrays 

926 r = np.ndarray(len(h), dtype = "float"); r[:] = 0. 

927 g = np.ndarray(len(h), dtype = "float"); g[:] = 0. 

928 b = np.ndarray(len(h), dtype = "float"); b[:] = 0. 

929 

930 for i in range(0,len(h)): 

931 tmp = getrgb(h[i], s[i], v[i]) 

932 r[i] = tmp[0]; g[i] = tmp[1]; b[i] = tmp[2] 

933 

934 return [r, g, b] 

935 

936 

937 # ------------------------------------------------------------------- 

938 # ------------------------------------------------------------------- 

939 # ------------------------------------------------------------------- 

940 # ------------------------------------------------------------------- 

941 def sRGB_to_HLS(self, r, g, b): 

942 """Convert Standard RGB (sRGB) to HLS 

943 

944 All r/g/b values in `[0., 1.]`, h in `[0., 360.]`, l and s in `[0., 1.]`. 

945 From: <http://wiki.beyondunreal.com/wiki/RGB_To_HLS_Conversion>. 

946 

947 Args: 

948 r (numpy.ndarray): Intensities for red (`[0., 1.]`) 

949 g (numpy.ndarray): Intensities for green (`[0., 1.]`) 

950 b (numpy.ndarray): Intensities for blue (`[0., 1.]`) 

951 

952 Returns: 

953 list: Returns a list of `numpy.ndarray`s with the corresponding 

954 coordinates in the HLS color space (`[h, l, s]`). Same length as 

955 the inputs. 

956 """ 

957 

958 __fname__ = inspect.stack()[0][3] # Name of this method 

959 

960 # Checking input 

961 self._check_input_arrays_(__fname__, r = r, g = g, b = b) 

962 

963 # Support function 

964 def gethls(r, g, b): 

965 min = np.min([r, g, b]) 

966 max = np.max([r, g, b]) 

967 

968 l = (max + min)/2.; 

969 

970 if max != min: 

971 if l < 0.5: s = (max - min) / (max + min) 

972 elif l >= 0.5: s = (max - min) / (2. - max - min) 

973 

974 if r == max: h = (g - b) / (max - min); 

975 if g == max: h = 2. + (b - r) / (max - min); 

976 if b == max: h = 4. + (r - g) / (max - min); 

977 

978 h = h * 60.; 

979 if h < 0.: h = h + 360.; 

980 if h > 360.: h = h - 360.; 

981 else: 

982 s = 0 

983 h = 0; 

984 

985 return [h, l, s] 

986 

987 # Result arrays 

988 h = np.ndarray(len(r), dtype = "float"); h[:] = 0. 

989 l = np.ndarray(len(r), dtype = "float"); l[:] = 0. 

990 s = np.ndarray(len(r), dtype = "float"); s[:] = 0. 

991 

992 for i in range(0,len(h)): 

993 tmp = gethls(r[i], g[i], b[i]) 

994 h[i] = tmp[0]; l[i] = tmp[1]; s[i] = tmp[2] 

995 

996 return [h, l, s] 

997 

998 

999 def HLS_to_sRGB(self, h, l, s): 

1000 """Convert HLC to Standard RGB (sRGB) 

1001 

1002 All r/g/b values in `[0., 1.]`, h in `[0., 360.]`, l and s in `[0., 1.]`. 

1003 

1004 Args: 

1005 h (numpy.ndarray): Hue values. 

1006 l (numpy.ndarray): Lightness. 

1007 s (numpy.ndarray): Saturation. 

1008 

1009 Returns: 

1010 list: Returns a list of `numpy.ndarray`s with the corresponding 

1011 coordinates in the sRGB color space (`[r, g, b]`). Same length as 

1012 the inputs. 

1013 """ 

1014 

1015 __fname__ = inspect.stack()[0][3] # Name of this method 

1016 

1017 # Checking input 

1018 self._check_input_arrays_(__fname__, h = h, l = l, s = s) 

1019 

1020 # Support function 

1021 def getrgb(h, l, s): 

1022 p2 = l * (1. + s) if l <= 0.5 else l + s - (l * s) 

1023 p1 = 2 * l - p2 

1024 

1025 # If saturation is zero 

1026 if (s == 0): return np.repeat(l, 3) 

1027 # Else 

1028 return [self._qtrans(p1, p2, h + 120.), # r 

1029 self._qtrans(p1, p2, h), # g 

1030 self._qtrans(p1, p2, h - 120.)] # b 

1031 

1032 # Result arrays 

1033 r = np.ndarray(len(h), dtype = "float"); r[:] = 0. 

1034 g = np.ndarray(len(h), dtype = "float"); g[:] = 0. 

1035 b = np.ndarray(len(h), dtype = "float"); b[:] = 0. 

1036 

1037 for i in range(0,len(r)): 

1038 tmp = getrgb(h[i], l[i], s[i]) 

1039 r[i] = tmp[0]; g[i] = tmp[1]; b[i] = tmp[2] 

1040 

1041 return [r, g, b] 

1042 

1043 

1044 # ------------------------------------------------------------------- 

1045 # ------------------------------------------------------------------- 

1046 # ------------------------------------------------------------------- 

1047 # ------------------------------------------------------------------- 

1048 def XYZ_to_uv(self, X, Y, Z): 

1049 """Convert CIEXYZ to u and v 

1050 

1051 Converting one (or multiple) colors defined by their X, Y, and Z 

1052 coordinates in the CIEXYZ color space to their corresponding 

1053 u and v coordinates. 

1054 

1055 Args: 

1056 X (numpy.ndarray): Values for the `Z` dimension. 

1057 Y (numpy.ndarray): Values for the `Y` dimension. 

1058 Z (numpy.ndarray): Values for the `Z` dimension. 

1059 

1060 Returns: 

1061 list: Returns a list of `numpy.ndarray`s (`[u, v]`).  

1062 """ 

1063 

1064 __fname__ = inspect.stack()[0][3] # Name of this method 

1065 

1066 # Checking input 

1067 self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

1068 

1069 # Result array 

1070 x = np.ndarray(len(X), dtype = "float"); x[:] = 0. 

1071 y = np.ndarray(len(X), dtype = "float"); y[:] = 0. 

1072 

1073 t = X + Y + Z 

1074 idx = np.where(t != 0) 

1075 x[idx] = X[idx] / t[idx]; 

1076 y[idx] = Y[idx] / t[idx]; 

1077 

1078 return [2.0 * x / (6. * y - x + 1.5), # u 

1079 4.5 * y / (6. * y - x + 1.5)] # v 

1080 

1081 def XYZ_to_LUV(self, X, Y, Z, XN = None, YN = None, ZN = None): 

1082 """Convert CIEXYZ to CIELUV. 

1083 

1084 `X`, `Y`, and `Z` specify the values in the three coordinates of the 

1085 CIELAB color space, 

1086 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

1087 specify a specific white point. 

1088 

1089 Args: 

1090 X (numpy.ndarray): Values for the `X` dimension. 

1091 Y (numpy.ndarray): Values for the `Y` dimension. 

1092 Z (numpy.ndarray): Values for the `Z` dimension. 

1093 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

1094 length `1`, the white point specification will be recycled if needed. 

1095 When not specified (all `None`) a default white point is used. 

1096 YN: See `XN`. 

1097 ZN: See `XN`. 

1098 

1099 Returns: 

1100 list: Returns corresponding coordinates of CIE chromaticities as 

1101 a list of `numpy.ndarray`s of the same length as the inputs (`[L, U, V]`). 

1102 """ 

1103 

1104 __fname__ = inspect.stack()[0][3] # Name of this method 

1105 n = len(X) # Number of colors 

1106 

1107 # Loading definition of white 

1108 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

1109 

1110 # Checking input 

1111 self._check_input_arrays_(__fname__, X = X, Y = Y, Z = Z) 

1112 

1113 # Convert X/Y/Z and XN/YN/ZN to uv 

1114 [u, v] = self.XYZ_to_uv(X, Y, Z ) 

1115 [uN, vN] = self.XYZ_to_uv(XN, YN, ZN) 

1116 

1117 # Calculate L 

1118 L = np.ndarray(len(X), dtype = "float"); L[:] = 0. 

1119 y = Y / YN 

1120 for i,val in np.ndenumerate(y): 

1121 L[i] = 116. * np.power(val, 1./3.) - 16. if val > self._EPSILON else self._KAPPA * val 

1122 

1123 # Calculate U/V 

1124 return [L, 13. * L * (u - uN), 13. * L * (v - vN)] # [L, U, V] 

1125 

1126 def LUV_to_XYZ(self, L, U, V, XN = None, YN = None, ZN = None): 

1127 """Convert CIELUV to CIELAB 

1128 

1129 `L`, `U`, and `V` specify the values in the three coordinates of the 

1130 CIELAB color space, 

1131 `XN`, `YN`, and `ZN` allow to specify additional CIE chromaticities to 

1132 specify a specific white point. 

1133 

1134 Args: 

1135 L (numpy.ndarray): Values for the `L` dimension. 

1136 U (numpy.ndarray): Values for the `U` dimension. 

1137 V (numpy.ndarray): Values for the `V` dimension. 

1138 XN (None, numpy.ndarray): Chromaticity of the white point. If of 

1139 length `1`, the white point specification will be recycled if needed. 

1140 When not specified (all `None`) a default white point is used. 

1141 YN: See `XN`. 

1142 ZN: See `XN`. 

1143 

1144 Returns: 

1145 list: Returns corresponding coordinates of CIE chromaticities as 

1146 a list of `numpy.ndarray`s of the same length as the inputs (`[L, A, B]`). 

1147 """ 

1148 

1149 __fname__ = inspect.stack()[0][3] # Name of this method 

1150 n = len(L) # Number of colors 

1151 

1152 # Loading definition of white 

1153 [XN, YN, ZN] = self._get_white_(__fname__, n, XN, YN, ZN) 

1154 

1155 # Checking input 

1156 self._check_input_arrays_(__fname__, L = L, U = U, V = V) 

1157 

1158 # Result arrays 

1159 X = np.ndarray(len(L), dtype = "float"); X[:] = 0. 

1160 Y = np.ndarray(len(L), dtype = "float"); Y[:] = 0. 

1161 Z = np.ndarray(len(L), dtype = "float"); Z[:] = 0. 

1162 

1163 # Check for which values we do have to do the transformation 

1164 def fun(L, U, V): 

1165 return False if L <= 0. and U == 0. and V == 0. else True 

1166 idx = np.where([fun(L[i], U[i], V[i]) for i in range(0, len(L))])[0] 

1167 if len(idx) == 0: return [X, Y, Z] 

1168 

1169 # Compute Y 

1170 for i in idx: 

1171 Y[i] = YN[i] * (np.power((L[i] + 16.)/116., 3.) if L[i] > 8. else L[i] / self._KAPPA) 

1172 

1173 # Calculate X/Z 

1174 from numpy import finfo, fmax 

1175 

1176 # Avoiding division by zero 

1177 eps = np.finfo(float).eps*10 

1178 L = fmax(eps, L) 

1179 

1180 [uN, vN] = self.XYZ_to_uv(XN, YN, ZN) 

1181 u = U / (13. * L) + uN 

1182 v = V / (13. * L) + vN 

1183 X = 9.0 * Y * u / (4 * v) 

1184 Z = -X / 3. - 5. * Y + 3. * Y / v 

1185 

1186 return [X, Y, Z] 

1187 

1188 

1189 ## ----- LUV <-> polarLUV ----- */ 

1190 def LUV_to_polarLUV(self, L, U, V): 

1191 """Convert CIELUV to the polar representation (polarLUV; HCL) 

1192 

1193 Converts colors from the CIELUV color space into its polar 

1194 representation (`polarLUV`). The `polarLUV` color space 

1195 is also known as the HCL (Hue-Chroma-Luminance) color space 

1196 which this package uses frequently, e.g., when creating 

1197 efficient color maps. Inverse function of :py:method:`polarLUV_to_LUV`. 

1198 

1199 Args: 

1200 L (numpy.ndarray): Values for the `L` dimension. 

1201 U (numpy.ndarray): Values for the `U` dimension. 

1202 V (numpy.ndarray): Values for the `V` dimension. 

1203 

1204 Returns: 

1205 list: Returns corresponding polar LUV chromaticities as a list of 

1206 `numpy.ndarray`s of the same length as the inputs (`[L, A, B]`), 

1207 also known as `[H, C, L]` coordinates. 

1208 """ 

1209 

1210 __fname__ = inspect.stack()[0][3] # Name of this method 

1211 

1212 self._check_input_arrays_(__fname__, L = L, U = U, V = V) 

1213 

1214 # Calculate polarLUV coordinates 

1215 C = np.sqrt(U * U + V * V) 

1216 H = self._RAD2DEG(np.arctan2(V, U)) 

1217 for i,val in np.ndenumerate(H): 

1218 while val > 360: val -= 360. 

1219 while val < 0.: val += 360. 

1220 H[i] = val 

1221 

1222 return [L, C, H] 

1223 

1224 def polarLUV_to_LUV(self, L, C, H): 

1225 """Convert Polar CIELUV (HCL) to CIELUV 

1226 

1227 Convert colors from the polar representation of the CIELUV color space, 

1228 also known as HCL (Hue-Chroma-Luminance) color space, into CIELAB 

1229 coordinates. Inverse function of :py:method:`LUV_to_polarLUV`. 

1230 

1231 Args: 

1232 L (numpy.ndarray): Values for the polar `L` dimension (Luminance). 

1233 C (numpy.ndarray): Values for the polar `C` dimension (Chroma). 

1234 H (numpy.ndarray): Values for the polar `H` dimension (Hue). 

1235 

1236 Returns: 

1237 list: Returns corresponding CIELAB chromaticities as a list of 

1238 `numpy.ndarray`s of the same length as the inputs (`[L, U, V]`). 

1239 """ 

1240 

1241 __fname__ = inspect.stack()[0][3] # Name of this method 

1242 

1243 # Checking input 

1244 self._check_input_arrays_(__fname__, L = L, C = C, H = H) 

1245 

1246 H = self._DEG2RAD(H) 

1247 return [L, C * np.cos(H), C * np.sin(H)] # [L, U, V] 

1248 

1249 

1250 def sRGB_to_hex(self, r, g, b, fixup = True): 

1251 """Convert Standard RGB (sRGB) to Hex Colors 

1252 

1253 Converting one (or multiple) colors defined by their red, green, and 

1254 blue coordinates from the Standard RGB color space to hex colors. 

1255 

1256 Args: 

1257 r (numpy.ndarray): Intensities for red (`[0., 1.,]`). 

1258 g (numpy.ndarray): Intensities for green (`[0., 1.,]`). 

1259 b (numpy.ndarray): Intensities for blue (`[0., 1.,]`). 

1260 fixup (bool): Whether or not the `rgb` values should be corrected 

1261 if they lie outside the defined RGB space (outside `[0., 1.,]`), 

1262 defaults to `True`. 

1263 

1264 Returns: 

1265 list: A list with hex color str. 

1266 """ 

1267 

1268 # Color fixup: limit r/g/b to [0-1] 

1269 def rgbfixup(r, g, b): 

1270 def fun(x): 

1271 return np.asarray([np.max([0, np.min([1, e])]) \ 

1272 if np.isfinite(e) else np.nan for e in x]) 

1273 return [fun(r), fun(g), fun(b)] 

1274 

1275 def rgbcleanup(r, g, b): 

1276 def fun(x): 

1277 tol = 1. / (2 * 255.) 

1278 # Allow tiny correction close to 0. and 1. 

1279 x[np.logical_and(x < 0.0, x >= -tol)] = 0.0 

1280 x[np.logical_and(x > 1.0, x <= 1.0 + tol)] = 1.0 

1281 return np.asarray([e if np.logical_and(e >= 0., e <= 1.) 

1282 else np.nan for e in x]) 

1283 return [fun(r), fun(g), fun(b)] 

1284 

1285 # Checking which r/g/b values are outside limits. 

1286 # This only happens if fixup = FALSE. 

1287 def validrgb(r, g, b): 

1288 idxr = np.isfinite(r) 

1289 idxg = np.isfinite(g) 

1290 idxb = np.isfinite(b) 

1291 return np.where(idxr * idxg * idxb)[0] 

1292 

1293 # Support function to create hex coded colors 

1294 def gethex(r, g, b): 

1295 

1296 # Converts int to hex string 

1297 def applyfun(x): 

1298 x = np.asarray(x * 255. + .5, dtype = int) 

1299 return f"#{x[0]:02X}{x[1]:02X}{x[2]:02X}" 

1300 

1301 h = np.vstack([r,g,b]).transpose().flatten().reshape([len(r), 3]) 

1302 return np.apply_along_axis(applyfun, 1, h) 

1303 

1304 # Let's do the conversion! 

1305 if fixup: [r, g, b] = rgbfixup(r, g, b) 

1306 else: [r, g, b] = rgbcleanup(r, g, b) 

1307 

1308 # Create return array 

1309 res = np.ndarray(len(r), dtype = "|S7"); res[:] = "" 

1310 

1311 # Check valid r/g/b coordinates 

1312 valid = validrgb(r, g, b) 

1313 if len(valid) > 0: 

1314 # Convert valid colors to hex 

1315 res[valid] = gethex(r[valid], g[valid], b[valid]) 

1316 

1317 # Create return list with NAN's for invalid colors 

1318 res = [None if len(x) == 0 else x.decode() for x in res] 

1319 

1320 # Return numpy array 

1321 return np.asarray(res) 

1322 

1323 def hex_to_sRGB(self, hex_, gamma = 2.4): 

1324 """Convert Hex Colors to Standard RGB (sRGB) 

1325 

1326 Convert one (or multiple) hex colors to sRGB. 

1327 

1328 Args: 

1329 hex_ (str, list of str): hex color str or list of str. 

1330 gamma (float): Gamma correction factor, defaults to `2.4`. 

1331 

1332 Returns: 

1333 list: Returns a list of `numpy.ndarray`s with the corresponding 

1334 red, green, and blue intensities (`[r, g, b]`), all in `[0., 1.]`. 

1335 """ 

1336 

1337 if isinstance(hex_,str): hex_ = [hex_] 

1338 hex_ = np.asarray(hex_) 

1339 

1340 # Check for valid hex colors 

1341 def validhex(hex_): 

1342 from re import compile 

1343 pat = compile("^#[0-9A-Fa-f]{6}([0-9]{2})?$") 

1344 from re import match 

1345 return np.where([None if x is None else pat.match(x) is not None for x in hex_])[0] 

1346 

1347 # Convert hex to rgb 

1348 def getrgb(x): 

1349 def applyfun(x): 

1350 return np.asarray([int(x[i:i+2], 16) for i in (1, 3, 5)]) 

1351 rgb = [applyfun(e) for e in x] 

1352 rgb = np.vstack(rgb).transpose().flatten().reshape([3,len(x)]) 

1353 return [rgb[0] / 255., rgb[1] / 255., rgb[2] / 255.] 

1354 

1355 # Result arrays 

1356 r = np.ndarray(len(hex_), dtype = "float"); r[:] = np.nan 

1357 g = np.ndarray(len(hex_), dtype = "float"); g[:] = np.nan 

1358 b = np.ndarray(len(hex_), dtype = "float"); b[:] = np.nan 

1359 

1360 # Check valid hex colors 

1361 valid = validhex(hex_) 

1362 if not len(valid) == 0: 

1363 # Decode valid hex strings 

1364 rgb = getrgb(hex_[valid]) 

1365 r[valid] = rgb[0] 

1366 g[valid] = rgb[1] 

1367 b[valid] = rgb[2] 

1368 

1369 return [r, g, b] 

1370 

1371 

1372 # ------------------------------------------------------------------- 

1373 # Direct conversion ('shortcut') from RGB to HLS 

1374 def RGB_to_HLS(self, r, g, b): 

1375 """Convert RGB to HLS 

1376 

1377 Shortcut from RGB to HLS (not via sRGB). 

1378 All r/g/b values in `[0., 1.]`, h in `[0., 360.]`, l and s in `[0., 1.]`. 

1379 

1380 Args: 

1381 r (numpy.ndarray): Intensities for red (`[0., 1.]`) 

1382 g (numpy.ndarray): Intensities for green (`[0., 1.]`) 

1383 b (numpy.ndarray): Intensities for blue (`[0., 1.]`) 

1384 

1385 Returns: 

1386 list: Returns a list of `numpy.ndarray`s with the corresponding 

1387 coordinates in the HLS color space (`[h, l, s]`). Same length as 

1388 the inputs. 

1389 """ 

1390 

1391 __fname__ = inspect.stack()[0][3] # Name of this method 

1392 

1393 # Checking input 

1394 self._check_input_arrays_(__fname__, r = r, g = g, b = b) 

1395 

1396 # Create 2d numpy array where the first dimension corresponds 

1397 # to specific colors, the second one to [r, g, b] of that color. 

1398 tmp = np.transpose(np.stack((r, g, b))) 

1399 

1400 def gethls(x): 

1401 """x is expected to be a numpy array of length 3 with [r, g, b] coordinates.""" 

1402 

1403 mn = np.min(x) 

1404 mx = np.max(x) 

1405 

1406 # If minimum equals maximum we know the solution already 

1407 if mn == mx: return [0., mn, 0.] # [h, l, s] 

1408 

1409 # Else do the calculations 

1410 l = (mn + mx) / 2. 

1411 s = (mx - mn) / (mx + mn) if l < 0.5 else (mx - mn) / (2. - mx - mn) 

1412 

1413 # x[0] is 'r', x[1] = 'g', x[2] = 'b' 

1414 if x[0] == mx: h = 60. * (x[1] - x[2]) / (mx - mn) 

1415 elif x[1] == mx: h = 60. * (2. + (x[2] - x[0]) / (mx - mn)) 

1416 else: h = 60. * (4. + (x[0] - x[1]) / (mx - mn)) 

1417 

1418 if h < 0.: h = h + 360. 

1419 elif h > 360.: h = h - 360. 

1420 

1421 return [h, l, s] 

1422 

1423 

1424 return np.transpose([gethls(x) for x in tmp]) 

1425 

1426 

1427 # ------------------------------------------------------------------- 

1428 # Direct conversion ('shortcut') from HLS to RGB 

1429 def HLS_to_RGB(self, h, l, s): 

1430 """Convert HLS to RGB 

1431 

1432 Shortcut from HLS to RGB (not via sRGB). Expecting h in `[0., 360.]`, 

1433 l/s in `[0., 1.]`. Returns r/g/b in `[0.,1.]`. 

1434 

1435 Args: 

1436 h (numpy.ndarray): Hue (`[0., 360.]`) 

1437 l (numpy.ndarray): Luminance (`[0., 1.]`) 

1438 s (numpy.ndarray): Saturation (`[0., 1.]`)  

1439 

1440 Returns: 

1441 list: Returns a list of `numpy.ndarray`s with the corresponding 

1442 coordinates in the RGB color space (`[r, g, b]`). Same length as 

1443 the inputs. 

1444 """ 

1445 

1446 __fname__ = inspect.stack()[0][3] # Name of this method 

1447 

1448 # Checking input 

1449 self._check_input_arrays_(__fname__, h = h, l = l, s = s) 

1450 

1451 # Create 2d numpy array where the first dimension corresponds 

1452 # to specific colors, the second one to [r, g, b] of that color. 

1453 tmp = np.transpose(np.stack((h, l, s))) 

1454 

1455 def getrgb(x): 

1456 """x is expected to be a numpy array of length 3 with [h, l, s] coordinates.""" 

1457 

1458 # If saturation equals zero, return [l, l, l] 

1459 if x[2] == 0.: return [x[1], x[1], x[1]] 

1460 

1461 # x[0] = 'h', x[1] = 'l', x[2] = 's' 

1462 p2 = x[1] * (1 + x[2]) if x[1] <= 0.5 else x[1] + x[2] - (x[1] * x[2]) 

1463 p1 = 2 * x[1] - p2 

1464 

1465 return [self._qtrans(p1, p2, x[0] + 120.), 

1466 self._qtrans(p1, p2, x[0]), 

1467 self._qtrans(p1, p2, x[0] - 120.)] 

1468 

1469 return np.transpose([getrgb(x) for x in tmp]) 

1470 

1471 # ------------------------------------------------------------------- 

1472 # Direct conversion ('shortcut') from RGB to HSV 

1473 def RGB_to_HSV(self, r, g, b): 

1474 """Convert RGB to HSV 

1475 

1476 Shortcut from RGB to HSV (not via sRGB). 

1477 All r/g/b values in `[0., 1.]`, h in `[0., 360.]`, l and s in `[0., 1.]`. 

1478 

1479 Args: 

1480 r (numpy.ndarray): Intensities for red (`[0., 1.]`) 

1481 g (numpy.ndarray): Intensities for green (`[0., 1.]`) 

1482 b (numpy.ndarray): Intensities for blue (`[0., 1.]`) 

1483 

1484 Returns: 

1485 list: Returns a list of `numpy.ndarray`s with the corresponding 

1486 coordinates in the HSV color space (`[h, s, v]`). Same length as 

1487 the inputs. 

1488 """ 

1489 

1490 __fname__ = inspect.stack()[0][3] # Name of this method 

1491 

1492 # Checking input 

1493 self._check_input_arrays_(__fname__, r = r, g = g, b = b) 

1494 

1495 # Create 2d numpy array where the first dimension corresponds 

1496 # to specific colors, the second one to [r, g, b] of that color. 

1497 tmp = np.transpose(np.stack((r, g, b))) 

1498 

1499 def gethsv(x): 

1500 """x is expected to be a numpy array of length 3 with [r, g, b] coordinates.""" 

1501 

1502 mn = np.min(x) 

1503 mx = np.max(x) 

1504 

1505 # If minimum equals maximum we know the solution already 

1506 if mn == mx: return [0., 0., mx] # [h, s, v] 

1507 

1508 # Else calculate new dimensions 

1509 f = (x[1] - x[2]) if x[0] == mn else x[2] - x[0] if x[1] == mn else x[0] - x[1] 

1510 i = 3. if x[0] == mn else 5. if x[1] == mn else 1. 

1511 

1512 # Returning [h, s, v] 

1513 return [60. * (i - f / (mx - mn)), (mx - mn) / mx, mx] 

1514 

1515 return np.transpose([gethsv(x) for x in tmp]) 

1516 

1517 

1518 # ------------------------------------------------------------------- 

1519 # Direct conversion ('shortcut') from HSV to RGB 

1520 def HSV_to_RGB(self, h, s, v): 

1521 """Convert HSV to RGB 

1522 

1523 Shortcut from HLS to RGB (not via sRGB). Expecting h in `[0., 360.]`, 

1524 l/s in `[0., 1.]`. Returns r/g/b in `[0.,1.]`. 

1525 

1526 Args: 

1527 h (numpy.ndarray): Hue (`[0., 360.]`) 

1528 s (numpy.ndarray): Saturation (`[0., 1.]`)  

1529 v (numpy.ndarray): Value (`[0., 1.]`) 

1530 

1531 Returns: 

1532 list: Returns a list of `numpy.ndarray`s with the corresponding 

1533 coordinates in the RGB color space (`[r, g, b]`). Same length as 

1534 the inputs. 

1535 """ 

1536 

1537 __fname__ = inspect.stack()[0][3] # Name of this method 

1538 

1539 # Checking input 

1540 self._check_input_arrays_(__fname__, h = h, s = s, v = v) 

1541 

1542 # Create 2d numpy array where the first dimension corresponds 

1543 # to specific colors, the second one to [r, g, b] of that color. 

1544 tmp = np.transpose(np.stack((h, s, v))) 

1545 

1546 def getrgb(x): 

1547 """x is expected to be a numpy array of length 3 with [h, s, v] coordinates.""" 

1548 

1549 h = x[0] / 60. # Convert to [0, 6] 

1550 i = np.int8(np.floor(h)) 

1551 f = h - i 

1552 if i % 2 == 0: f = 1. - f # if i is even 

1553 

1554 m = x[2] * (1. - x[1]) 

1555 n = x[2] * (1. - x[1] * f) 

1556 

1557 if i == 0 or i == 6: return [x[2], n, m] 

1558 elif i == 1: return [n, x[2], m] 

1559 elif i == 2: return [m, x[2], n] 

1560 elif i == 3: return [m, n, x[2]] 

1561 elif i == 4: return [n, m, x[2]] 

1562 elif i == 5: return [x[2], m, n] 

1563 

1564 return np.transpose([getrgb(x) for x in tmp]) 

1565 

1566 

1567# ------------------------------------------------------------------- 

1568# Color object base class 

1569# will be extended by the different color classes. 

1570# ------------------------------------------------------------------- 

1571class colorobject: 

1572 """Superclass for All Color Objects 

1573 

1574 A series of constructors are available to construct `colorobjects` in a 

1575 variety of different color spaces, all inheriting from this class. This 

1576 superclass provides the general functionality to handle colors (sets of 

1577 colors) and convert colors from and to different color spaces. 

1578 

1579 Users should use the dedicated classes for the available color spaces which 

1580 all extend this class. These are: CIELAB, CIELUV, CIEXYZ, hexcols, HLS, 

1581 HSV, polarLAB, polarLUV, RGB, and sRGB. 

1582 """ 

1583 

1584 import numpy as np 

1585 

1586 # Allowed/defined color spaces 

1587 ALLOWED = ["CIEXYZ", "CIELUV", "CIELAB", "polarLUV", "polarLAB", 

1588 "RGB", "sRGB", "HCL", "HSV", "HLS", "hex"] 

1589 """List of allowed/defined color spaces; used to check when converting 

1590 colors from one color space to another.""" 

1591 

1592 # Used to store alpha if needed. Will only be used for some of 

1593 # the colorobject objects as only few color spaces allow alpha 

1594 # values. 

1595 ALPHA = None 

1596 """Used to store (keep) transparency when needed; will be dropped during conversion.""" 

1597 

1598 GAMMA = 2.4 # Used to adjust RGB (sRGB_to_RGB and back). 

1599 """Gamma value used used to adjust RGB colors; currently a fixed value of 2.4.""" 

1600 

1601 # Standard representation of colorobject objects. 

1602 def __repr__(self, digits = 2): 

1603 """Color Object Standard Representation 

1604 

1605 Standard representation of the color object; shows the values 

1606 of all coordinates (or the hex strings if hex colors). 

1607 

1608 Args: 

1609 digits (int): Number of digits, defaults to `2`. 

1610 

1611 Returns: 

1612 str: Returns a str of the colors/coordinates of the current object. 

1613 """ 

1614 dims = list(self._data_.keys()) # Dimensions 

1615 

1616 from .colorlib import hexcols 

1617 import numpy as np 

1618 

1619 # Sorting the dimensions 

1620 from re import match 

1621 if match("^(hex_|alpha){1,2}$", "".join(dims)): dims = ["hex_"] 

1622 elif match("^(R|G|B|alpha){3,4}$", "".join(dims)): dims = ["R", "G", "B"] 

1623 elif match("^(L|A|B|alpha){3,4}$", "".join(dims)): dims = ["L", "A", "B"] 

1624 elif match("^(L|U|V|alpha){3,4}$", "".join(dims)): dims = ["L", "U", "V"] 

1625 elif match("^(H|C|L|alpha){3,4}$", "".join(dims)): dims = ["H", "C", "L"] 

1626 elif match("^(X|Y|Z|alpha){3,4}$", "".join(dims)): dims = ["X", "Y", "Z"] 

1627 elif match("^(H|S|V|alpha){3,4}$", "".join(dims)): dims = ["H", "S", "V"] 

1628 elif match("^(H|L|S|alpha){3,4}$", "".join(dims)): dims = ["H", "L", "S"] 

1629 

1630 # Number of colors 

1631 ncol = max([0 if self._data_[x] is None else len(self._data_[x]) for x in dims]) 

1632 

1633 # Add 'alpha' to object 'dims' if we have defined alpha values 

1634 # for this colorobject. Else alpha will not be printed. 

1635 if "alpha" in list(self._data_.keys()): 

1636 if self._data_["alpha"] is not None: dims += ["alpha"] 

1637 

1638 # Start creating the string: 

1639 res = ["{:s} color object ({:d} colors)".format(self.__class__.__name__, ncol)] 

1640 

1641 # Show header 

1642 fmt = "".join(["{:>", "{:d}".format(digits + 6), "s}"]) 

1643 res.append(" " + "".join([fmt.format(x) for x in dims])) 

1644 

1645 # Show data 

1646 # In case of a hexcols object: string formatting and 

1647 # nan-replacement beforehand. 

1648 if isinstance(self, hexcols): 

1649 data = {} 

1650 fmt = "".join(["{:", "{:d}.{:d}".format(6 + digits, 3), "f}"]) 

1651 data["hex_"] = np.ndarray(ncol, dtype = "|S7") 

1652 for n in range(0, ncol): 

1653 x = self._data_["hex_"][n] 

1654 if x is None: 

1655 data["hex_"][n] = None 

1656 else: 

1657 data["hex_"][n] = fmt.format(x) if isinstance(x, float) else x[0:7] 

1658 data["alpha"] = self.get("alpha") 

1659 fmt = "{:<10s}" 

1660 else: 

1661 fmt = "".join(["{:", "{:d}.{:d}".format(6+digits, digits), "f}"]) 

1662 data = self._data_ 

1663 

1664 # Print object content 

1665 count = 0 

1666 for n in range(0, ncol): 

1667 if (n % 10) == 0: 

1668 tmp = "{:3d}: ".format(n+1) 

1669 else: 

1670 tmp = " " 

1671 for d in dims: 

1672 # Special handling for alpha 

1673 if d == "alpha": 

1674 if data[d][n] is None: 

1675 tmp += " ---" 

1676 elif isinstance(data[d][n], float): 

1677 if np.isnan(data[d][n]): 

1678 tmp += " ---" 

1679 elif isinstance(self, hexcols): 

1680 tmp += " {:02X}".format(int(255. * data[d][n])) 

1681 else: 

1682 tmp += " {:4.2f}".format(data[d][n]) 

1683 else: 

1684 if data[d] is None or data[d][n] is None: 

1685 tmp += " ---" 

1686 elif isinstance(data[d][n], str) or isinstance(data[d][n], np.bytes_): 

1687 tmp += fmt.format(data[d][n]) 

1688 else: 

1689 tmp += fmt.format(float(data[d][n])) 

1690 

1691 count += 1 

1692 res.append(tmp) 

1693 

1694 if count >= 30 and ncol > 40: 

1695 res.append("".join([" ......"]*len(dims))) 

1696 res.append("And {:d} more [truncated]".format(ncol - count)) 

1697 break 

1698 

1699 return "\n".join(res) 

1700 

1701 def __call__(self, fixup = True, rev = False): 

1702 """Magic Method 

1703 

1704 Default call method of all color objects. Always returns 

1705 hex colors, same as the `.colors()` method does. 

1706 

1707 Args: 

1708 fixup (bool): Fix colors outside defined color space, defaults to `True`. 

1709 rev (bool): Revert colors, defaults to `False`. 

1710 

1711 Returns: 

1712 list: Returns a list of hex colors. 

1713 """ 

1714 return self.colors(fixup = fixup, rev = rev) 

1715 

1716 def __iter__(self): 

1717 self.n = -1 

1718 return self 

1719 

1720 def __next__(self): 

1721 if self.n < (self.length() - 1): 

1722 self.n += 1 

1723 res = self[self.n] 

1724 return res 

1725 else: 

1726 raise StopIteration 

1727 

1728 def __getitem__(self, key): 

1729 if not isinstance(key, int): 

1730 raise TypeError("argument `key` must be int (index)") 

1731 

1732 from copy import deepcopy 

1733 from numpy import array, newaxis 

1734 res = deepcopy(self) 

1735 for n in list(res._data_.keys()): 

1736 # If None: keep it as it is, else subset 

1737 if res._data_[n] is None: continue 

1738 res._data_[n] = res._data_[n][newaxis, key] 

1739 

1740 return res 

1741 

1742 

1743 def get_whitepoint(self): 

1744 """Get White Point 

1745 

1746 This method returns the definition of the white point in use. If not 

1747 explicitly set via the :py:method:`set_whitepoint` method, a default white 

1748 point is used. 

1749 

1750 Returns: 

1751 dict: Returns a dict with `X`, `Y`, `Z`, the white point specification 

1752 for the three dimensions. 

1753 

1754 Example: 

1755 

1756 >>> from colorspace import hexcols 

1757 >>> c = hexcols("#ff0000") 

1758 >>> c.get_whitepoint() 

1759 """ 

1760 return {"X": self.WHITEX, "Y": self.WHITEY, "Z": self.WHITEZ} 

1761 

1762 def set_whitepoint(self, **kwargs): 

1763 """Set White Point 

1764 

1765 A white point definition is used to adjust the colors. 

1766 This method allows to set custom values. If not explicitly 

1767 set a default specification is used. The :py:method:`get_whitepoint` 

1768 method can be used to extract the currently used definition. 

1769 

1770 Args: 

1771 **kwargs: Named arguments. Allowed are `X`, `Y`, and `Z`, 

1772 each of which must be float: White specification for 

1773 dimension `X`/`Y`/`Z`. 

1774 

1775 Example: 

1776 

1777 >>> from colorspace import hexcols 

1778 >>> c = hexcols("#ff0000") 

1779 >>> c.set_whitepoint(X = 100., Y = 100., Z = 101.) 

1780 >>> c.get_whitepoint() 

1781 

1782 Raises: 

1783 ValueError: If named argument is not one of `X`, `Y`, `Z`. 

1784 """ 

1785 for key,arg in kwargs.items(): 

1786 if key == "X": self.WHITEX = float(arg) 

1787 elif key == "Y": self.WHITEY = float(arg) 

1788 elif key == "Z": self.WHITEZ = float(arg) 

1789 else: 

1790 raise ValueError(f"error in .set_whitepoint: " + \ 

1791 "argument \"{key}\" not recognized.") 

1792 

1793 

1794 def _check_if_allowed_(self, x): 

1795 """Check for Valid Transformation 

1796 

1797 Helper function checking if the transformation of the current 

1798 object into another color space is allowed or not. 

1799 An exception will be thrown if the transformation is not possible. 

1800 

1801 Args: 

1802 x (str): Name of the target color space. 

1803 

1804 Returns: 

1805 No return, raises an Exception if the transformation is invalid. 

1806 """ 

1807 if not x in self.ALLOWED: 

1808 raise Exception(f"transformation from {self.__class__.__name__}" + \ 

1809 f" to \"{x}\" is unknown (not implemented). " + \ 

1810 f"The following are allowed: {', '.join(self.ALLOWED)}") 

1811 return 

1812 

1813 

1814 def _transform_via_path_(self, via, fixup): 

1815 """Transform Colors along Path 

1816 

1817 Helper function to transform a colorobject into a new color 

1818 space. Calls the :py:func:`to` method one or multiple times along 'a path' 

1819 as specified by `via`. 

1820 

1821 Returns: 

1822 No return, converts the current color space object (see method :py:func:`to`). 

1823 

1824 Args: 

1825 via (list of str): The path via which the current color object 

1826 should be transformed. For example: A :py:class:`hexcols` 

1827 object can be transformed into CIEXYZ by specifying 

1828 `via = ["sRGB", "RGB", "CIEXYZ"]`. 

1829 fixup (bool): Whether or not to correct invalid rgb values outside 

1830 `[0., 1.]` if necessary 

1831 """ 

1832 for v in via: self.to(v, fixup = fixup) 

1833 

1834 def _colorobject_check_input_arrays_(self, **kwargs): 

1835 """Colorobject Check User Input 

1836 

1837 Checks if all inputs in `**kwargs` are of type `numpy.ndarray` OR lists 

1838 (will be converted to `numpy.ndarray`s) and that all are of the same length. 

1839 If not, the script will throw an exception. 

1840 

1841 If `alpha` is given it is handled in a special way. If `alpha = None` 

1842 it will simply be dropped (no alpha channel specified), else it is 

1843 handled like the rest and has to fulfill the requirements all the 

1844 other dimensions have to (length and type). 

1845 

1846 Args: 

1847 **kwargs: Named keywords, objects to be checked. 

1848 

1849 Returns: 

1850 bool: Returns `True` if all checks where fine, throws an exception 

1851 if the inputs do not fulfil the requirements. 

1852 """ 

1853 

1854 from numpy import asarray, float64 

1855 

1856 # Message will be dropped if problems occur 

1857 msg = f"Problem while checking inputs \"{', '.join(kwargs.keys())}\" " + \ 

1858 f"to class \"{self.__class__.__name__}\"." 

1859 

1860 res = {} 

1861 lengths = [] 

1862 keys_to_check = [] 

1863 for key,val in kwargs.items(): 

1864 # No alpha provided, simply proceed 

1865 if key == "alpha" and val is None: continue 

1866 

1867 keys_to_check.append(key) 

1868 

1869 # If is list: convert to ndarray no matter how long the element is 

1870 if isinstance(val, float) or isinstance(val, int): 

1871 val = np.asarray([val]) 

1872 elif isinstance(val,list): 

1873 try: 

1874 val = np.asarray(val) 

1875 except Exception as e: 

1876 raise Exception(e) 

1877 

1878 

1879 # For alpha, R, G, and B: check range 

1880 if isinstance(self, RGB) or isinstance(self, sRGB): 

1881 if np.max(val) > 1. or np.max(val) < 0.: 

1882 raise ValueError("wrong values specified for " + \ 

1883 f"dimension {key} in {self.__class__.__name__}: " + \ 

1884 "values have to lie within [0., 1.]") 

1885 

1886 # Check object type 

1887 from numpy import asarray 

1888 try: 

1889 val = asarray(val) 

1890 except Exception as e: 

1891 raise ValueError(f"input {key} to {self.__class__.__name__}" + \ 

1892 f" could not have been converted to `numpy.ndarray`: {str(e)}") 

1893 

1894 # Else append length and proceed 

1895 lengths.append(len(val)) 

1896 

1897 # Append to result vector 

1898 if isinstance(val, int) or isinstance(val, float): val = [val] 

1899 res[key] = val if key == "hex_" else asarray(val, float64) 

1900 

1901 # Check if all do have the same length 

1902 if not np.all([x == lengths[0] for x in lengths]): 

1903 msg += " Arguments of different lengths: {:s}".format( 

1904 ", ".join(["{:s} = {:d}".format(keys_to_check[i], lengths[i]) \ 

1905 for i in range(0, len(keys_to_check))])) 

1906 raise ValueError(msg) 

1907 

1908 return res 

1909 

1910 

1911 def hasalpha(self): 

1912 """Check for Alpha Channel 

1913 

1914 Helper method to check if the current color object has 

1915 an alpha channel or not. 

1916 

1917 Examples: 

1918 

1919 >>> from colorspace import sRGB 

1920 >>> x1 = sRGB(R = 0.5, G = 0.1, B = 0.3) 

1921 >>> x1 

1922 >>> #: 

1923 >>> x2 = sRGB(R = 0.5, G = 0.1, B = 0.3, alpha = 0.5) 

1924 >>> x2 

1925 >>> #: Checking both color objects for alpha channel 

1926 >>> [x1.hasalpha(), x2.hasalpha()] 

1927 

1928 Returns: 

1929 bool: `True` if alpha values are present, `False` if not. 

1930 """ 

1931 if not "alpha" in self._data_.keys(): 

1932 return False 

1933 elif self._data_["alpha"] is None: 

1934 return False 

1935 else: 

1936 return True 

1937 

1938 

1939 def dropalpha(self): 

1940 """Remove Alpha Channel 

1941 

1942 Remove alpha channel from the color object, if defined 

1943 (see :py:method:`hasalpha`). Works for all `colorobject`s. 

1944 

1945 Examples: 

1946 

1947 >>> from colorspace.colorlib import HCL, sRGB, HSV 

1948 >>> # Example using HCL colors 

1949 >>> cols = HCL([0, 40, 80], 

1950 >>> [30, 60, 80], 

1951 >>> [85, 60, 35], 

1952 >>> alpha = [1.0, 0.5, 0.1]) 

1953 >>> cols # with alpha channel 

1954 >>> #: 

1955 >>> cols.dropalpha() 

1956 >>> cols # alpha channel removed 

1957 >>> 

1958 >>> #: No effect if there is no alpha channel 

1959 >>> cols.dropalpha() 

1960 >>> cols 

1961 >>> 

1962 >>> #: Example using sRGB colors 

1963 >>> cols = sRGB([0.01, 0.89, 0.56], 

1964 >>> [0.25, 0.89, 0.02], 

1965 >>> [0.65, 0.89, 0.23], 

1966 >>> alpha = [1.0, 0.5, 0.1]) 

1967 >>> cols # with alpha channel 

1968 >>> #: 

1969 >>> cols.dropalpha() 

1970 >>> cols # alpha channel removed 

1971 >>> 

1972 >>> #: Example using HSV colors 

1973 >>> cols = HSV([218, 0, 336], 

1974 >>> [1, 0, 1], 

1975 >>> [0.65, 0.89, 0.56], 

1976 >>> alpha = [1.0, 0.5, 0.1]) 

1977 >>> cols # with alpha channel 

1978 >>> #: 

1979 >>> cols.dropalpha() 

1980 >>> cols # alpha channel removed 

1981 

1982 

1983 """ 

1984 if self.hasalpha(): 

1985 del self._data_["alpha"] 

1986 

1987 

1988 def specplot(self, **kwargs): 

1989 """Color Spectrum Plot 

1990 

1991 Visualization of the spectrum of this color object. 

1992 Internally calls :py:func:`specplot <colorspace.specplot.specplot>`, 

1993 additional arguments to this main function can be forwarded via the 

1994 `**kwargs` argument. 

1995 

1996 Args: 

1997 **kwargs: Additional named arguments forwarded to 

1998 :py:func:`specplot <colorspace.specplot.specplot>`. 

1999 

2000 Return: 

2001 Returns what :py:func:`colorspace.specplot.specplot` returns. 

2002 

2003 Example: 

2004 

2005 >>> # Example using HCL colors 

2006 >>> from colorspace import HCL, hexcols 

2007 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2008 >>> C = [ 44, 49, 55, 59, 50], 

2009 >>> L = [ 49, 61, 72, 82, 90]) 

2010 >>> cols.specplot(figsize = (8, 4)); 

2011 >>> 

2012 >>> #: Example using hex colors 

2013 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2014 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2015 >>> cols.specplot(rgb = True, hcl = True, palette = True) 

2016 

2017 """ 

2018 from copy import copy 

2019 cols = copy(self) 

2020 cols.to("hex") 

2021 

2022 from .specplot import specplot 

2023 return specplot(cols.colors(), **kwargs) 

2024 

2025 

2026 def swatchplot(self, **kwargs): 

2027 """Palette Swatch Plot 

2028 

2029 Visualization the color palette of this color object. 

2030 Internally calls :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`, 

2031 additional arguments to this main function can be forwarded via the 

2032 `**kwargs` argument. 

2033 

2034 Args: 

2035 **kwargs: Additional named arguments forwarded to 

2036 :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`. 

2037 

2038 Return: 

2039 Returns what :py:func:`colorspace.swatchplot.swatchplot` returns. 

2040 

2041 Example: 

2042 

2043 >>> # Example using HCL colors 

2044 >>> from colorspace import HCL, hexcols 

2045 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2046 >>> C = [ 44, 49, 55, 59, 50], 

2047 >>> L = [ 49, 61, 72, 82, 90]) 

2048 >>> cols.swatchplot(figsize = (8, 2)) 

2049 >>> 

2050 >>> #: Example using hex colors 

2051 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2052 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2053 >>> cols.swatchplot(figsize = (8, 3.5)); 

2054 """ 

2055 

2056 from .swatchplot import swatchplot 

2057 if "show_names" in kwargs.keys(): 

2058 del kwargs["show_names"] 

2059 return swatchplot(pals = self.colors(), show_names = False, **kwargs) 

2060 

2061 

2062 def hclplot(self, **kwargs): 

2063 """Palette Plot in HCL Space 

2064 

2065 Convenience method for calling :py:func:`hclplot <colorspace.hclplot.hclplot>` 

2066 on the current color object. Additional arguments can be forwarded via `**kwargs` 

2067 (see :py:func:`hclplot <colorspace.hclplot.hclplot>` for details). 

2068 

2069 Args: 

2070 **kwargs: Additional named arguments forwarded to 

2071 :py:func:`hclplot <colorspace.hclplot.hclplot>`. 

2072 

2073 Return: 

2074 Returns what :py:func:`colorspace.hclplot.hclplot` returns. 

2075 

2076 Example: 

2077 

2078 >>> # Example using HCL colors 

2079 >>> from colorspace import HCL, hexcols 

2080 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2081 >>> C = [ 44, 49, 55, 59, 50], 

2082 >>> L = [ 49, 61, 72, 82, 90]) 

2083 >>> cols.hclplot(); 

2084 >>> 

2085 >>> #: Example using hex colors 

2086 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2087 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2088 >>> cols.hclplot(figsize = (8, 3.5)); 

2089 """ 

2090 

2091 from .hclplot import hclplot 

2092 return hclplot(x = self.colors(), **kwargs) 

2093 

2094 def colors(self, fixup = True, rev = False): 

2095 """Extract Hex Colors 

2096 

2097 Convers the current object into an object of class :py:class:`hexcols` 

2098 and extracts the hex colors as list of str. 

2099 

2100 If the object contains alpha values, the alpha level is added to the 

2101 hex string if and only if alpha is not equal to `1.0`. 

2102 

2103 Args: 

2104 fixup (bool): Whether or not to correct rgb values outside the 

2105 defined range of `[0., 1.]`, defaults to `True`. 

2106 rev (bool): Should the color palette be reversed? Defaults to `False`. 

2107 

2108 Returns: 

2109 list: Returns a list of hex color strings. 

2110 

2111 Example: 

2112 

2113 >>> from colorspace import HCL, sRGB, HSV 

2114 >>> # Example using HCL colors 

2115 >>> cols = HCL([0, 40, 80], 

2116 >>> [30, 60, 80], 

2117 >>> [85, 60, 35]) 

2118 >>> cols.colors() 

2119 >>> 

2120 >>> #: Example using sRGB colors 

2121 >>> cols = sRGB([0.01, 0.89, 0.56], 

2122 >>> [0.25, 0.89, 0.02], 

2123 >>> [0.65, 0.89, 0.23]) 

2124 >>> cols.colors() 

2125 >>> 

2126 >>> #: Example using HSV colors 

2127 >>> cols = HSV([218, 0, 336], 

2128 >>> [1, 0, 1], 

2129 >>> [0.65, 0.89, 0.56]) 

2130 >>> cols.colors() 

2131 

2132 """ 

2133 

2134 from copy import copy 

2135 from numpy import ndarray, round 

2136 

2137 x = copy(self) 

2138 x.to("hex", fixup = fixup) 

2139 if x.hasalpha(): 

2140 res = x.get("hex_").tolist() 

2141 # Appending alpha if alpha < 1.0 

2142 for i in range(0, len(res)): 

2143 if self._data_["alpha"][i] < 1.0: 

2144 tmp = int(round(self._data_["alpha"][i] * 255. + 0.0001)) 

2145 res[i] += f"{tmp:02X}" 

2146 # Return hex with alpha 

2147 colors = res 

2148 else: 

2149 colors = x.get("hex_") 

2150 

2151 if rev: 

2152 from numpy import flip 

2153 colors = flip(colors) 

2154 

2155 return colors.tolist() if isinstance(colors, ndarray) else colors 

2156 

2157 

2158 def get(self, dimname = None): 

2159 """Extracting Color Coordinates 

2160 

2161 Allows to extract the current values of one or multiple dimensions 

2162 for all colors of this color object. The names of the coordinates varies 

2163 between different color spaces. 

2164 

2165 Args: 

2166 dimname (None, str): If `None` (default) values of all coordinates 

2167 of the current color object are returned. A specific coordinate 

2168 can be specified if needed. 

2169 

2170 Returns: 

2171 Returns a `numpy.ndarray` if coordinates of one specific dimension are 

2172 requested, else a `dict` of arrays. 

2173 

2174 Example: 

2175 

2176 >>> from colorspace import HCL, sRGB, hexcols 

2177 >>> # Example using HCL color object with alpha channel 

2178 >>> cols = HCL([260, 80, 30], [80, 0, 80], [30, 90, 30], [1, 0.6, 0.2]) 

2179 >>> cols.get("H") # Specific dimension 

2180 >>> #: 

2181 >>> cols.get("alpha") # Alpha (if existing) 

2182 >>> #: 

2183 >>> cols.get() # All dimensions 

2184 >>> 

2185 >>> #: Convert colors to sRGB 

2186 >>> cols.to("sRGB") 

2187 >>> cols.get("R") # Specific dimension 

2188 >>> #: 

2189 >>> cols.get() # All dimensions 

2190 >>> 

2191 >>> #: Convert to hexcols 

2192 >>> cols.to("hex") 

2193 >>> cols.get("hex_") 

2194 

2195 Raises: 

2196 TypeError: If argument `dimname` is neither None or str. 

2197 ValueError: If the dimension specified on `dimnames` does not exist. 

2198 """ 

2199 

2200 # Return all coordinates 

2201 from copy import copy 

2202 if dimname is None: 

2203 return copy(self._data_) 

2204 # No string? 

2205 elif not isinstance(dimname, str): 

2206 raise TypeError("argument `dimname` must be None or str") 

2207 # Else only the requested dimension 

2208 elif not dimname in self._data_.keys(): 

2209 # Alpha channel never defined, return None (which 

2210 # is a valid value for "no alpha") 

2211 if dimname == "alpha": 

2212 return None 

2213 else: 

2214 raise ValueError(f"{self.__class__.__name__} has no dimension {dimname}") 

2215 

2216 return copy(self._data_[dimname]) 

2217 

2218 

2219 def set(self, **kwargs): 

2220 """Set Coordinates/Manipulate Colors 

2221 

2222 Allows to manipulate current colors. The named input arguments 

2223 have to fulfil a specific set or requirements. If not, the function 

2224 raises exceptions. The requirements: 

2225 

2226 * Dimension has to exist 

2227 * New data/values must be of same length and type as the existing ones 

2228 

2229 Args: 

2230 **kwargs: Named arguments. The key is the name of the dimension to 

2231 be changed, the value an object which fulfills the requirements 

2232 (see description of this method) 

2233 

2234 Raises: 

2235 ValueError: If the dimension does not exist. 

2236 ValueError: If the new data can't be converted into 

2237 `numpy.array` (is done automatically if needed). 

2238 ValueError: If new data has wrong length (does not match the 

2239 number of colors/length of current values). 

2240 

2241 Example: 

2242 

2243 >>> # Example shown for HCL colors, works the same 

2244 >>> # for all other color objects (sRGB, hexcols, ...) 

2245 >>> from colorspace import HCL 

2246 >>> cols = HCL([260, 80, 30], [80, 0, 80], [30, 90, 30]) 

2247 >>> cols 

2248 >>> #: 

2249 >>> cols.set(H = [150, 150, 30]) 

2250 >>> cols 

2251 """ 

2252 # Looping over inputs 

2253 from numpy import asarray, ndarray 

2254 for key,vals in kwargs.items(): 

2255 key.upper() 

2256 

2257 # Check if the key provided by the user is a valid dimension 

2258 # of the current object. 

2259 if not key in self._data_.keys(): 

2260 raise ValueError(f"{self.__class__.__name__} has no dimension {key}") 

2261 

2262 # In case the input is a single int/float or a list; try 

2263 # to convert the input into a numpy.array using the same 

2264 # dtype as the existing dimension (loaded via self.get(key)). 

2265 if isinstance(vals, (list, int, float)): 

2266 if isinstance(vals, (int, float)): vals = [vals] 

2267 t = type(self.get(key)[0]) # Current type (get current dimension) 

2268 try: 

2269 vals = np.asarray(vals, dtype = t) 

2270 except Exception as e: 

2271 raise ValueError(f"problems converting new data to {t} " + \ 

2272 f" in {self.__class__.__name__}: {str(e)}") 

2273 

2274 # New values do have to have the same length as the old ones, 

2275 n = len(self.get(key)) 

2276 t = type(self.get(key)[0]) 

2277 try: 

2278 vals = np.asarray(vals, dtype = t) 

2279 except Exception as e: 

2280 raise ValueError(f"problems converting new data to {t} " + \ 

2281 f" in {self.__class__.__name__}: {str(e)}") 

2282 if not vals.size == n: 

2283 raise ValueError("number of values to be stored on the object " + \ 

2284 f"{self.__class__.__name__} have to match the current dimension") 

2285 

2286 self._data_[key] = vals 

2287 

2288 def length(self): 

2289 """Get Number of Colors 

2290 

2291 Returns the number of colors defined in this color object. 

2292 Note that `len(<object>)` works as well. 

2293 

2294 Returns: 

2295 int: Number of colors. 

2296 

2297 Examples: 

2298 

2299 >>> from colorspace import sRGB, hexcols, HCL 

2300 >>> # Examples for three different color objects 

2301 >>> x1 = sRGB([1, 0], [1, 1], [0, 0]) 

2302 >>> [x1.length(), len(x1)] 

2303 >>> #: 

2304 >>> x2 = hexcols(["#ff0000", "#00ff00", "#0000ff"]) 

2305 >>> [x2.length(), len(x2)] 

2306 >>> #: 

2307 >>> x3 = HCL([275, 314, 353, 31, 70], 

2308 >>> [70, 85, 102, 86, 45], 

2309 >>> [25, 40, 55, 70, 85]) 

2310 >>> [x3.length(), len(x3)] 

2311 

2312 """ 

2313 return max([0 if self._data_[x] is None else len(self._data_[x]) for x in self._data_.keys()]) 

2314 

2315 def __len__(self): 

2316 return self.length() 

2317 

2318 

2319 # Currently not used but implemented as fallback for the future 

2320 def _cannot(self, from_, to): 

2321 """Error: Conversion not Possible 

2322 

2323 Helper function used to raise an exception as a specific 

2324 transformation is not possible by definition. 

2325 

2326 Args: 

2327 from_ (str): Name of the current color space. 

2328 to (str): Name of the target color space. 

2329 

2330 Raises: 

2331 Exception: Always, that is the intent of this method. 

2332 """ 

2333 raise Exception(f"cannot convert class \"{from_}\" to \"{to}\"") 

2334 

2335 def _ambiguous(self, from_, to): 

2336 """Error: Conversion Ambiguous 

2337 

2338 Helper function used to raise an exception as a specific 

2339 transformation is ambiguous and therefore not possible by definition. 

2340 

2341 Args: 

2342 from_ (str): Name of the current color space. 

2343 to (str): Name of the target color space. 

2344 

2345 Raises: 

2346 Exception: Always, that is the intent of this method. 

2347 """ 

2348 raise Exception(f"conversion not possible, ambiguous conversion from \"{from_}\" to \"{to}\"") 

2349 

2350 

2351# ------------------------------------------------------------------- 

2352# PolarLUV or HCL object 

2353# ------------------------------------------------------------------- 

2354class polarLUV(colorobject): 

2355 """Create polarLUV (HCL) Color Object 

2356 

2357 Creates a color object in the polar representation of the :py:class:`CIELUV` 

2358 color space, also known as the Hue-Chroma-Luminance (HCL) color space. 

2359 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2360 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2361 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2362 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2363 

2364 Args: 

2365 H (int, float, list, numpy.array): 

2366 Numeric value(s) for hue dimension (`[-360., 360.]`). 

2367 C (int, float, list, numpy.array): 

2368 Numeric value(s) for chroma dimension (`[0., 100.+]`). 

2369 L (int, float, list, numpy.array): 

2370 Numeric value(s) for luminance dimension (`[0., 100.]`). 

2371 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2372 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2373 opacity. If `None` (default) no transparency is added. 

2374 

2375 Example: 

2376 

2377 >>> from colorspace import polarLUV, HCL 

2378 >>> # Constructing color object with one single color via float 

2379 >>> polarLUV(100., 30, 50.) 

2380 >>> #: polarLUV is the HCL color space, this 

2381 >>> # is equivalent to the command above. 

2382 >>> HCL(100., 30, 50.) 

2383 >>> #: Constructing object via lists 

2384 >>> HCL([100, 80], [30, 50], [30, 80]) 

2385 >>> #: Constructing object via numpy arrays 

2386 >>> from numpy import asarray 

2387 >>> HCL(asarray([100, 80]), asarray([30, 50]), asarray([30, 80])) 

2388 """ 

2389 

2390 def __init__(self, H, C, L, alpha = None): 

2391 

2392 # Checking inputs, save inputs on object 

2393 self._data_ = {} # Dict to store the colors/color dimensions 

2394 tmp = self._colorobject_check_input_arrays_(H = H, C = C, L = L, alpha = alpha) 

2395 for key,val in tmp.items(): self._data_[key] = val 

2396 # White spot definition (the default) 

2397 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2398 

2399 def to(self, to, fixup = True): 

2400 """Transform Color Space 

2401 

2402 Allows to transform the current object into a different color space, 

2403 if possible. Converting the colors of the current object into 

2404 another color space. After calling this method, the object 

2405 will be of a different class. 

2406 

2407 Args: 

2408 to (str): Name of the color space into which the colors should be 

2409 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2410 fixup (bool): Whether or not colors outside the defined rgb color space 

2411 should be corrected if necessary, defaults to `True`. 

2412 

2413 Examples: 

2414 

2415 >>> # HCL() identical to polarLUV() 

2416 >>> from colorspace import HCL 

2417 >>> x = HCL([275, 314, 353, 31, 70], 

2418 >>> [ 70, 85, 102, 86, 45], 

2419 >>> [ 25, 40, 55, 70, 85]) 

2420 >>> x 

2421 >>> #: 

2422 >>> type(x) 

2423 >>> #: Convert colors to sRGB 

2424 >>> x.to("sRGB") 

2425 >>> x 

2426 >>> #: 

2427 >>> type(x) 

2428 >>> #: Convert from sRGB to hex 

2429 >>> x.to("hex") 

2430 >>> x 

2431 >>> #: Convert back to HCL colors. 

2432 >>> # Round-off errors due to conversion to 'hex'. 

2433 >>> x.to("HCL") 

2434 >>> x 

2435 >>> #: Extracting hex colors (returns list of str) 

2436 >>> x.colors() 

2437 

2438 """ 

2439 self._check_if_allowed_(to) 

2440 from . import colorlib 

2441 clib = colorlib() 

2442 

2443 # Nothing to do (converted to itself) 

2444 if to in ["HCL", self.__class__.__name__]: 

2445 return 

2446 

2447 # This is the only transformation from polarLUV -> LUV 

2448 elif to == "CIELUV": 

2449 [L, U, V] = clib.polarLUV_to_LUV(self.get("L"), self.get("C"), self.get("H")) 

2450 self._data_ = {"L" : L, "U" : U, "V" : V, "alpha" : self.get("alpha")} 

2451 self.__class__ = CIELUV 

2452 

2453 # The rest are transformations along a path 

2454 elif to == "CIEXYZ": 

2455 via = ["CIELUV", to] 

2456 self._transform_via_path_(via, fixup = fixup) 

2457 

2458 elif to == "CIELAB": 

2459 via = ["CIELUV", "CIEXYZ", to] 

2460 self._transform_via_path_(via, fixup = fixup) 

2461 

2462 elif to == "RGB": 

2463 via = ["CIELUV", "CIEXYZ", to] 

2464 self._transform_via_path_(via, fixup = fixup) 

2465 

2466 elif to == "sRGB": 

2467 via = ["CIELUV", "CIEXYZ", to] 

2468 self._transform_via_path_(via, fixup = fixup) 

2469 

2470 elif to == "polarLAB": 

2471 via = ["CIELUV", "CIEXYZ", "CIELAB", to] 

2472 self._transform_via_path_(via, fixup = fixup) 

2473 

2474 elif to == "hex": 

2475 via = ["CIELUV", "CIEXYZ", "sRGB", to] 

2476 self._transform_via_path_(via, fixup = fixup) 

2477 

2478 elif to in ["HLS", "HSV"]: 

2479 self._ambiguous(self.__class__.__name__, to) 

2480 

2481 # Currently not used but implemented as fallback for the future 

2482 else: self._cannot(self.__class__.__name__, to) 

2483 

2484# polarLUV is HCL, make copy 

2485HCL = polarLUV 

2486 

2487 

2488# ------------------------------------------------------------------- 

2489# CIELUV color object 

2490# ------------------------------------------------------------------- 

2491class CIELUV(colorobject): 

2492 """Create CIELUV Color Object 

2493 

2494 Creates a color object in the CIELUV color space. 

2495 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2496 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2497 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2498 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2499 

2500 Args: 

2501 L (int, float, list, numpy.array): 

2502 Numeric value(s) for L dimension. 

2503 U (int, float, list, numpy.array): 

2504 Numeric value(s) for U dimension. 

2505 V (int, float, list, numpy.array): 

2506 Numeric value(s) for L dimension. 

2507 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2508 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2509 opacity. If `None` (default) no transparency is added. 

2510 

2511 Example: 

2512 

2513 >>> from colorspace import CIELUV 

2514 >>> # Constructing color object with one single color via float 

2515 >>> CIELUV(0, 10, 10) 

2516 >>> #: Constructing object via lists 

2517 >>> CIELUV([10, 30], [20, 80], [100, 40]) 

2518 >>> #: Constructing object via numpy arrays 

2519 >>> from numpy import asarray 

2520 >>> CIELUV(asarray([10, 30]), asarray([20, 80]), asarray([100, 40])) 

2521 

2522 """ 

2523 def __init__(self, L, U, V, alpha = None): 

2524 

2525 # checking inputs, save inputs on object 

2526 self._data_ = {} # Dict to store the colors/color dimensions 

2527 tmp = self._colorobject_check_input_arrays_(L = L, U = U, V = V, alpha = alpha) 

2528 for key,val in tmp.items(): self._data_[key] = val 

2529 # White spot definition (the default) 

2530 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2531 

2532 

2533 def to(self, to, fixup = True): 

2534 """Transform Color Space 

2535 

2536 Allows to transform the current object into a different color space, 

2537 if possible. Converting the colors of the current object into 

2538 another color space. After calling this method, the object 

2539 will be of a different class. 

2540 

2541 Args: 

2542 to (str): Name of the color space into which the colors should be 

2543 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2544 fixup (bool): Whether or not colors outside the defined rgb color space 

2545 should be corrected if necessary, defaults to `True`. 

2546 

2547 Examples: 

2548 

2549 >>> from colorspace import CIELUV 

2550 >>> x = CIELUV([ 25, 45, 65, 85], 

2551 >>> [ 6, 75, 90, 16], 

2552 >>> [-70, -50, 30, 42]) 

2553 >>> x 

2554 >>> #: 

2555 >>> type(x) 

2556 >>> #: Convert colors to sRGB 

2557 >>> x.to("sRGB") 

2558 >>> x 

2559 >>> #: 

2560 >>> type(x) 

2561 >>> #: Convert from sRGB to hex 

2562 >>> x.to("hex") 

2563 >>> x 

2564 >>> #: Convert back to CIELUV colors. 

2565 >>> # Round-off errors due to conversion to 'hex'. 

2566 >>> x.to("CIELUV") 

2567 >>> x 

2568 >>> #: Extracting hex colors (returns list of str) 

2569 >>> x.colors() 

2570 

2571 """ 

2572 self._check_if_allowed_(to) 

2573 from . import colorlib 

2574 clib = colorlib() 

2575 

2576 # Nothing to do (converted to itself) 

2577 if to == self.__class__.__name__: 

2578 return 

2579 # Transformation from CIELUV -> CIEXYZ 

2580 elif to == "CIEXYZ": 

2581 [X, Y, Z] = clib.LUV_to_XYZ(self.get("L"), self.get("U"), self.get("V"), 

2582 self.WHITEX, self.WHITEY, self.WHITEZ) 

2583 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

2584 self.__class__ = CIEXYZ 

2585 

2586 # Transformation from CIELUV -> polarLUV (HCL) 

2587 elif to in ["HCL","polarLUV"]: 

2588 [L, C, H] = clib.LUV_to_polarLUV(self.get("L"), self.get("U"), self.get("V")) 

2589 self._data_ = {"L" : L, "C" : C, "H" : H, "alpha" : self.get("alpha")} 

2590 self.__class__ = polarLUV 

2591 

2592 # The rest are transformations along a path 

2593 elif to == "CIELAB": 

2594 via = ["CIEXYZ", to] 

2595 self._transform_via_path_(via, fixup = fixup) 

2596 

2597 elif to == "RGB": 

2598 via = ["CIEXYZ", to] 

2599 self._transform_via_path_(via, fixup = fixup) 

2600 

2601 elif to == "sRGB": 

2602 via = ["CIEXYZ", "RGB", to] 

2603 self._transform_via_path_(via, fixup = fixup) 

2604 

2605 elif to == "polarLAB": 

2606 via = ["CIEXYZ", "CIELAB", to] 

2607 self._transform_via_path_(via, fixup = fixup) 

2608 

2609 elif to == "hex": 

2610 via = ["CIEXYZ", "RGB", "sRGB", to] 

2611 self._transform_via_path_(via, fixup = fixup) 

2612 

2613 elif to in ["HLS", "HSV"]: 

2614 self._ambiguous(self.__class__.__name__, to) 

2615 

2616 else: self._cannot(self.__class__.__name__, to) 

2617 

2618# ------------------------------------------------------------------- 

2619# CIEXYZ color object 

2620# ------------------------------------------------------------------- 

2621class CIEXYZ(colorobject): 

2622 """Create CIEXYZ Color Object 

2623 

2624 Creates a color object in the CIEXYZ color space. 

2625 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2626 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2627 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2628 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2629 

2630 Args: 

2631 X (int, float, list, numpy.array): 

2632 Numeric value(s) for X dimension. 

2633 Y (int, float, list, numpy.array): 

2634 Numeric value(s) for Y dimension. 

2635 Z (int, float, list, numpy.array): 

2636 Numeric value(s) for Z dimension. 

2637 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2638 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2639 opacity. If `None` (default) no transparency is added. 

2640 

2641 Example: 

2642 

2643 >>> from colorspace import CIEXYZ 

2644 >>> # Constructing color object with one single color via float 

2645 >>> CIEXYZ(80, 30, 10) 

2646 >>> #: Constructing object via lists 

2647 >>> CIEXYZ([10, 0], [20, 80], [40, 40]) 

2648 >>> #: Constructing object via numpy arrays 

2649 >>> from numpy import asarray 

2650 >>> CIEXYZ(asarray([10, 0]), asarray([20, 80]), asarray([40, 40])) 

2651 

2652 """ 

2653 def __init__(self, X, Y, Z, alpha = None): 

2654 

2655 # checking inputs, save inputs on object 

2656 self._data_ = {} # Dict to store the colors/color dimensions 

2657 tmp = self._colorobject_check_input_arrays_(X = X, Y = Y, Z = Z, alpha = alpha) 

2658 for key,val in tmp.items(): self._data_[key] = val 

2659 # White spot definition (the default) 

2660 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2661 

2662 

2663 def to(self, to, fixup = True): 

2664 """Transform Color Space 

2665 

2666 Allows to transform the current object into a different color space, 

2667 if possible. Converting the colors of the current object into 

2668 another color space. After calling this method, the object 

2669 will be of a different class. 

2670 

2671 Args: 

2672 to (str): Name of the color space into which the colors should be 

2673 converted (e.g., `"CIELUV"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2674 fixup (bool): Whether or not colors outside the defined rgb color space 

2675 should be corrected if necessary, defaults to `True`. 

2676 

2677 Examples: 

2678 

2679 >>> from colorspace import CIEXYZ 

2680 >>> x = CIEXYZ([ 8.5, 27.8, 46.2, 62.1], 

2681 >>> [ 4.4, 14.5, 34.1, 65.9], 

2682 >>> [27.2, 31.9, 17.2, 40.0]) 

2683 >>> x 

2684 >>> #: 

2685 >>> type(x) 

2686 >>> #: Convert colors to sRGB 

2687 >>> x.to("sRGB") 

2688 >>> x 

2689 >>> #: 

2690 >>> type(x) 

2691 >>> #: Convert from sRGB to hex 

2692 >>> x.to("hex") 

2693 >>> x 

2694 >>> #: Convert back to CIEXYZ colors. 

2695 >>> # Round-off errors due to conversion to 'hex'. 

2696 >>> x.to("CIEXYZ") 

2697 >>> x 

2698 >>> #: Extracting hex colors (returns list of str) 

2699 >>> x.colors() 

2700 

2701 """ 

2702 self._check_if_allowed_(to) 

2703 from . import colorlib 

2704 clib = colorlib() 

2705 

2706 # Nothing to do (converted to itself) 

2707 if to == self.__class__.__name__: 

2708 return 

2709 

2710 # Transformation from CIEXYZ -> CIELUV 

2711 elif to == "CIELUV": 

2712 [L, U, V] = clib.XYZ_to_LUV(self.get("X"), self.get("Y"), self.get("Z"), 

2713 self.WHITEX, self.WHITEY, self.WHITEZ) 

2714 self._data_ = {"L" : L, "U" : U, "V" : V, "alpha" : self.get("alpha")} 

2715 self.__class__ = CIELUV 

2716 

2717 # Transformation from CIEXYZ -> CIELAB 

2718 elif to == "CIELAB": 

2719 [L, A, B] = clib.XYZ_to_LAB(self.get("X"), self.get("Y"), self.get("Z"), 

2720 self.WHITEX, self.WHITEY, self.WHITEZ) 

2721 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

2722 self.__class__ = CIELAB 

2723 

2724 # Transformation from CIEXYZ -> RGB 

2725 elif to == "RGB": 

2726 [R, G, B] = clib.XYZ_to_RGB(self.get("X"), self.get("Y"), self.get("Z"), 

2727 self.WHITEX, self.WHITEY, self.WHITEZ) 

2728 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2729 self.__class__ = RGB 

2730 

2731 # The rest are transformations along a path 

2732 elif to == "polarLAB": 

2733 via = ["CIELAB", to] 

2734 self._transform_via_path_(via, fixup = fixup) 

2735 

2736 elif to in ["HCL", "polarLUV"]: 

2737 via = ["CIELUV", to] 

2738 self._transform_via_path_(via, fixup = fixup) 

2739 

2740 elif to == "sRGB": 

2741 via = ["RGB", to] 

2742 self._transform_via_path_(via, fixup = fixup) 

2743 

2744 elif to == "hex": 

2745 via = ["RGB", "sRGB", to] 

2746 self._transform_via_path_(via, fixup = fixup) 

2747 

2748 elif to in ["HLS", "HSV"]: 

2749 self._ambiguous(self.__class__.__name__, to) 

2750 

2751 else: self._cannot(self.__class__.__name__, to) 

2752 

2753 

2754class RGB(colorobject): 

2755 """Create RGB Color Object 

2756 

2757 Allows conversions to: :py:class:`CIELAB`, :py:class:`CIELUV`, 

2758 :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, :py:class:`hexcols`. 

2759 :py:class:`polarLAB`, :py:class:`polarLUV` and :py:class:`sRGB`. 

2760 

2761 Args: 

2762 R (int, float, list, numpy.array): 

2763 Numeric value(s) for red intensity (`[0., 1.]`). 

2764 G (int, float, list, numpy.array): 

2765 Numeric value(s) for green intensity (`[0., 1.]`). 

2766 B (int, float, list, numpy.array): 

2767 Numeric value(s) for blue intensity (`[0., 1.]`). 

2768 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2769 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2770 opacity. If `None` (default) no transparency is added. 

2771 

2772 Example: 

2773 

2774 >>> from colorspace import RGB 

2775 >>> # Constructing color object with one single color via float 

2776 >>> RGB(1., 0.3, 0.5) 

2777 >>> #: Constructing object via lists 

2778 >>> RGB([1., 0.8], [0.5, 0.5], [0.0, 0.2]) 

2779 >>> #: Constructing object via numpy arrays 

2780 >>> from numpy import asarray 

2781 >>> RGB(asarray([1., 0.8]), asarray([0.5, 0.5]), asarray([0.0, 0.2])) 

2782 

2783 """ 

2784 

2785 def __init__(self, R, G, B, alpha = None): 

2786 

2787 # checking inputs, save inputs on object 

2788 self._data_ = {} # Dict to store the colors/color dimensions 

2789 

2790 tmp = self._colorobject_check_input_arrays_(R = R, G = G, B = B, alpha = alpha) 

2791 for key,val in tmp.items(): self._data_[key] = val 

2792 # White spot definition (the default) 

2793 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2794 

2795 

2796 def to(self, to, fixup = True): 

2797 """Transform Color Space 

2798 

2799 Allows to transform the current object into a different color space, 

2800 if possible. Converting the colors of the current object into 

2801 another color space. After calling this method, the object 

2802 will be of a different class. 

2803 

2804 Args: 

2805 to (str): Name of the color space into which the colors should be 

2806 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2807 fixup (bool): Whether or not colors outside the defined rgb color space 

2808 should be corrected if necessary, defaults to `True`. 

2809 

2810 Examples: 

2811 

2812 >>> from colorspace import RGB 

2813 >>> x = RGB([0.070, 0.520, 0.887, 0.799], 

2814 >>> [0.012, 0.015, 0.198, 0.651], 

2815 >>> [0.283, 0.323, 0.138, 0.323]) 

2816 >>> x 

2817 >>> #: 

2818 >>> type(x) 

2819 >>> #: Convert colors to CIEXYZ 

2820 >>> x.to("CIELUV") 

2821 >>> x 

2822 >>> #: 

2823 >>> type(x) 

2824 >>> #: Convert from CIELUV to HCL 

2825 >>> x.to("HCL") 

2826 >>> x 

2827 >>> # Convert back to RGB 

2828 >>> x.to("RGB") 

2829 >>> x 

2830 >>> #: Extracting hex colors (returns list of str) 

2831 >>> x.colors() 

2832 

2833 """ 

2834 self._check_if_allowed_(to) 

2835 from . import colorlib 

2836 clib = colorlib() 

2837 

2838 # Nothing to do (converted to itself) 

2839 if to == self.__class__.__name__: 

2840 return 

2841 

2842 # Transform from RGB -> sRGB 

2843 elif to == "sRGB": 

2844 [R, G, B] = clib.RGB_to_sRGB(self.get("R"), self.get("G"), self.get("B"), 

2845 self.GAMMA) 

2846 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2847 self.__class__ = sRGB 

2848 

2849 # Transform from RGB -> CIEXYZ 

2850 elif to == "CIEXYZ": 

2851 [X, Y, Z] = clib.RGB_to_XYZ(self.get("R"), self.get("G"), self.get("B"), 

2852 self.WHITEX, self.WHITEY, self.WHITEZ) 

2853 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

2854 self.__class__ = CIEXYZ 

2855 

2856 # From RGB to HLS: take direct path (not via sRGB) 

2857 elif to in ["HLS"]: 

2858 [H, L, S] = clib.RGB_to_HLS(self.get("R"), self.get("G"), self.get("B")) 

2859 self._data_ = {"H" : H, "L" : L, "S" : S, "alpha" : self.get("alpha")} 

2860 self.__class__ = HLS 

2861 

2862 # From RGB to HSV: take direct path (not via sRGB) 

2863 elif to in ["HSV"]: 

2864 [H, S, V] = clib.RGB_to_HSV(self.get("R"), self.get("G"), self.get("B")) 

2865 self._data_ = {"H" : H, "S" : S, "V" : V, "alpha" : self.get("alpha")} 

2866 self.__class__ = HSV 

2867 

2868 # The rest are transformations along a path 

2869 elif to in ["hex"]: 

2870 via = ["sRGB", to] 

2871 self._transform_via_path_(via, fixup = fixup) 

2872 

2873 elif to in ["CIELUV", "CIELAB"]: 

2874 via = ["CIEXYZ", to] 

2875 self._transform_via_path_(via, fixup = fixup) 

2876 

2877 elif to in ["HCL","polarLUV"]: 

2878 via = ["CIEXYZ", "CIELUV", to] 

2879 self._transform_via_path_(via, fixup = fixup) 

2880 

2881 elif to == "polarLAB": 

2882 via = ["CIEXYZ", "CIELAB", to] 

2883 self._transform_via_path_(via, fixup = fixup) 

2884 

2885 else: self._cannot(self.__class__.__name__, to) 

2886 

2887 

2888class sRGB(colorobject): 

2889 """Create Standard RGB (sRGB) Color Object 

2890 

2891 Allows conversions to: :py:class:`CIELAB`, :py:class:`CIELUV`, 

2892 :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, :py:class:`RGB`, 

2893 :py:class:`hexcols`. :py:class:`polarLAB` and :py:class:`polarLUV`. 

2894 

2895 Args: 

2896 R (int, float, list, numpy.array): 

2897 Numeric value(s) for red intensity (`[0., 1.]`). 

2898 G (int, float, list, numpy.array): 

2899 Numeric value(s) for green intensity (`[0., 1.]`). 

2900 B (int, float, list, numpy.array): 

2901 Numeric value(s) for blue intensity (`[0., 1.]`). 

2902 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2903 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2904 opacity. If `None` (default) no transparency is added. 

2905 gamma (None, float): If `None` (default) the default gamma value is used. 

2906 Can be specified to overwrite the default. 

2907 

2908 Example: 

2909 

2910 >>> from colorspace import sRGB 

2911 >>> # Constructing color object with one single color via float 

2912 >>> sRGB(1., 0.3, 0.5) 

2913 >>> #: Constructing object via lists 

2914 >>> sRGB([1., 0.8], [0.5, 0.5], [0.0, 0.2]) 

2915 >>> #: Constructing object via numpy arrays 

2916 >>> from numpy import asarray 

2917 >>> sRGB(asarray([1., 0.8]), asarray([0.5, 0.5]), asarray([0.0, 0.2])) 

2918 

2919 """ 

2920 

2921 def __init__(self, R, G, B, alpha = None, gamma = None): 

2922 

2923 # checking inputs, save inputs on object 

2924 self._data_ = {} # Dict to store the colors/color dimensions 

2925 tmp = self._colorobject_check_input_arrays_(R = R, G = G, B = B, alpha = alpha) 

2926 for key,val in tmp.items(): self._data_[key] = val 

2927 

2928 # White spot definition (the default) 

2929 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2930 

2931 if isinstance(gamma, float): self.GAMMA = gamma 

2932 

2933 

2934 def to(self, to, fixup = True): 

2935 """Transform Color Space 

2936 

2937 Allows to transform the current object into a different color space, 

2938 if possible. Converting the colors of the current object into 

2939 another color space. After calling this method, the object 

2940 will be of a different class. 

2941 

2942 Args: 

2943 to (str): Name of the color space into which the colors should be 

2944 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2945 fixup (bool): Whether or not colors outside the defined rgb color space 

2946 should be corrected if necessary, defaults to `True`. 

2947 

2948 Examples: 

2949 

2950 >>> from colorspace import sRGB 

2951 >>> x = sRGB([0.294, 0.749, 0.949, 0.905], 

2952 >>> [0.113, 0.129, 0.482, 0.827], 

2953 >>> [0.568, 0.603, 0.407, 0.603]) 

2954 >>> x 

2955 >>> #: 

2956 >>> type(x) 

2957 >>> #: Convert colors to CIEXYZ 

2958 >>> x.to("CIELUV") 

2959 >>> x 

2960 >>> #: 

2961 >>> type(x) 

2962 >>> #: Convert from CIELUV to HCL 

2963 >>> x.to("HCL") 

2964 >>> x 

2965 >>> #: Convert back to Standard RGB colors. 

2966 >>> x.to("sRGB") 

2967 >>> x 

2968 >>> #: Extracting hex colors (returns list of str) 

2969 >>> x.colors() 

2970 

2971 """ 

2972 self._check_if_allowed_(to) 

2973 from . import colorlib 

2974 clib = colorlib() 

2975 

2976 # Nothing to do (converted to itself) 

2977 if to == self.__class__.__name__: 

2978 return 

2979 

2980 # Transformation sRGB -> RGB 

2981 elif to == "RGB": 

2982 [R, G, B] = clib.sRGB_to_RGB(self.get("R"), self.get("G"), self.get("B"), 

2983 gamma = self.GAMMA) 

2984 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2985 self.__class__ = RGB 

2986 

2987 # Transformation sRGB -> hex 

2988 elif to == "hex": 

2989 hex_ = clib.sRGB_to_hex(self.get("R"), self.get("G"), self.get("B"), fixup) 

2990 self._data_ = {"hex_" : hex_, "alpha" : self.get("alpha")} 

2991 self.__class__ = hexcols 

2992 

2993 # Transform from RGB -> HLS 

2994 elif to == "HLS": 

2995 [H, L, S] = clib.sRGB_to_HLS(self.get("R"), self.get("G"), self.get("B")) 

2996 self._data_ = {"H" : H, "L" : L, "S" : S, "alpha" : self.get("alpha")} 

2997 self.__class__ = HLS 

2998 

2999 # Transform from RGB -> HSV 

3000 elif to == "HSV": 

3001 [H, S, V] = clib.sRGB_to_HSV(self.get("R"), self.get("G"), self.get("B")) 

3002 self._data_ = {"H" : H, "S" : S, "V" : V, "alpha" : self.get("alpha")} 

3003 self.__class__ = HSV 

3004 

3005 # The rest are transformations along a path 

3006 elif to in ["CIEXYZ"]: 

3007 via = ["RGB", to] 

3008 self._transform_via_path_(via, fixup = fixup) 

3009 

3010 elif to in ["CIELUV", "CIELAB"]: 

3011 via = ["RGB", "CIEXYZ", to] 

3012 self._transform_via_path_(via, fixup = fixup) 

3013 

3014 elif to in ["HCL","polarLUV"]: 

3015 via = ["RGB", "CIEXYZ", "CIELUV", to] 

3016 self._transform_via_path_(via, fixup = fixup) 

3017 

3018 elif to == "polarLAB": 

3019 via = ["RGB", "CIEXYZ", "CIELAB", to] 

3020 self._transform_via_path_(via, fixup = fixup) 

3021 

3022 else: self._cannot(self.__class__.__name__, to) 

3023 

3024 

3025class CIELAB(colorobject): 

3026 """Create CIELAB Color Object 

3027 

3028 Creates a color object in the CIELAB color space. 

3029 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

3030 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

3031 :py:class:`polarLAB`, and :py:class:`hexcols`. 

3032 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

3033 

3034 Args: 

3035 L (int, float, list, numpy.array): 

3036 Numeric value(s) for L dimension. 

3037 A (int, float, list, numpy.array): 

3038 Numeric value(s) for A dimension. 

3039 B (int, float, list, numpy.array): 

3040 Numeric value(s) for B dimension. 

3041 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3042 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3043 opacity. If `None` (default) no transparency is added. 

3044 

3045 Example: 

3046 

3047 >>> from colorspace import CIELAB 

3048 >>> # Constructing color object with one single color via float 

3049 >>> CIELAB(-30, 10, 10) 

3050 >>> #: Constructing object via lists 

3051 >>> CIELAB([-30, 30], [20, 80], [40, 40]) 

3052 >>> #: Constructing object via numpy arrays 

3053 >>> from numpy import asarray 

3054 >>> CIELAB(asarray([-30, 30]), asarray([20, 80]), asarray([40, 40])) 

3055 

3056 """ 

3057 

3058 def __init__(self, L, A, B, alpha = None): 

3059 

3060 # checking inputs, save inputs on object 

3061 self._data_ = {} # Dict to store the colors/color dimensions 

3062 tmp = self._colorobject_check_input_arrays_(L = L, A = A, B = B, alpha = alpha) 

3063 for key,val in tmp.items(): self._data_[key] = val 

3064 # White spot definition (the default) 

3065 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3066 

3067 

3068 def to(self, to, fixup = True): 

3069 """Transform Color Space 

3070 

3071 Allows to transform the current object into a different color space, 

3072 if possible. 

3073 

3074 Args: 

3075 to (str): Name of the color space into which the colors should be 

3076 converted (e.g., `CIEXYZ`, `HCL`, `hex`, `RGB`, ...) 

3077 fixup (bool): Whether or not colors outside the defined rgb color space 

3078 should be corrected if necessary, defaults to True. 

3079 

3080 Returns: 

3081 No return, converts the object into a new color space and modifies 

3082 the underlying object. After calling this method the object will 

3083 be of a different class. 

3084 """ 

3085 self._check_if_allowed_(to) 

3086 from . import colorlib 

3087 clib = colorlib() 

3088 

3089 # Nothing to do (converted to itself) 

3090 if to == self.__class__.__name__: 

3091 return 

3092 

3093 # Transformations CIELAB -> CIEXYZ 

3094 elif to == "CIEXYZ": 

3095 [X, Y, Z] = clib.LAB_to_XYZ(self.get("L"), self.get("A"), self.get("B"), 

3096 self.WHITEX, self.WHITEY, self.WHITEZ) 

3097 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

3098 self.__class__ = CIEXYZ 

3099 

3100 # Transformation CIELAB -> polarLAB 

3101 elif to == "polarLAB": 

3102 [L, A, B] = clib.LAB_to_polarLAB(self.get("L"), self.get("A"), self.get("B")) 

3103 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

3104 self.__class__ = polarLAB 

3105 

3106 # The rest are transformations along a path 

3107 elif to == "CIELUV": 

3108 via = ["CIEXYZ", to] 

3109 self._transform_via_path_(via, fixup = fixup) 

3110 

3111 elif to in ["HCL","polarLUV"]: 

3112 via = ["CIEXYZ", "CIELUV", to] 

3113 self._transform_via_path_(via, fixup = fixup) 

3114 

3115 elif to == "RGB": 

3116 via = ["CIEXYZ", to] 

3117 self._transform_via_path_(via, fixup = fixup) 

3118 

3119 elif to == "sRGB": 

3120 via = ["CIEXYZ", "RGB", to] 

3121 self._transform_via_path_(via, fixup = fixup) 

3122 

3123 elif to == "hex": 

3124 via = ["CIEXYZ", "RGB", "sRGB", to] 

3125 self._transform_via_path_(via, fixup = fixup) 

3126 

3127 elif to in ["HLS", "HSV"]: 

3128 self._ambiguous(self.__class__.__name__, to) 

3129 

3130 else: self._cannot(self.__class__.__name__, to) 

3131 

3132 

3133class polarLAB(colorobject): 

3134 """Create Polar LAB Color Object 

3135 

3136 Creates a color object in the polar representation of the 

3137 :py:class:`CIELAB` color space. 

3138 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

3139 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

3140 :py:class:`polarLAB`, and :py:class:`hexcols`. 

3141 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

3142 

3143 Args: 

3144 L (int, float, list, numpy.array): 

3145 Numeric value(s) for L dimension. 

3146 A (int, float, list, numpy.array): 

3147 Numeric value(s) for A dimension. 

3148 B (int, float, list, numpy.array): 

3149 Numeric value(s) for B dimension. 

3150 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3151 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3152 opacity. If `None` (default) no transparency is added. 

3153 

3154 Examples: 

3155 

3156 >>> from colorspace import polarLAB 

3157 >>> cols = polarLAB([50, 80, 30], [100, 120, 140], [40, 130, 300]) 

3158 >>> cols 

3159 >>> #: Convert to hex colors 

3160 >>> cols.to("hex") 

3161 >>> cols 

3162 

3163 """ 

3164 

3165 def __init__(self, L, A, B, alpha = None): 

3166 

3167 # checking inputs, save inputs on object 

3168 self._data_ = {} # Dict to store the colors/color dimensions 

3169 tmp = self._colorobject_check_input_arrays_(L = L, A = A, B = B, alpha = alpha) 

3170 for key,val in tmp.items(): self._data_[key] = val 

3171 # White spot definition (the default) 

3172 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3173 

3174 

3175 def to(self, to, fixup = True): 

3176 """Transform Color Space 

3177 

3178 Allows to transform the current object into a different color space, 

3179 if possible. Converting the colors of the current object into 

3180 another color space. After calling this method, the object 

3181 will be of a different class. 

3182 

3183 Args: 

3184 to (str): Name of the color space into which the colors should be 

3185 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3186 fixup (bool): Whether or not colors outside the defined rgb color space 

3187 should be corrected if necessary, defaults to `True`. 

3188 

3189 Examples: 

3190 

3191 >>> from colorspace import polarLAB 

3192 >>> x = polarLAB([ 25, 45, 65, 85], 

3193 >>> [ 72, 75, 54, 31], 

3194 >>> [310, 338, 36, 92]) 

3195 >>> x 

3196 >>> #: 

3197 >>> type(x) 

3198 >>> #: Convert colors to sRGB 

3199 >>> x.to("sRGB") 

3200 >>> x 

3201 >>> #: 

3202 >>> type(x) 

3203 >>> #: Convert from sRGB to hex 

3204 >>> x.to("hex") 

3205 >>> x 

3206 >>> #: Convert back to polarLAB colors. 

3207 >>> # Round-off errors due to conversion to 'hex'. 

3208 >>> x.to("polarLAB") 

3209 >>> x 

3210 >>> #: Extracting hex colors (returns list of str) 

3211 >>> x.colors() 

3212 

3213 """ 

3214 self._check_if_allowed_(to) 

3215 from . import colorlib 

3216 clib = colorlib() 

3217 

3218 # Nothing to do (converted to itself) 

3219 if to == self.__class__.__name__: 

3220 return 

3221 

3222 # The only transformation we need is from polarLAB -> LAB 

3223 elif to == "CIELAB": 

3224 [L, A, B] = clib.polarLAB_to_LAB(self.get("L"), self.get("A"), self.get("B")) 

3225 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

3226 self.__class__ = CIELAB 

3227 

3228 # The rest are transformationas along a path 

3229 elif to == "CIEXYZ": 

3230 via = ["CIELAB", to] 

3231 self._transform_via_path_(via, fixup = fixup) 

3232 

3233 elif to == "CIELUV": 

3234 via = ["CIELAB", "CIEXYZ", to] 

3235 self._transform_via_path_(via, fixup = fixup) 

3236 

3237 elif to in ["HCL", "polarLUV"]: 

3238 via = ["CIELAB", "CIEXYZ", "CIELUV", to] 

3239 self._transform_via_path_(via, fixup = fixup) 

3240 

3241 elif to == "RGB": 

3242 via = ["CIELAB", "CIEXYZ", to] 

3243 self._transform_via_path_(via, fixup = fixup) 

3244 

3245 elif to == "sRGB": 

3246 via = ["CIELAB", "CIEXYZ", "RGB", to] 

3247 self._transform_via_path_(via, fixup = fixup) 

3248 

3249 elif to == "hex": 

3250 via = ["CIELAB", "CIEXYZ", "RGB", "sRGB", to] 

3251 self._transform_via_path_(via, fixup = fixup) 

3252 

3253 elif to in ["HLS", "HSV"]: 

3254 self._ambiguous(self.__class__.__name__, to) 

3255 

3256 else: self._cannot(self.__class__.__name__, to) 

3257 

3258 

3259class HSV(colorobject): 

3260 """Create HSV Color Object 

3261 

3262 Creates a color object in the Hue-Saturation-Value (HSV) color space. 

3263 Can be converted to: :py:class:`RGB`, :py:class:`sRGB`, :py:class:`HLS`, 

3264 and :py:class:`hexcols`. 

3265 Not allowed (ambiguous) are transformations to :py:class:`CIEXYZ`, 

3266 :py:class:`CIELUV`, :py:class:`CIELAB`, :py:class:`polarLUV`, and 

3267 :py:class:`polarLAB`. 

3268 

3269 Args: 

3270 H (int, float, list, numpy.array): 

3271 Numeric value(s) for Hue dimension. 

3272 S (int, float, list, numpy.array): 

3273 Numeric value(s) for Saturation dimension. 

3274 V (int, float, list, numpy.array): 

3275 Numeric value(s) for Value dimension. 

3276 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3277 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3278 opacity. If `None` (default) no transparency is added. 

3279 

3280 Examples: 

3281 

3282 >>> #: Constructing object via numpy arrays 

3283 >>> from colorspace import HSV 

3284 >>> # Constructing color object with one single color via float 

3285 >>> HSV(150, 150, 10) 

3286 >>> #: Constructing object via lists 

3287 >>> HSV([150, 150, 10], [1.5, 0, 1.5], [0.1, 0.7, 0.1]) 

3288 >>> #: Constructing object via numpy arrays 

3289 >>> from numpy import asarray 

3290 >>> cols = HSV(asarray([150, 150, 150]), 

3291 >>> asarray([1.5, 0, 1.5]), 

3292 >>> asarray([0.1, 0.7, 0.1])) 

3293 >>> cols 

3294 >>> #: Converting to RGB 

3295 >>> cols.to("RGB") 

3296 >>> cols 

3297 """ 

3298 

3299 def __init__(self, H, S, V, alpha = None): 

3300 

3301 # checking inputs, save inputs on object 

3302 self._data_ = {} # Dict to store the colors/color dimensions 

3303 tmp = self._colorobject_check_input_arrays_(H = H, S = S, V = V, alpha = alpha) 

3304 for key,val in tmp.items(): self._data_[key] = val 

3305 # White spot definition (the default) 

3306 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3307 

3308 

3309 def to(self, to, fixup = True): 

3310 """Transform Color Space 

3311 

3312 Allows to transform the current object into a different color space, 

3313 if possible. Converting the colors of the current object into 

3314 another color space. After calling this method, the object 

3315 will be of a different class. 

3316 

3317 Args: 

3318 to (str): Name of the color space into which the colors should be 

3319 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3320 fixup (bool): Whether or not colors outside the defined rgb color space 

3321 should be corrected if necessary, defaults to `True`. 

3322 

3323 Examples: 

3324 

3325 >>> from colorspace import HSV 

3326 >>> x = HSV([ 264, 314, 8, 44], 

3327 >>> [0.80, 0.83, 0.57, 0.33], 

3328 >>> [0.57, 0.75, 0.95, 0.91]) 

3329 >>> x 

3330 >>> #: 

3331 >>> type(x) 

3332 >>> #: Convert colors to HLS 

3333 >>> x.to("HLS") 

3334 >>> x 

3335 >>> #: 

3336 >>> type(x) 

3337 >>> #: Convert colors to HSV 

3338 >>> x.to("HSV") 

3339 >>> x 

3340 >>> #: Extracting hex colors (returns list of str) 

3341 >>> x.colors() 

3342 

3343 """ 

3344 self._check_if_allowed_(to) 

3345 from . import colorlib 

3346 clib = colorlib() 

3347 

3348 # Nothing to do (converted to itself) 

3349 if to == self.__class__.__name__: 

3350 return 

3351 

3352 # The only transformation we need is back to RGB 

3353 elif to == "sRGB": 

3354 [R, G, B] = clib.HSV_to_sRGB(self.get("H"), self.get("S"), self.get("V")) 

3355 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3356 self.__class__ = sRGB 

3357 

3358 # From HLS to RGB: take direct path (not via sRGB) 

3359 elif to in ["RGB"]: 

3360 [R, G, B] = clib.HSV_to_RGB(self.get("H"), self.get("S"), self.get("V")) 

3361 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3362 self.__class__ = RGB 

3363 

3364 elif to == "hex": 

3365 via = ["sRGB", to] 

3366 self._transform_via_path_(via, fixup = fixup) 

3367 

3368 elif to == "HLS": 

3369 via = ["sRGB", to] 

3370 self._transform_via_path_(via, fixup = fixup) 

3371 

3372 elif to in ["CIEXYZ", "CIELUV", "CIELAB", "polarLUV", "HCL", "polarLAB"]: 

3373 self._ambiguous(self.__class__.__name__, to) 

3374 

3375 else: self._cannot(self.__class__.__name__, to) 

3376 

3377 

3378class HLS(colorobject): 

3379 """Create HLS Color Object 

3380 

3381 Creates a color object in the Hue-Lightness-Saturation (HLS) color space. 

3382 Can be converted to: :py:class:`RGB`, :py:class:`sRGB`, :py:class:`HSV`, 

3383 and :py:class:`hexcols`. 

3384 Not allowed (ambiguous) are transformations to :py:class:`CIEXYZ`, 

3385 :py:class:`CIELUV`, :py:class:`CIELAB`, :py:class:`polarLUV`, and 

3386 :py:class:`polarLAB`. 

3387 

3388 Args: 

3389 H (int, float, list, numpy.array): 

3390 Numeric value(s) for Hue dimension. 

3391 L (int, float, list, numpy.array): 

3392 Numeric value(s) for Lightness dimension. 

3393 S (int, float, list, numpy.array): 

3394 Numeric value(s) for Saturation dimension. 

3395 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3396 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3397 opacity. If `None` (default) no transparency is added. 

3398 

3399 Examples: 

3400 

3401 >>> from colorspace import HLS 

3402 >>> # Constructing color object with one single color via float 

3403 >>> HLS(150, 0.1, 3) 

3404 >>> #: Constructing object via lists 

3405 >>> HLS([150, 0, 10], [0.1, 0.7, 0.1], [3, 0, 3]) 

3406 >>> #: Constructing object via numpy arrays 

3407 >>> from numpy import asarray 

3408 >>> cols = HLS(asarray([150, 0, 10]), 

3409 >>> asarray([0.1, 0.7, 0.1]), 

3410 >>> asarray([3, 0, 3])) 

3411 >>> cols 

3412 >>> #: Converting to RGB 

3413 >>> cols.to("RGB") 

3414 >>> cols 

3415 """ 

3416 

3417 def __init__(self, H, L, S, alpha = None): 

3418 

3419 # checking inputs, save inputs on object 

3420 self._data_ = {} # Dict to store the colors/color dimensions 

3421 tmp = self._colorobject_check_input_arrays_(H = H, L = L, S = S, alpha = None) 

3422 for key,val in tmp.items(): self._data_[key] = val 

3423 # White spot definition (the default) 

3424 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3425 

3426 

3427 def to(self, to, fixup = True): 

3428 """Transform Color Space 

3429 

3430 Allows to transform the current object into a different color space, 

3431 if possible. Converting the colors of the current object into 

3432 another color space. After calling this method, the object 

3433 will be of a different class. 

3434 

3435 Args: 

3436 to (str): Name of the color space into which the colors should be 

3437 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3438 fixup (bool): Whether or not colors outside the defined rgb color space 

3439 should be corrected if necessary, defaults to `True`. 

3440 

3441 Examples: 

3442 

3443 >>> from colorspace import HLS 

3444 >>> x = HLS([264, 314, 8, 44], 

3445 >>> [0.34, 0.44, 0.68, 0.75], 

3446 >>> [0.67, 0.71, 0.84, 0.62]) 

3447 >>> x 

3448 >>> #: 

3449 >>> type(x) 

3450 >>> #: Convert colors to HSV 

3451 >>> x.to("HSV") 

3452 >>> x 

3453 >>> #: 

3454 >>> type(x) 

3455 >>> #: Convert colors to HLS 

3456 >>> x.to("HLS") 

3457 >>> x 

3458 >>> #: Extracting hex colors (returns list of str) 

3459 >>> x.colors() 

3460 

3461 """ 

3462 self._check_if_allowed_(to) 

3463 from . import colorlib 

3464 clib = colorlib() 

3465 

3466 # Nothing to do (converted to itself) 

3467 if to == self.__class__.__name__: 

3468 return 

3469 

3470 # The only transformation we need is back to RGB 

3471 elif to == "sRGB": 

3472 [R, G, B] = clib.HLS_to_sRGB(self.get("H"), self.get("L"), self.get("S")) 

3473 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3474 self.__class__ = sRGB 

3475 

3476 # From HSV to RGB: take direct path (not via sRGB) 

3477 elif to in ["RGB"]: 

3478 [R, G, B] = clib.HLS_to_RGB(self.get("H"), self.get("L"), self.get("S")) 

3479 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3480 self.__class__ = RGB 

3481 

3482 elif to == "hex": 

3483 via = ["sRGB", to] 

3484 self._transform_via_path_(via, fixup = fixup) 

3485 

3486 elif to == "HSV": 

3487 via = ["sRGB", to] 

3488 self._transform_via_path_(via, fixup = fixup) 

3489 

3490 elif to in ["CIEXYZ", "CIELUV", "CIELAB", "polarLUV", "HCL", "polarLAB"]: 

3491 self._ambiguous(self.__class__.__name__, to) 

3492 

3493 else: self._cannot(self.__class__.__name__, to) 

3494 

3495 

3496class hexcols(colorobject): 

3497 """Create Hex Color Object 

3498 

3499 Creates a color object using hex colors (str). 

3500 Can be converted to all other color spaces: :py:class:`CIELAB`, 

3501 :py:class:`CIELUV`, :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, 

3502 :py:class:`RGB`, :py:class:`polarLAB`, :py:class:`polarLUV`, and 

3503 :py:class:`sRGB`. 

3504 

3505 Args: 

3506 hex_ (str, list of str, numpy.ndarray of type str): 

3507 Hex colors. Only six and eight digit hex colors are allowed (e.g., 

3508 `#000000` or `#00000050` if with alpha channel). If invalid hex 

3509 colors are provided the object will raise an exception. Invalid hex 

3510 colors will be handled as `numpy.nan`. 

3511 

3512 Examples: 

3513 

3514 >>> from colorspace import hexcols 

3515 >>> # Creating hex color object from string 

3516 >>> hexcols("#cecece") 

3517 >>> #: Creating hex color object from list of strings 

3518 >>> hexcols(["#ff0000", "#00ff00"]) 

3519 >>> #: Creating hex colors via numpy array 

3520 >>> from numpy import asarray 

3521 >>> cols = hexcols(asarray(["#ff000030", "#00ff0030",  

3522 >>> "#FFFFFF", "#000"])) 

3523 >>> cols 

3524 >>> #: Convert hex colors to another color space (CIEXYZ) 

3525 >>> cols.to("CIEXYZ") 

3526 >>> cols 

3527 >>> #: Picking 7 hex colors from the Green-Orange 

3528 >>> # diverging palette for demonstrating standard representation 

3529 >>> # in jupyter engine and standard print. 

3530 >>> from colorspace import diverging_hcl 

3531 >>> cols2 = hexcols(diverging_hcl("Green-Orange")(7)) 

3532 >>> cols2 # jupyter HTML representation 

3533 >>> #: 

3534 >>> print(cols2) # default representation 

3535 """ 

3536 

3537 def __init__(self, hex_): 

3538 

3539 from colorspace import check_hex_colors 

3540 import numpy as np 

3541 

3542 # If hex_ is str, convert to list 

3543 if isinstance(hex_, str): hex_ = [hex_] 

3544 hex_ = check_hex_colors(hex_) 

3545 

3546 self._data_ = {} # Dict to store the colors/color dimensions 

3547 

3548 # This is the one step where we extract transparency from 

3549 # hex colors once we enter the world of colorobjects. 

3550 def get_alpha(hex_): 

3551 # Trying to extract char 7:9, leave None if color is None 

3552 hex_ = [None if (x is None or len(x) < 9) else x[7:9] for x in hex_] 

3553 return [None if x is None else int(x, 16) / 255 for x in hex_] 

3554 

3555 # Remove apha if any 

3556 def remove_alpha(hex_): 

3557 return [None if x is None else x[:7] if len(x) > 7 else x for x in hex_] 

3558 

3559 # Forwarding input 'hex_' to check_hex_colors which will throw 

3560 # an error if we do not understand this input type. 

3561 tmp = np.asarray(get_alpha(hex_), dtype = "float") 

3562 # Remove alpha from 9-digit hex if any, convert to ndarray 

3563 self._data_["hex_"] = np.asarray(remove_alpha(hex_), dtype = object) 

3564 # Store alpha (if any) 

3565 if not np.all(np.isnan(tmp)): self._data_["alpha"] = tmp 

3566 

3567 # White spot definition (the default) 

3568 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3569 

3570 

3571 def to(self, to, fixup = True): 

3572 """Transform Color Space 

3573 

3574 Allows to transform the current object into a different color space, 

3575 if possible. 

3576 

3577 Allows to transform the current object into a different color space, 

3578 if possible. Converting the colors of the current object into 

3579 another color space. After calling this method, the object 

3580 will be of a different class. 

3581 

3582 Args: 

3583 to (str): Name of the color space into which the colors should be 

3584 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"HSL"`, `"sRGB"`, ...). 

3585 fixup (bool): Whether or not colors outside the defined rgb color space 

3586 should be corrected if necessary, defaults to `True`. 

3587 

3588 Examples: 

3589 

3590 >>> from colorspace import hexcols 

3591 >>> x = hexcols(["#4B1D91", "#BF219A", "#F27B68", "#E7D39A"]) 

3592 >>> x 

3593 >>> #: 

3594 >>> type(x) 

3595 >>> #: Convert colors to sRGB 

3596 >>> x.to("sRGB") 

3597 >>> x 

3598 >>> #: 

3599 >>> type(x) 

3600 >>> #: Convert from sRGB to HCL 

3601 >>> x.to("HCL") 

3602 >>> x 

3603 >>> #: Convert back to hex colors. 

3604 >>> # Round-off errors due to conversion to 'hex'. 

3605 >>> x.to("hex") 

3606 >>> x 

3607 

3608 """ 

3609 self._check_if_allowed_(to) 

3610 from . import colorlib 

3611 clib = colorlib() 

3612 

3613 # Nothing to do (converted to itself) 

3614 if to in ["hex", self.__class__.__name__]: 

3615 return 

3616 

3617 # The only transformation we need is from hexcols -> sRGB 

3618 elif to == "sRGB": 

3619 [R, G, B] = clib.hex_to_sRGB([None if x is None else x[0:7] for x in self.get("hex_")]) 

3620 alpha = self.get("alpha") 

3621 self._data_ = {"R": R, "G": G, "B": B} 

3622 if alpha is not None: self._data_["alpha"] = alpha 

3623 self.__class__ = sRGB 

3624 

3625 # The rest are transformations along a path 

3626 elif to == "RGB": 

3627 via = ["sRGB", to] 

3628 self._transform_via_path_(via, fixup = fixup) 

3629 

3630 elif to in ["HLS", "HSV"]: 

3631 via = ["sRGB", to] 

3632 self._transform_via_path_(via, fixup = fixup) 

3633 

3634 elif to in ["CIEXYZ"]: 

3635 via = ["sRGB", "RGB", to] 

3636 self._transform_via_path_(via, fixup = fixup) 

3637 

3638 elif to in ["CIELUV", "CIELAB"]: 

3639 via = ["sRGB", "RGB", "CIEXYZ", to] 

3640 self._transform_via_path_(via, fixup = fixup) 

3641 

3642 elif to in ["HCL", "polarLUV"]: 

3643 via = ["sRGB", "RGB", "CIEXYZ", "CIELUV", to] 

3644 self._transform_via_path_(via, fixup = fixup) 

3645 

3646 elif to in "polarLAB": 

3647 via = ["sRGB", "RGB", "CIEXYZ", "CIELAB", to] 

3648 self._transform_via_path_(via, fixup = fixup) 

3649 

3650 else: self._cannot(self.__class__.__name__, to) 

3651 

3652 def _repr_html_(self): 

3653 """_repr_html_() 

3654 

3655 Standard HTML representation of the object when using 

3656 the jupyter engine. Will display the colors as html list, 

3657 thanks to @matteoferla (github) for the idea and contribution. 

3658 """ 

3659 from colorspace import contrast_ratio 

3660 

3661 # ul style 

3662 su = {"font-size": "0.5em", "list-style": "none", "display": "flex", 

3663 "padding": "0 0 0.5em 0", "text-align": "center"} 

3664 # li style 

3665 sl = {"width": "5.75em", "height": "5.75em", "padding": "0.25em", 

3666 "display": "inline-block", "margin": "0 0.25em 0 0", 

3667 "border": "0.5px solid gray"} 

3668 

3669 # Getting list of hex colors 

3670 cols = self.colors() 

3671 

3672 dict2style = lambda d: ';'.join(map(':'.join, d.items())) 

3673 

3674 res = f"<ul class=\"colorspace-hexcols\" style=\"{dict2style(su)}\">\n" 

3675 for i in range(len(self)): 

3676 # Calculating contrast ratio to decide text color 

3677 cw = contrast_ratio("#FFF", bg = cols[i])[0] 

3678 cb = contrast_ratio("#000", bg = cols[i])[0] 

3679 sl["color"] = "white" if cw > cb else "black" 

3680 sl["background-color"] = cols[i] 

3681 res += f"<li style=\"{dict2style(sl)}\">{cols[i]}</li>\n" 

3682 

3683 res += "</ul>\n" 

3684 return res 

3685 

3686def compare_colors(a, b, exact = False, _all = True, atol = None): 

3687 """Compare Sets of Colors 

3688 

3689 Compares two sets of colors based on two color objects. The objects 

3690 provided on argument `a` and `b` must inherit from `colorobject`. 

3691 This can be any of the following classes: :py:class:`CIELAB`, 

3692 :py:class:`CIELUV`, :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, 

3693 :py:class:`RGB`, :py:class:`hexcols`, :py:class:`polarLAB`, 

3694 :py:class:`polarLUV`, or :py:class:`sRGB`. 

3695 

3696 Args: 

3697 a (colorobject): Object which inherits from `colorobject`. 

3698 b (colorobject): Object which inherits from `colorobject`. 

3699 exact (bool): Default `False`, check for colors being nearly equal 

3700 (see `atol`). If set to `True` the coordinates must be identical. 

3701 Note: in case `a` and `b` are hex colors 

3702 (colorspace.colorlib.hexcols) strings will always be matched exactly. 

3703 _all (bool): Default `True`; the function will return `True` if 

3704 all colors are identical/nearly equal. If set to `False` the return 

3705 will be a list of bool containing `True` and `False` for each 

3706 pair of colors. 

3707 atol (None or float): Absolute tolerance for the distance measure 

3708 between two colors to be considered as nearly equal (must be > 0 if set). 

3709 Only used if `exact = False`, else `atol = 1e-6` is used. If set 

3710 to `None` the tolerance will automatically be set depending on the 

3711 type of the objects. Defaults to None. 

3712 

3713 

3714 Returns: 

3715 bool, list: Returns `True` if all colors of `a` are exactly equal or 

3716 nearly equal (see arguments) to the colors in object `b`. If `_all = 

3717 False`, a list of bool is returned indicating pair-wise comparison 

3718 of all colors in `a` and `b`. 

3719 

3720 Example: 

3721 

3722 >>> from colorspace import RGB, hexcols, compare_colors 

3723 >>> 

3724 >>> # Three RGB colors 

3725 >>> a = RGB([0.5, 0.5], [0.1, 0.1], [0.9, 0.9]) 

3726 >>> b = RGB([0.5, 0.5], [0.1, 0.1], [0.9, 0.91]) 

3727 >>>  

3728 >>> compare_colors(a, b) 

3729 >>> #: 

3730 >>> compare_colors(a, b, atol = 0.1) 

3731 >>> #: 

3732 >>> compare_colors(a, b, exact = True) 

3733 >>> #: 

3734 >>> compare_colors(a, b, exact = True, _all = False) 

3735 >>> 

3736 >>> #: Same example using two sets of hexcolor objects 

3737 >>> x = hexcols(["#ff00ff", "#003300"]) 

3738 >>> y = hexcols(["#ff00ff", "#003301"]) 

3739 >>> compare_colors(x, y) 

3740 >>> #: 

3741 >>> compare_colors(x, y, _all = False) 

3742 >>> 

3743 >>> #: Convert HEX to HCL (polarLUV) and back, compare the 

3744 >>> # resulting colors to the original ones; should be identical 

3745 >>> from copy import deepcopy 

3746 >>> z = hexcols(["#ff00ff", "#003301"]) 

3747 >>> zz = deepcopy(z) 

3748 >>> zz.to("HCL") 

3749 >>> zz 

3750 >>> #: 

3751 >>> zz.to("hex") 

3752 >>> zz 

3753 >>> #: 

3754 >>> compare_colors(z, zz) 

3755 

3756 Raises: 

3757 TypeError: If `a` or `b` are not objects of a class which inherits from 

3758 `colorobject`. 

3759 TypeError: If `a` and `b` are not of the same class. 

3760 ValueError: If `a` and `b` are not of the same length, i.e., do not contain 

3761 the same number of colors. 

3762 TypeError: If `exact` or `_all` are not bool. 

3763 TypeError: If `atol` is neither `None` nor float. 

3764 ValueError: If `atol` is not larger than 0. 

3765 """ 

3766 

3767 from numpy import sqrt, isclose 

3768 

3769 if not isinstance(a, colorobject): 

3770 raise TypeError("argument `a` must be an object based on colorspace.colorlib.colorobject") 

3771 if not isinstance(b, colorobject): 

3772 raise TypeError("argument `b` must be an object based on colorspace.colorlib.colorobject") 

3773 if not type(a) == type(b): 

3774 raise TypeError("Input `a` and `b` not of same type") 

3775 if not a.length() == b.length(): 

3776 raise ValueError("Objects do not contain the same number of colors") 

3777 if not isinstance(exact, bool): 

3778 raise TypeError("argument `exact` must be bool") 

3779 if not isinstance(_all, bool): 

3780 raise TypeError("argument `_all` must be bool") 

3781 if not isinstance(atol, float) and not isinstance(atol, type(None)): 

3782 raise TypeError("argument `atol` must be float or None") 

3783 if atol is not None and atol <= 0: 

3784 raise ValueError("argument `atol` must be > 0.") 

3785 

3786 if exact: atol = 1e-6 

3787 

3788 def distance(a, b): 

3789 dist = 0. # Start with zero distance 

3790 for n in list(a._data_.keys()): 

3791 tmpa = a.get(n) 

3792 tmpb = b.get(n) 

3793 # Both None and that is our alpha channel, no judgement 

3794 if tmpa is None and tmpb is None and n == "alpha": 

3795 continue 

3796 # Bot not none, calc Eudlidean distance 

3797 elif not tmpa is None and not tmpb is None: 

3798 dist += (tmpa[0] - tmpb[0])**2.0 

3799 # One missing? Penalize by + 100 

3800 else: 

3801 dist += 100. 

3802 

3803 return sqrt(dist) 

3804 

3805 # Compare hex colors; always on string level 

3806 if isinstance(a, hexcols): 

3807 # Getting coordinates 

3808 res = [a[i].get("hex_")[0].upper() == b[i].get("hex_")[0].upper() for i in range(0, a.length())] 

3809 # Calculate absolute difference between coordinates R/G/B[/alpha]. 

3810 # Threading alpha like another coordinates as all coordinates are scaled [0-1]. 

3811 elif isinstance(a, RGB) or isinstance(a, sRGB) or \ 

3812 isinstance(a, HLS) or isinstance(a, HSV): 

3813 # HEX precision in RGB coordinates is about sqrt((1./255.)**2 * 3) / 2 = 0.003396178 

3814 if not atol: atol = 0.005 

3815 res = [distance(a[i], b[i]) for i in range(0, a.length())] 

3816 res = isclose(res, 0, atol = atol) 

3817 # HCL or polarLUV (both return instance polarLUV) 

3818 # TODO(enhancement): Calculating the Euclidean distance on HCL and (if 

3819 # available) alpha which itself is in [0, 1]. Should be weighted 

3820 # differently (scaled distance)? 

3821 elif isinstance(a, polarLUV) or isinstance(a, CIELUV) or isinstance(a, CIELAB) or \ 

3822 isinstance(a, polarLAB) or isinstance(a, CIEXYZ): 

3823 

3824 if not atol: atol = 1 

3825 res = [distance(a[i], b[i]) for i in range(0, a.length())] 

3826 res = isclose(res, 0, atol = atol) 

3827 

3828 

3829 # If _all is True: check if all elements are True 

3830 if _all: 

3831 from numpy import all 

3832 res = all(res) 

3833 return res 

3834 

3835 

3836 

3837