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

1069 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-29 15:11 +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 # Convert input to float 

242 u = np.asarray(u, dtype = "float") 

243 

244 # Checking inputs 

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

246 

247 # Transform 

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

249 # np.fmax to avoid overflow 

250 if val > 0.00304: 

251 u[i] = 1.055 * np.power(val, (1. / gamma[i])) - 0.055 

252 else: 

253 u[i] = 12.92 * val 

254 

255 return u 

256 

257 def ftrans(self, u, gamma): 

258 """Gamma Correction 

259 

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

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

262 

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

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

265 a power transform with exponent `2.4`. 

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

267 

268 Args: 

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

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

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

272 

273 Returns: 

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

275 """ 

276 

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

278 

279 # Input check 

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

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

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

283 

284 # Checking inputs 

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

286 

287 # Convert input to float 

288 u = np.asarray(u, dtype = "float") 

289 

290 # Transform  

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

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

293 else: u[i] = val / 12.92 

294 

295 return u 

296 

297 # Support function qtrans 

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

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

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

301 

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

303 elif hue < 180.: return q2 

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

305 else: return q1 

306 

307 

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

309 """Convert Standard RGB to RGB 

310 

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

312 

313 Args: 

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

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

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

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

318 

319 Returns: 

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

321 """ 

322 

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

324 

325 # Input check 

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

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

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

329 

330 # Checking inputs 

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

332 

333 # Apply gamma correction 

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

335 

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

337 """Convert RGB to Standard RGB 

338 

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

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

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

342 

343 Args: 

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

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

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

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

348 

349 Returns: 

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

351 """ 

352 

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

354 

355 # Input check 

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

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

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

359 

360 # Checking inputs 

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

362 

363 # Apply gamma correction 

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

365 

366 # ------------------------------------------------------------------- 

367 # ------------------------------------------------------------------- 

368 # ------------------------------------------------------------------- 

369 # ------------------------------------------------------------------- 

370 

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

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

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

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

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

376 """Convert RGB to CIEXYZ 

377 

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

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

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

381 specify a specific white point. 

382 

383 Args: 

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

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

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

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

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

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

390 YN: See `XN`. 

391 ZN: See `XN`. 

392 

393 Returns: 

394 list: Returns corresponding coordinates of CIE chromaticities, a 

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

396 """ 

397 

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

399 n = len(R) # Number of colors 

400 

401 # Loading definition of white 

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

403 

404 # Checking input 

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

406 

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

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

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

410 

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

412 """Convert CIEXYZ to RGB 

413 

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

415 CIEXYZ color space, 

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

417 specify a specific white point. 

418 

419 Args: 

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

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

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

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

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

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

426 YN: See `XN`. 

427 ZN: See `XN`. 

428 

429 Returns: 

430 list: Returns corresponding coordinates as a list of 

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

432 """ 

433 

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

435 n = len(X) # Number of colors 

436 

437 # Loading definition of white 

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

439 

440 # Checking input 

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

442 

443 # Only YN is used 

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

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

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

447 

448 

449 # ------------------------------------------------------------------- 

450 # ------------------------------------------------------------------- 

451 # ------------------------------------------------------------------- 

452 # ------------------------------------------------------------------- 

453 

454 

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

456 ## 

457 ## ## ----- CIE-XYZ <-> sRGB ----- 

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

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

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

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

462 ## """sRGB to CIEXYZ. 

463 

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

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

466 

467 ## Args: 

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

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

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

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

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

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

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

475 ## YN: See `XN`. 

476 ## ZN: See `XN`. 

477 

478 ## Returns: 

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

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

481 ## Z]`). 

482 ## """ 

483 

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

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

486 

487 ## # Loading definition of white 

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

489 

490 ## # Checking input 

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

492 

493 ## # Transform R/G/B 

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

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

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

497 

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

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

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

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

502 

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

504 ## """CIEXYZ to sRGB. 

505 

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

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

508 

509 ## Args: 

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

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

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

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

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

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

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

517 ## YN: See `XN`. 

518 ## ZN: See `XN`. 

519 

520 ## Returns: 

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

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

523 ## """ 

524 

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

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

527 

528 ## # Loading definition of white 

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

530 

531 ## # Checking input 

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

533 

534 ## # Transform and return 

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

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

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

538 

539 

540 # ------------------------------------------------------------------- 

541 # ------------------------------------------------------------------- 

542 # ------------------------------------------------------------------- 

543 # ------------------------------------------------------------------- 

544 

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

546 

547 

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

549 """Convert CIELAB to CIEXYZ 

550 

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

552 CIELAB color space, 

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

554 specify a specific white point. 

555 

556 Args: 

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

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

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

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

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

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

563 YN: See `XN`. 

564 ZN: See `XN`. 

565 

566 Returns: 

567 list: Returns corresponding coordinates of CIE chromaticities as a 

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

569 """ 

570 

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

572 n = len(L) # Number of colors 

573 

574 # Loading definition of white 

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

576 

577 # Checking input 

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

579 

580 # Result arrays 

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

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

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

584 

585 # Calculate Y 

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

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

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

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

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

591 

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

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

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

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

596 else: 

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

598 

599 # Calculate X 

600 fx = fy + (A / 500.) 

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

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

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

604 else: 

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

606 

607 # Calculate Z 

608 fz = fy - (B / 200.) 

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

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

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

612 else: 

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

614 

615 return [X, Y, Z] 

616 

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

618 """Convert CIEXYZ to CIELAB 

619 

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

621 CIELAB color space, 

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

623 specify a specific white point. 

624 

625 Args: 

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

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

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

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

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

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

632 YN: See `XN`. 

633 ZN: See `XN`. 

634 

635 Returns: 

636 list: Returns corresponding coordinates of CIE chromaticities as 

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

638 """ 

639 

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

641 n = len(X) # Number of colors 

642 

643 # Loading definition of white 

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

645 

646 # Checking input 

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

648 

649 # Support function 

650 def f(t, _KAPPA, _EPSILON): 

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

652 if val > _EPSILON: 

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

654 else: 

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

656 return t 

657 

658 # Scaling 

659 xr = X / XN; 

660 yr = Y / YN; 

661 zr = Z / ZN; 

662 

663 # Calculate L 

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

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

666 if val > self._EPSILON: 

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

668 else: 

669 L[i] = self._KAPPA * val 

670 

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

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

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

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

675 

676 

677 # ------------------------------------------------------------------- 

678 # ------------------------------------------------------------------- 

679 # ------------------------------------------------------------------- 

680 # ------------------------------------------------------------------- 

681 

682 ## Commented as not yet used 

683 ## 

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

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

686 

687 ## .. note:: 

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

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

690 

691 ## Args: 

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

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

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

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

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

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

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

699 ## YN: See `XN`. 

700 ## ZN: See `XN`. 

701 

702 ## Returns: 

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

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

705 ## """ 

706 

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

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

709 

710 ## # Loading definition of white 

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

712 

713 ## # Checking input 

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

715 

716 ## # Transform 

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

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

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

720 

721 

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

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

724 

725 ## .. note:: 

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

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

728 

729 ## Args: 

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

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

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

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

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

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

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

737 ## YN: See `XN`. 

738 ## ZN: See `XN`. 

739 

740 ## Returns: 

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

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

743 ## """ 

744 

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

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

747 

748 ## # Loading definition of white 

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

750 

751 ## # Checking input 

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

753 

754 ## # Transform 

755 ## vY = L / 10.; 

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

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

758 ## vY = vY * vY; 

759 

760 ## Y = vY * XN 

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

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

763 

764 ## return [X, Y, Z] 

765 

766 

767 # ------------------------------------------------------------------- 

768 # ------------------------------------------------------------------- 

769 # ------------------------------------------------------------------- 

770 # ------------------------------------------------------------------- 

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

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

773 

774 Converts colors from the CIELAB color space into its polar 

775 representation (`polarLAB`). 

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

777 

778 Args: 

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

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

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

782 

783 Returns: 

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

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

786 """ 

787 

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

789 

790 # Checking input 

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

792 

793 # Compute H 

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

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

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

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

798 H[i] = val 

799 # Compute C 

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

801 

802 return [L, C, H] 

803 

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

805 """Convert polarLAB to CIELAB 

806 

807 Convert colors from the polar representation of the CIELAB 

808 color space into CIELAB coordinates. 

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

810 

811 Args: 

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

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

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

815 

816 Returns: 

817 list: Returns corresponding CIELAB chromaticities as a list of 

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

819 """ 

820 

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

822 

823 # Checking input 

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

825 

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

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

828 

829 return [L, A, B] 

830 

831 # ------------------------------------------------------------------- 

832 # ------------------------------------------------------------------- 

833 # ------------------------------------------------------------------- 

834 # ------------------------------------------------------------------- 

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

836 """Convert RGB to HSV 

837 

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

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

840 and value (HSV) coordinates. 

841 

842 Args: 

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

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

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

846 

847 Returns: 

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

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

850 the inputs. 

851 """ 

852 

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

854 

855 # Checking input 

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

857 

858 # Support function 

859 def gethsv(r, g, b): 

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

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

862 if y != x: 

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

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

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

866 s = (y - x)/y 

867 v = y 

868 else: 

869 h = 0. 

870 s = 0. 

871 v = y 

872 return [h, s, v] 

873 

874 # Result arrays 

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

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

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

878 

879 # Calculate h/s/v 

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

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

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

883 

884 return [h, s, v] 

885 

886 

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

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

889 

890 Takes a series of HSV coordinates and converts them 

891 to the sRGB color space. 

892 

893 Args: 

894 h (nympy.ndarray): Hue values. 

895 s (numpy.ndarray): Saturation. 

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

897 

898 Returns: 

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

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

901 the inputs. 

902 """ 

903 

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

905 

906 # Checking input 

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

908 

909 # Support function 

910 def getrgb(h, s, v): 

911 

912 # If Hue is not defined: 

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

914 

915 # Convert to [0-6] 

916 h = h / 60. 

917 i = np.floor(h) 

918 f = h - i 

919 

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

921 f = 1 - f 

922 

923 m = v * (1 - s) 

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

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

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

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

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

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

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

931 else: 

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

933 

934 # Result arrays 

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

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

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

938 

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

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

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

942 

943 return [r, g, b] 

944 

945 

946 # ------------------------------------------------------------------- 

947 # ------------------------------------------------------------------- 

948 # ------------------------------------------------------------------- 

949 # ------------------------------------------------------------------- 

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

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

952 

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

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

955 

956 Args: 

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

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

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

960 

961 Returns: 

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

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

964 the inputs. 

965 """ 

966 

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

968 

969 # Checking input 

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

971 

972 # Support function 

973 def gethls(r, g, b): 

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

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

976 

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

978 

979 if max != min: 

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

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

982 

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

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

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

986 

987 h = h * 60.; 

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

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

990 else: 

991 s = 0 

992 h = 0; 

993 

994 return [h, l, s] 

995 

996 # Result arrays 

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

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

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

1000 

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

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

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

1004 

1005 return [h, l, s] 

1006 

1007 

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

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

1010 

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

1012 

1013 Args: 

1014 h (numpy.ndarray): Hue values. 

1015 l (numpy.ndarray): Lightness. 

1016 s (numpy.ndarray): Saturation. 

1017 

1018 Returns: 

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

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

1021 the inputs. 

1022 """ 

1023 

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

1025 

1026 # Checking input 

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

1028 

1029 # Support function 

1030 def getrgb(h, l, s): 

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

1032 p1 = 2 * l - p2 

1033 

1034 # If saturation is zero 

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

1036 # Else 

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

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

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

1040 

1041 # Result arrays 

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

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

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

1045 

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

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

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

1049 

1050 return [r, g, b] 

1051 

1052 

1053 # ------------------------------------------------------------------- 

1054 # ------------------------------------------------------------------- 

1055 # ------------------------------------------------------------------- 

1056 # ------------------------------------------------------------------- 

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

1058 """Convert CIEXYZ to u and v 

1059 

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

1061 coordinates in the CIEXYZ color space to their corresponding 

1062 u and v coordinates. 

1063 

1064 Args: 

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

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

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

1068 

1069 Returns: 

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

1071 """ 

1072 

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

1074 

1075 # Checking input 

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

1077 

1078 # Result array 

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

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

1081 

1082 t = X + Y + Z 

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

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

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

1086 

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

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

1089 

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

1091 """Convert CIEXYZ to CIELUV. 

1092 

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

1094 CIELAB color space, 

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

1096 specify a specific white point. 

1097 

1098 Args: 

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

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

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

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

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

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

1105 YN: See `XN`. 

1106 ZN: See `XN`. 

1107 

1108 Returns: 

1109 list: Returns corresponding coordinates of CIE chromaticities as 

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

1111 """ 

1112 

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

1114 n = len(X) # Number of colors 

1115 

1116 # Loading definition of white 

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

1118 

1119 # Checking input 

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

1121 

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

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

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

1125 

1126 # Calculate L 

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

1128 y = Y / YN 

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

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

1131 

1132 # Calculate U/V 

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

1134 

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

1136 """Convert CIELUV to CIELAB 

1137 

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

1139 CIELAB color space, 

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

1141 specify a specific white point. 

1142 

1143 Args: 

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

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

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

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

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

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

1150 YN: See `XN`. 

1151 ZN: See `XN`. 

1152 

1153 Returns: 

1154 list: Returns corresponding coordinates of CIE chromaticities as 

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

1156 """ 

1157 

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

1159 n = len(L) # Number of colors 

1160 

1161 # Loading definition of white 

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

1163 

1164 # Checking input 

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

1166 

1167 # Result arrays 

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

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

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

1171 

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

1173 def fun(L, U, V): 

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

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

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

1177 

1178 # Compute Y 

1179 for i in idx: 

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

1181 

1182 # Calculate X/Z 

1183 from numpy import finfo, fmax 

1184 

1185 # Avoiding division by zero 

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

1187 L = fmax(eps, L) 

1188 

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

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

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

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

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

1194 

1195 return [X, Y, Z] 

1196 

1197 

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

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

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

1201 

1202 Converts colors from the CIELUV color space into its polar 

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

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

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

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

1207 

1208 Args: 

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

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

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

1212 

1213 Returns: 

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

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

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

1217 """ 

1218 

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

1220 

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

1222 

1223 # Calculate polarLUV coordinates 

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

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

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

1227 while val > 360: val -= 360. 

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

1229 H[i] = val 

1230 

1231 return [L, C, H] 

1232 

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

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

1235 

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

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

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

1239 

1240 Args: 

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

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

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

1244 

1245 Returns: 

1246 list: Returns corresponding CIELAB chromaticities as a list of 

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

1248 """ 

1249 

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

1251 

1252 # Checking input 

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

1254 

1255 H = self._DEG2RAD(H) 

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

1257 

1258 

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

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

1261 

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

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

1264 

1265 Args: 

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

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

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

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

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

1271 defaults to `True`. 

1272 

1273 Returns: 

1274 list: A list with hex color str. 

1275 """ 

1276 

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

1278 def rgbfixup(r, g, b): 

1279 def fun(x): 

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

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

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

1283 

1284 def rgbcleanup(r, g, b): 

1285 def fun(x): 

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

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

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

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

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

1291 else np.nan for e in x]) 

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

1293 

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

1295 # This only happens if fixup = FALSE. 

1296 def validrgb(r, g, b): 

1297 idxr = np.isfinite(r) 

1298 idxg = np.isfinite(g) 

1299 idxb = np.isfinite(b) 

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

1301 

1302 # Support function to create hex coded colors 

1303 def gethex(r, g, b): 

1304 

1305 # Converts int to hex string 

1306 def applyfun(x): 

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

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

1309 

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

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

1312 

1313 # Let's do the conversion! 

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

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

1316 

1317 # Create return array 

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

1319 

1320 # Check valid r/g/b coordinates 

1321 valid = validrgb(r, g, b) 

1322 if len(valid) > 0: 

1323 # Convert valid colors to hex 

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

1325 

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

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

1328 

1329 # Return numpy array 

1330 return np.asarray(res) 

1331 

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

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

1334 

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

1336 

1337 Args: 

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

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

1340 

1341 Returns: 

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

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

1344 """ 

1345 

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

1347 hex_ = np.asarray(hex_) 

1348 

1349 # Check for valid hex colors 

1350 def validhex(hex_): 

1351 from re import compile 

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

1353 from re import match 

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

1355 

1356 # Convert hex to rgb 

1357 def getrgb(x): 

1358 def applyfun(x): 

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

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

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

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

1363 

1364 # Result arrays 

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

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

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

1368 

1369 # Check valid hex colors 

1370 valid = validhex(hex_) 

1371 if not len(valid) == 0: 

1372 # Decode valid hex strings 

1373 rgb = getrgb(hex_[valid]) 

1374 r[valid] = rgb[0] 

1375 g[valid] = rgb[1] 

1376 b[valid] = rgb[2] 

1377 

1378 return [r, g, b] 

1379 

1380 

1381 # ------------------------------------------------------------------- 

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

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

1384 """Convert RGB to HLS 

1385 

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

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

1388 

1389 Args: 

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

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

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

1393 

1394 Returns: 

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

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

1397 the inputs. 

1398 """ 

1399 

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

1401 

1402 # Checking input 

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

1404 

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

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

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

1408 

1409 def gethls(x): 

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

1411 

1412 mn = np.min(x) 

1413 mx = np.max(x) 

1414 

1415 # If minimum equals maximum we know the solution already 

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

1417 

1418 # Else do the calculations 

1419 l = (mn + mx) / 2. 

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

1421 

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

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

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

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

1426 

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

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

1429 

1430 return [h, l, s] 

1431 

1432 

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

1434 

1435 

1436 # ------------------------------------------------------------------- 

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

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

1439 """Convert HLS to RGB 

1440 

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

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

1443 

1444 Args: 

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

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

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

1448 

1449 Returns: 

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

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

1452 the inputs. 

1453 """ 

1454 

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

1456 

1457 # Checking input 

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

1459 

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

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

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

1463 

1464 def getrgb(x): 

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

1466 

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

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

1469 

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

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

1472 p1 = 2 * x[1] - p2 

1473 

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

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

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

1477 

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

1479 

1480 # ------------------------------------------------------------------- 

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

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

1483 """Convert RGB to HSV 

1484 

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

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

1487 

1488 Args: 

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

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

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

1492 

1493 Returns: 

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

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

1496 the inputs. 

1497 """ 

1498 

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

1500 

1501 # Checking input 

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

1503 

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

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

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

1507 

1508 def gethsv(x): 

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

1510 

1511 mn = np.min(x) 

1512 mx = np.max(x) 

1513 

1514 # If minimum equals maximum we know the solution already 

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

1516 

1517 # Else calculate new dimensions 

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

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

1520 

1521 # Returning [h, s, v] 

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

1523 

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

1525 

1526 

1527 # ------------------------------------------------------------------- 

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

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

1530 """Convert HSV to RGB 

1531 

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

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

1534 

1535 Args: 

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

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

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

1539 

1540 Returns: 

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

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

1543 the inputs. 

1544 """ 

1545 

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

1547 

1548 # Checking input 

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

1550 

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

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

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

1554 

1555 def getrgb(x): 

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

1557 

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

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

1560 f = h - i 

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

1562 

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

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

1565 

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

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

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

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

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

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

1572 

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

1574 

1575 

1576# ------------------------------------------------------------------- 

1577# Color object base class 

1578# will be extended by the different color classes. 

1579# ------------------------------------------------------------------- 

1580class colorobject: 

1581 """Superclass for All Color Objects 

1582 

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

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

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

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

1587 

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

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

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

1591 """ 

1592 

1593 import numpy as np 

1594 

1595 # Allowed/defined color spaces 

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

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

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

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

1600 

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

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

1603 # values. 

1604 ALPHA = None 

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

1606 

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

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

1609 

1610 # Standard representation of colorobject objects. 

1611 def __repr__(self, digits = 2): 

1612 """Color Object Standard Representation 

1613 

1614 Standard representation of the color object; shows the values 

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

1616 

1617 Args: 

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

1619 

1620 Returns: 

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

1622 """ 

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

1624 

1625 from .colorlib import hexcols 

1626 import numpy as np 

1627 

1628 # Sorting the dimensions 

1629 from re import match 

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

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

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

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

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

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

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

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

1638 

1639 # Number of colors 

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

1641 

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

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

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

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

1646 

1647 # Start creating the string: 

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

1649 

1650 # Show header 

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

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

1653 

1654 # Show data 

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

1656 # nan-replacement beforehand. 

1657 if isinstance(self, hexcols): 

1658 data = {} 

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

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

1661 for n in range(0, ncol): 

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

1663 if x is None: 

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

1665 else: 

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

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

1668 fmt = "{:<10s}" 

1669 else: 

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

1671 data = self._data_ 

1672 

1673 # Print object content 

1674 count = 0 

1675 for n in range(0, ncol): 

1676 if (n % 10) == 0: 

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

1678 else: 

1679 tmp = " " 

1680 for d in dims: 

1681 # Special handling for alpha 

1682 if d == "alpha": 

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

1684 tmp += " ---" 

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

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

1687 tmp += " ---" 

1688 elif isinstance(self, hexcols): 

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

1690 else: 

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

1692 else: 

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

1694 tmp += " ---" 

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

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

1697 else: 

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

1699 

1700 count += 1 

1701 res.append(tmp) 

1702 

1703 if count >= 30 and ncol > 40: 

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

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

1706 break 

1707 

1708 return "\n".join(res) 

1709 

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

1711 """Magic Method 

1712 

1713 Default call method of all color objects. Always returns 

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

1715 

1716 Args: 

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

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

1719 

1720 Returns: 

1721 list: Returns a list of hex colors. 

1722 """ 

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

1724 

1725 def __iter__(self): 

1726 self.n = -1 

1727 return self 

1728 

1729 def __next__(self): 

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

1731 self.n += 1 

1732 res = self[self.n] 

1733 return res 

1734 else: 

1735 raise StopIteration 

1736 

1737 def __getitem__(self, key): 

1738 if not isinstance(key, int): 

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

1740 

1741 from copy import deepcopy 

1742 from numpy import array, newaxis 

1743 res = deepcopy(self) 

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

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

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

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

1748 

1749 return res 

1750 

1751 

1752 def get_whitepoint(self): 

1753 """Get White Point 

1754 

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

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

1757 point is used. 

1758 

1759 Returns: 

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

1761 for the three dimensions. 

1762 

1763 Example: 

1764 

1765 >>> from colorspace import hexcols 

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

1767 >>> c.get_whitepoint() 

1768 """ 

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

1770 

1771 def set_whitepoint(self, **kwargs): 

1772 """Set White Point 

1773 

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

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

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

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

1778 

1779 Args: 

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

1781 each of which must be float: White specification for 

1782 dimension `X`/`Y`/`Z`. 

1783 

1784 Example: 

1785 

1786 >>> from colorspace import hexcols 

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

1788 >>> c.set_whitepoint(X = 100., Y = 100., Z = 101.) 

1789 >>> c.get_whitepoint() 

1790 

1791 Raises: 

1792 ValueError: If named argument is not one of `X`, `Y`, `Z`. 

1793 """ 

1794 for key,arg in kwargs.items(): 

1795 if key == "X": self.WHITEX = float(arg) 

1796 elif key == "Y": self.WHITEY = float(arg) 

1797 elif key == "Z": self.WHITEZ = float(arg) 

1798 else: 

1799 raise ValueError(f"error in .set_whitepoint: " + \ 

1800 "argument \"{key}\" not recognized.") 

1801 

1802 

1803 def _check_if_allowed_(self, x): 

1804 """Check for Valid Transformation 

1805 

1806 Helper function checking if the transformation of the current 

1807 object into another color space is allowed or not. 

1808 An exception will be thrown if the transformation is not possible. 

1809 

1810 Args: 

1811 x (str): Name of the target color space. 

1812 

1813 Returns: 

1814 No return, raises an Exception if the transformation is invalid. 

1815 """ 

1816 if not x in self.ALLOWED: 

1817 raise Exception(f"transformation from {self.__class__.__name__}" + \ 

1818 f" to \"{x}\" is unknown (not implemented). " + \ 

1819 f"The following are allowed: {', '.join(self.ALLOWED)}") 

1820 return 

1821 

1822 

1823 def _transform_via_path_(self, via, fixup): 

1824 """Transform Colors along Path 

1825 

1826 Helper function to transform a colorobject into a new color 

1827 space. Calls the :py:func:`to` method one or multiple times along 'a path' 

1828 as specified by `via`. 

1829 

1830 Returns: 

1831 No return, converts the current color space object (see method :py:func:`to`). 

1832 

1833 Args: 

1834 via (list of str): The path via which the current color object 

1835 should be transformed. For example: A :py:class:`hexcols` 

1836 object can be transformed into CIEXYZ by specifying 

1837 `via = ["sRGB", "RGB", "CIEXYZ"]`. 

1838 fixup (bool): Whether or not to correct invalid rgb values outside 

1839 `[0., 1.]` if necessary 

1840 """ 

1841 for v in via: self.to(v, fixup = fixup) 

1842 

1843 def _colorobject_check_input_arrays_(self, **kwargs): 

1844 """Colorobject Check User Input 

1845 

1846 Checks if all inputs in `**kwargs` are of type `numpy.ndarray` OR lists 

1847 (will be converted to `numpy.ndarray`s) and that all are of the same length. 

1848 If not, the script will throw an exception. 

1849 

1850 If `alpha` is given it is handled in a special way. If `alpha = None` 

1851 it will simply be dropped (no alpha channel specified), else it is 

1852 handled like the rest and has to fulfill the requirements all the 

1853 other dimensions have to (length and type). 

1854 

1855 Args: 

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

1857 

1858 Returns: 

1859 bool: Returns `True` if all checks where fine, throws an exception 

1860 if the inputs do not fulfil the requirements. 

1861 """ 

1862 

1863 from numpy import asarray, float64 

1864 

1865 # Message will be dropped if problems occur 

1866 msg = f"Problem while checking inputs \"{', '.join(kwargs.keys())}\" " + \ 

1867 f"to class \"{self.__class__.__name__}\"." 

1868 

1869 res = {} 

1870 lengths = [] 

1871 keys_to_check = [] 

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

1873 # No alpha provided, simply proceed 

1874 if key == "alpha" and val is None: continue 

1875 

1876 keys_to_check.append(key) 

1877 

1878 # If is list: convert to ndarray no matter how long the element is 

1879 if isinstance(val, float) or isinstance(val, int): 

1880 val = np.asarray([val]) 

1881 elif isinstance(val,list): 

1882 try: 

1883 val = np.asarray(val) 

1884 except Exception as e: 

1885 raise Exception(e) 

1886 

1887 

1888 # For alpha, R, G, and B: check range 

1889 if isinstance(self, RGB) or isinstance(self, sRGB): 

1890 if np.max(val) > 1. or np.max(val) < 0.: 

1891 raise ValueError("wrong values specified for " + \ 

1892 f"dimension {key} in {self.__class__.__name__}: " + \ 

1893 "values have to lie within [0., 1.]") 

1894 

1895 # Check object type 

1896 from numpy import asarray 

1897 try: 

1898 val = asarray(val) 

1899 except Exception as e: 

1900 raise ValueError(f"input {key} to {self.__class__.__name__}" + \ 

1901 f" could not have been converted to `numpy.ndarray`: {str(e)}") 

1902 

1903 # Else append length and proceed 

1904 lengths.append(len(val)) 

1905 

1906 # Append to result vector 

1907 if isinstance(val, int) or isinstance(val, float): val = [val] 

1908 res[key] = val if key == "hex_" else asarray(val, float64) 

1909 

1910 # Check if all do have the same length 

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

1912 msg += " Arguments of different lengths: {:s}".format( 

1913 ", ".join(["{:s} = {:d}".format(keys_to_check[i], lengths[i]) \ 

1914 for i in range(0, len(keys_to_check))])) 

1915 raise ValueError(msg) 

1916 

1917 return res 

1918 

1919 

1920 def hasalpha(self): 

1921 """Check for Alpha Channel 

1922 

1923 Helper method to check if the current color object has 

1924 an alpha channel or not. 

1925 

1926 Examples: 

1927 

1928 >>> from colorspace import sRGB 

1929 >>> x1 = sRGB(R = 0.5, G = 0.1, B = 0.3) 

1930 >>> x1 

1931 >>> #: 

1932 >>> x2 = sRGB(R = 0.5, G = 0.1, B = 0.3, alpha = 0.5) 

1933 >>> x2 

1934 >>> #: Checking both color objects for alpha channel 

1935 >>> [x1.hasalpha(), x2.hasalpha()] 

1936 

1937 Returns: 

1938 bool: `True` if alpha values are present, `False` if not. 

1939 """ 

1940 if not "alpha" in self._data_.keys(): 

1941 return False 

1942 elif self._data_["alpha"] is None: 

1943 return False 

1944 else: 

1945 return True 

1946 

1947 

1948 def dropalpha(self): 

1949 """Remove Alpha Channel 

1950 

1951 Remove alpha channel from the color object, if defined 

1952 (see :py:method:`hasalpha`). Works for all `colorobject`s. 

1953 

1954 Examples: 

1955 

1956 >>> from colorspace.colorlib import HCL, sRGB, HSV 

1957 >>> # Example using HCL colors 

1958 >>> cols = HCL([0, 40, 80], 

1959 >>> [30, 60, 80], 

1960 >>> [85, 60, 35], 

1961 >>> alpha = [1.0, 0.5, 0.1]) 

1962 >>> cols # with alpha channel 

1963 >>> #: 

1964 >>> cols.dropalpha() 

1965 >>> cols # alpha channel removed 

1966 >>> 

1967 >>> #: No effect if there is no alpha channel 

1968 >>> cols.dropalpha() 

1969 >>> cols 

1970 >>> 

1971 >>> #: Example using sRGB colors 

1972 >>> cols = sRGB([0.01, 0.89, 0.56], 

1973 >>> [0.25, 0.89, 0.02], 

1974 >>> [0.65, 0.89, 0.23], 

1975 >>> alpha = [1.0, 0.5, 0.1]) 

1976 >>> cols # with alpha channel 

1977 >>> #: 

1978 >>> cols.dropalpha() 

1979 >>> cols # alpha channel removed 

1980 >>> 

1981 >>> #: Example using HSV colors 

1982 >>> cols = HSV([218, 0, 336], 

1983 >>> [1, 0, 1], 

1984 >>> [0.65, 0.89, 0.56], 

1985 >>> alpha = [1.0, 0.5, 0.1]) 

1986 >>> cols # with alpha channel 

1987 >>> #: 

1988 >>> cols.dropalpha() 

1989 >>> cols # alpha channel removed 

1990 

1991 

1992 """ 

1993 if self.hasalpha(): 

1994 del self._data_["alpha"] 

1995 

1996 

1997 def specplot(self, **kwargs): 

1998 """Color Spectrum Plot 

1999 

2000 Visualization of the spectrum of this color object. 

2001 Internally calls :py:func:`specplot <colorspace.specplot.specplot>`, 

2002 additional arguments to this main function can be forwarded via the 

2003 `**kwargs` argument. 

2004 

2005 Args: 

2006 **kwargs: Additional named arguments forwarded to 

2007 :py:func:`specplot <colorspace.specplot.specplot>`. 

2008 

2009 Return: 

2010 Returns what :py:func:`colorspace.specplot.specplot` returns. 

2011 

2012 Example: 

2013 

2014 >>> # Example using HCL colors 

2015 >>> from colorspace import HCL, hexcols 

2016 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2017 >>> C = [ 44, 49, 55, 59, 50], 

2018 >>> L = [ 49, 61, 72, 82, 90]) 

2019 >>> cols.specplot(figsize = (8, 4)); 

2020 >>> 

2021 >>> #: Example using hex colors 

2022 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2023 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2024 >>> cols.specplot(rgb = True, hcl = True, palette = True) 

2025 

2026 """ 

2027 from copy import copy 

2028 cols = copy(self) 

2029 cols.to("hex") 

2030 

2031 from .specplot import specplot 

2032 return specplot(cols.colors(), **kwargs) 

2033 

2034 

2035 def swatchplot(self, **kwargs): 

2036 """Palette Swatch Plot 

2037 

2038 Visualization the color palette of this color object. 

2039 Internally calls :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`, 

2040 additional arguments to this main function can be forwarded via the 

2041 `**kwargs` argument. 

2042 

2043 Args: 

2044 **kwargs: Additional named arguments forwarded to 

2045 :py:func:`swatchplot <colorspace.swatchplot.swatchplot>`. 

2046 

2047 Return: 

2048 Returns what :py:func:`colorspace.swatchplot.swatchplot` returns. 

2049 

2050 Example: 

2051 

2052 >>> # Example using HCL colors 

2053 >>> from colorspace import HCL, hexcols 

2054 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2055 >>> C = [ 44, 49, 55, 59, 50], 

2056 >>> L = [ 49, 61, 72, 82, 90]) 

2057 >>> cols.swatchplot(figsize = (8, 2)) 

2058 >>> 

2059 >>> #: Example using hex colors 

2060 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2061 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2062 >>> cols.swatchplot(figsize = (8, 3.5)); 

2063 """ 

2064 

2065 from .swatchplot import swatchplot 

2066 if "show_names" in kwargs.keys(): 

2067 del kwargs["show_names"] 

2068 return swatchplot(pals = self.colors(), show_names = False, **kwargs) 

2069 

2070 

2071 def hclplot(self, **kwargs): 

2072 """Palette Plot in HCL Space 

2073 

2074 Convenience method for calling :py:func:`hclplot <colorspace.hclplot.hclplot>` 

2075 on the current color object. Additional arguments can be forwarded via `**kwargs` 

2076 (see :py:func:`hclplot <colorspace.hclplot.hclplot>` for details). 

2077 

2078 Args: 

2079 **kwargs: Additional named arguments forwarded to 

2080 :py:func:`hclplot <colorspace.hclplot.hclplot>`. 

2081 

2082 Return: 

2083 Returns what :py:func:`colorspace.hclplot.hclplot` returns. 

2084 

2085 Example: 

2086 

2087 >>> # Example using HCL colors 

2088 >>> from colorspace import HCL, hexcols 

2089 >>> cols = HCL(H = [220, 196, 172, 148, 125], 

2090 >>> C = [ 44, 49, 55, 59, 50], 

2091 >>> L = [ 49, 61, 72, 82, 90]) 

2092 >>> cols.hclplot(); 

2093 >>> 

2094 >>> #: Example using hex colors 

2095 >>> cols = hexcols(["#0FCFC0", "#9CDED6", "#D5EAE7", 

2096 >>> "#F1F1F1", "#F3E1EB", "#F6C4E1", "#F79CD4"]) 

2097 >>> cols.hclplot(figsize = (8, 3.5)); 

2098 """ 

2099 

2100 from .hclplot import hclplot 

2101 return hclplot(x = self.colors(), **kwargs) 

2102 

2103 def colors(self, fixup = True, rev = False): 

2104 """Extract Hex Colors 

2105 

2106 Convers the current object into an object of class :py:class:`hexcols` 

2107 and extracts the hex colors as list of str. 

2108 

2109 If the object contains alpha values, the alpha level is added to the 

2110 hex string if and only if alpha is not equal to `1.0`. 

2111 

2112 Args: 

2113 fixup (bool): Whether or not to correct rgb values outside the 

2114 defined range of `[0., 1.]`, defaults to `True`. 

2115 rev (bool): Should the color palette be reversed? Defaults to `False`. 

2116 

2117 Returns: 

2118 list: Returns a list of hex color strings. 

2119 

2120 Example: 

2121 

2122 >>> from colorspace import HCL, sRGB, HSV 

2123 >>> # Example using HCL colors 

2124 >>> cols = HCL([0, 40, 80], 

2125 >>> [30, 60, 80], 

2126 >>> [85, 60, 35]) 

2127 >>> cols.colors() 

2128 >>> 

2129 >>> #: Example using sRGB colors 

2130 >>> cols = sRGB([0.01, 0.89, 0.56], 

2131 >>> [0.25, 0.89, 0.02], 

2132 >>> [0.65, 0.89, 0.23]) 

2133 >>> cols.colors() 

2134 >>> 

2135 >>> #: Example using HSV colors 

2136 >>> cols = HSV([218, 0, 336], 

2137 >>> [1, 0, 1], 

2138 >>> [0.65, 0.89, 0.56]) 

2139 >>> cols.colors() 

2140 

2141 """ 

2142 

2143 from copy import copy 

2144 from numpy import ndarray, round 

2145 

2146 x = copy(self) 

2147 x.to("hex", fixup = fixup) 

2148 if x.hasalpha(): 

2149 res = x.get("hex_").tolist() 

2150 # Appending alpha if alpha < 1.0 

2151 for i in range(0, len(res)): 

2152 if self._data_["alpha"][i] < 1.0: 

2153 tmp = int(round(self._data_["alpha"][i] * 255. + 0.0001)) 

2154 res[i] += f"{tmp:02X}" 

2155 # Return hex with alpha 

2156 colors = res 

2157 else: 

2158 colors = x.get("hex_") 

2159 

2160 if rev: 

2161 from numpy import flip 

2162 colors = flip(colors) 

2163 

2164 return colors.tolist() if isinstance(colors, ndarray) else colors 

2165 

2166 

2167 def get(self, dimname = None): 

2168 """Extracting Color Coordinates 

2169 

2170 Allows to extract the current values of one or multiple dimensions 

2171 for all colors of this color object. The names of the coordinates varies 

2172 between different color spaces. 

2173 

2174 Args: 

2175 dimname (None, str): If `None` (default) values of all coordinates 

2176 of the current color object are returned. A specific coordinate 

2177 can be specified if needed. 

2178 

2179 Returns: 

2180 Returns a `numpy.ndarray` if coordinates of one specific dimension are 

2181 requested, else a `dict` of arrays. 

2182 

2183 Example: 

2184 

2185 >>> from colorspace import HCL, sRGB, hexcols 

2186 >>> # Example using HCL color object with alpha channel 

2187 >>> cols = HCL([260, 80, 30], [80, 0, 80], [30, 90, 30], [1, 0.6, 0.2]) 

2188 >>> cols.get("H") # Specific dimension 

2189 >>> #: 

2190 >>> cols.get("alpha") # Alpha (if existing) 

2191 >>> #: 

2192 >>> cols.get() # All dimensions 

2193 >>> 

2194 >>> #: Convert colors to sRGB 

2195 >>> cols.to("sRGB") 

2196 >>> cols.get("R") # Specific dimension 

2197 >>> #: 

2198 >>> cols.get() # All dimensions 

2199 >>> 

2200 >>> #: Convert to hexcols 

2201 >>> cols.to("hex") 

2202 >>> cols.get("hex_") 

2203 

2204 Raises: 

2205 TypeError: If argument `dimname` is neither None or str. 

2206 ValueError: If the dimension specified on `dimnames` does not exist. 

2207 """ 

2208 

2209 # Return all coordinates 

2210 from copy import copy 

2211 if dimname is None: 

2212 return copy(self._data_) 

2213 # No string? 

2214 elif not isinstance(dimname, str): 

2215 raise TypeError("argument `dimname` must be None or str") 

2216 # Else only the requested dimension 

2217 elif not dimname in self._data_.keys(): 

2218 # Alpha channel never defined, return None (which 

2219 # is a valid value for "no alpha") 

2220 if dimname == "alpha": 

2221 return None 

2222 else: 

2223 raise ValueError(f"{self.__class__.__name__} has no dimension {dimname}") 

2224 

2225 return copy(self._data_[dimname]) 

2226 

2227 

2228 def set(self, **kwargs): 

2229 """Set Coordinates/Manipulate Colors 

2230 

2231 Allows to manipulate current colors. The named input arguments 

2232 have to fulfil a specific set or requirements. If not, the function 

2233 raises exceptions. The requirements: 

2234 

2235 * Dimension has to exist 

2236 * New data/values must be of same length and type as the existing ones 

2237 

2238 Args: 

2239 **kwargs: Named arguments. The key is the name of the dimension to 

2240 be changed, the value an object which fulfills the requirements 

2241 (see description of this method) 

2242 

2243 Raises: 

2244 ValueError: If the dimension does not exist. 

2245 ValueError: If the new data can't be converted into 

2246 `numpy.array` (is done automatically if needed). 

2247 ValueError: If new data has wrong length (does not match the 

2248 number of colors/length of current values). 

2249 

2250 Example: 

2251 

2252 >>> # Example shown for HCL colors, works the same 

2253 >>> # for all other color objects (sRGB, hexcols, ...) 

2254 >>> from colorspace import HCL 

2255 >>> cols = HCL([260, 80, 30], [80, 0, 80], [30, 90, 30]) 

2256 >>> cols 

2257 >>> #: 

2258 >>> cols.set(H = [150, 150, 30]) 

2259 >>> cols 

2260 """ 

2261 # Looping over inputs 

2262 from numpy import asarray, ndarray 

2263 for key,vals in kwargs.items(): 

2264 key.upper() 

2265 

2266 # Check if the key provided by the user is a valid dimension 

2267 # of the current object. 

2268 if not key in self._data_.keys(): 

2269 raise ValueError(f"{self.__class__.__name__} has no dimension {key}") 

2270 

2271 # In case the input is a single int/float or a list; try 

2272 # to convert the input into a numpy.array using the same 

2273 # dtype as the existing dimension (loaded via self.get(key)). 

2274 if isinstance(vals, (list, int, float)): 

2275 if isinstance(vals, (int, float)): vals = [vals] 

2276 t = type(self.get(key)[0]) # Current type (get current dimension) 

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 

2283 # New values do have to have the same length as the old ones, 

2284 n = len(self.get(key)) 

2285 t = type(self.get(key)[0]) 

2286 try: 

2287 vals = np.asarray(vals, dtype = t) 

2288 except Exception as e: 

2289 raise ValueError(f"problems converting new data to {t} " + \ 

2290 f" in {self.__class__.__name__}: {str(e)}") 

2291 if not vals.size == n: 

2292 raise ValueError("number of values to be stored on the object " + \ 

2293 f"{self.__class__.__name__} have to match the current dimension") 

2294 

2295 self._data_[key] = vals 

2296 

2297 def length(self): 

2298 """Get Number of Colors 

2299 

2300 Returns the number of colors defined in this color object. 

2301 Note that `len(<object>)` works as well. 

2302 

2303 Returns: 

2304 int: Number of colors. 

2305 

2306 Examples: 

2307 

2308 >>> from colorspace import sRGB, hexcols, HCL 

2309 >>> # Examples for three different color objects 

2310 >>> x1 = sRGB([1, 0], [1, 1], [0, 0]) 

2311 >>> [x1.length(), len(x1)] 

2312 >>> #: 

2313 >>> x2 = hexcols(["#ff0000", "#00ff00", "#0000ff"]) 

2314 >>> [x2.length(), len(x2)] 

2315 >>> #: 

2316 >>> x3 = HCL([275, 314, 353, 31, 70], 

2317 >>> [70, 85, 102, 86, 45], 

2318 >>> [25, 40, 55, 70, 85]) 

2319 >>> [x3.length(), len(x3)] 

2320 

2321 """ 

2322 return max([0 if self._data_[x] is None else len(self._data_[x]) for x in self._data_.keys()]) 

2323 

2324 def __len__(self): 

2325 return self.length() 

2326 

2327 

2328 # Currently not used but implemented as fallback for the future 

2329 def _cannot(self, from_, to): 

2330 """Error: Conversion not Possible 

2331 

2332 Helper function used to raise an exception as a specific 

2333 transformation is not possible by definition. 

2334 

2335 Args: 

2336 from_ (str): Name of the current color space. 

2337 to (str): Name of the target color space. 

2338 

2339 Raises: 

2340 Exception: Always, that is the intent of this method. 

2341 """ 

2342 raise Exception(f"cannot convert class \"{from_}\" to \"{to}\"") 

2343 

2344 def _ambiguous(self, from_, to): 

2345 """Error: Conversion Ambiguous 

2346 

2347 Helper function used to raise an exception as a specific 

2348 transformation is ambiguous and therefore not possible by definition. 

2349 

2350 Args: 

2351 from_ (str): Name of the current color space. 

2352 to (str): Name of the target color space. 

2353 

2354 Raises: 

2355 Exception: Always, that is the intent of this method. 

2356 """ 

2357 raise Exception(f"conversion not possible, ambiguous conversion from \"{from_}\" to \"{to}\"") 

2358 

2359 

2360# ------------------------------------------------------------------- 

2361# PolarLUV or HCL object 

2362# ------------------------------------------------------------------- 

2363class polarLUV(colorobject): 

2364 """Create polarLUV (HCL) Color Object 

2365 

2366 Creates a color object in the polar representation of the :py:class:`CIELUV` 

2367 color space, also known as the Hue-Chroma-Luminance (HCL) color space. 

2368 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2369 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2370 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2371 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2372 

2373 Args: 

2374 H (int, float, list, numpy.array): 

2375 Numeric value(s) for hue dimension (`[-360., 360.]`). 

2376 C (int, float, list, numpy.array): 

2377 Numeric value(s) for chroma dimension (`[0., 100.+]`). 

2378 L (int, float, list, numpy.array): 

2379 Numeric value(s) for luminance dimension (`[0., 100.]`). 

2380 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2381 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2382 opacity. If `None` (default) no transparency is added. 

2383 

2384 Example: 

2385 

2386 >>> from colorspace import polarLUV, HCL 

2387 >>> # Constructing color object with one single color via float 

2388 >>> polarLUV(100., 30, 50.) 

2389 >>> #: polarLUV is the HCL color space, this 

2390 >>> # is equivalent to the command above. 

2391 >>> HCL(100., 30, 50.) 

2392 >>> #: Constructing object via lists 

2393 >>> HCL([100, 80], [30, 50], [30, 80]) 

2394 >>> #: Constructing object via numpy arrays 

2395 >>> from numpy import asarray 

2396 >>> HCL(asarray([100, 80]), asarray([30, 50]), asarray([30, 80])) 

2397 """ 

2398 

2399 def __init__(self, H, C, L, alpha = None): 

2400 

2401 # Checking inputs, save inputs on object 

2402 self._data_ = {} # Dict to store the colors/color dimensions 

2403 tmp = self._colorobject_check_input_arrays_(H = H, C = C, L = L, alpha = alpha) 

2404 for key,val in tmp.items(): self._data_[key] = val 

2405 # White spot definition (the default) 

2406 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2407 

2408 def to(self, to, fixup = True): 

2409 """Transform Color Space 

2410 

2411 Allows to transform the current object into a different color space, 

2412 if possible. Converting the colors of the current object into 

2413 another color space. After calling this method, the object 

2414 will be of a different class. 

2415 

2416 Args: 

2417 to (str): Name of the color space into which the colors should be 

2418 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2419 fixup (bool): Whether or not colors outside the defined rgb color space 

2420 should be corrected if necessary, defaults to `True`. 

2421 

2422 Examples: 

2423 

2424 >>> # HCL() identical to polarLUV() 

2425 >>> from colorspace import HCL 

2426 >>> x = HCL([275, 314, 353, 31, 70], 

2427 >>> [ 70, 85, 102, 86, 45], 

2428 >>> [ 25, 40, 55, 70, 85]) 

2429 >>> x 

2430 >>> #: 

2431 >>> type(x) 

2432 >>> #: Convert colors to sRGB 

2433 >>> x.to("sRGB") 

2434 >>> x 

2435 >>> #: 

2436 >>> type(x) 

2437 >>> #: Convert from sRGB to hex 

2438 >>> x.to("hex") 

2439 >>> x 

2440 >>> #: Convert back to HCL colors. 

2441 >>> # Round-off errors due to conversion to 'hex'. 

2442 >>> x.to("HCL") 

2443 >>> x 

2444 >>> #: Extracting hex colors (returns list of str) 

2445 >>> x.colors() 

2446 

2447 """ 

2448 self._check_if_allowed_(to) 

2449 from . import colorlib 

2450 clib = colorlib() 

2451 

2452 # Nothing to do (converted to itself) 

2453 if to in ["HCL", self.__class__.__name__]: 

2454 return 

2455 

2456 # This is the only transformation from polarLUV -> LUV 

2457 elif to == "CIELUV": 

2458 [L, U, V] = clib.polarLUV_to_LUV(self.get("L"), self.get("C"), self.get("H")) 

2459 self._data_ = {"L" : L, "U" : U, "V" : V, "alpha" : self.get("alpha")} 

2460 self.__class__ = CIELUV 

2461 

2462 # The rest are transformations along a path 

2463 elif to == "CIEXYZ": 

2464 via = ["CIELUV", to] 

2465 self._transform_via_path_(via, fixup = fixup) 

2466 

2467 elif to == "CIELAB": 

2468 via = ["CIELUV", "CIEXYZ", to] 

2469 self._transform_via_path_(via, fixup = fixup) 

2470 

2471 elif to == "RGB": 

2472 via = ["CIELUV", "CIEXYZ", to] 

2473 self._transform_via_path_(via, fixup = fixup) 

2474 

2475 elif to == "sRGB": 

2476 via = ["CIELUV", "CIEXYZ", to] 

2477 self._transform_via_path_(via, fixup = fixup) 

2478 

2479 elif to == "polarLAB": 

2480 via = ["CIELUV", "CIEXYZ", "CIELAB", to] 

2481 self._transform_via_path_(via, fixup = fixup) 

2482 

2483 elif to == "hex": 

2484 via = ["CIELUV", "CIEXYZ", "sRGB", to] 

2485 self._transform_via_path_(via, fixup = fixup) 

2486 

2487 elif to in ["HLS", "HSV"]: 

2488 self._ambiguous(self.__class__.__name__, to) 

2489 

2490 # Currently not used but implemented as fallback for the future 

2491 else: self._cannot(self.__class__.__name__, to) 

2492 

2493# polarLUV is HCL, make copy 

2494HCL = polarLUV 

2495 

2496 

2497# ------------------------------------------------------------------- 

2498# CIELUV color object 

2499# ------------------------------------------------------------------- 

2500class CIELUV(colorobject): 

2501 """Create CIELUV Color Object 

2502 

2503 Creates a color object in the CIELUV color space. 

2504 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2505 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2506 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2507 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2508 

2509 Args: 

2510 L (int, float, list, numpy.array): 

2511 Numeric value(s) for L dimension. 

2512 U (int, float, list, numpy.array): 

2513 Numeric value(s) for U dimension. 

2514 V (int, float, list, numpy.array): 

2515 Numeric value(s) for L dimension. 

2516 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2517 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2518 opacity. If `None` (default) no transparency is added. 

2519 

2520 Example: 

2521 

2522 >>> from colorspace import CIELUV 

2523 >>> # Constructing color object with one single color via float 

2524 >>> CIELUV(0, 10, 10) 

2525 >>> #: Constructing object via lists 

2526 >>> CIELUV([10, 30], [20, 80], [100, 40]) 

2527 >>> #: Constructing object via numpy arrays 

2528 >>> from numpy import asarray 

2529 >>> CIELUV(asarray([10, 30]), asarray([20, 80]), asarray([100, 40])) 

2530 

2531 """ 

2532 def __init__(self, L, U, V, alpha = None): 

2533 

2534 # checking inputs, save inputs on object 

2535 self._data_ = {} # Dict to store the colors/color dimensions 

2536 tmp = self._colorobject_check_input_arrays_(L = L, U = U, V = V, alpha = alpha) 

2537 for key,val in tmp.items(): self._data_[key] = val 

2538 # White spot definition (the default) 

2539 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2540 

2541 

2542 def to(self, to, fixup = True): 

2543 """Transform Color Space 

2544 

2545 Allows to transform the current object into a different color space, 

2546 if possible. Converting the colors of the current object into 

2547 another color space. After calling this method, the object 

2548 will be of a different class. 

2549 

2550 Args: 

2551 to (str): Name of the color space into which the colors should be 

2552 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2553 fixup (bool): Whether or not colors outside the defined rgb color space 

2554 should be corrected if necessary, defaults to `True`. 

2555 

2556 Examples: 

2557 

2558 >>> from colorspace import CIELUV 

2559 >>> x = CIELUV([ 25, 45, 65, 85], 

2560 >>> [ 6, 75, 90, 16], 

2561 >>> [-70, -50, 30, 42]) 

2562 >>> x 

2563 >>> #: 

2564 >>> type(x) 

2565 >>> #: Convert colors to sRGB 

2566 >>> x.to("sRGB") 

2567 >>> x 

2568 >>> #: 

2569 >>> type(x) 

2570 >>> #: Convert from sRGB to hex 

2571 >>> x.to("hex") 

2572 >>> x 

2573 >>> #: Convert back to CIELUV colors. 

2574 >>> # Round-off errors due to conversion to 'hex'. 

2575 >>> x.to("CIELUV") 

2576 >>> x 

2577 >>> #: Extracting hex colors (returns list of str) 

2578 >>> x.colors() 

2579 

2580 """ 

2581 self._check_if_allowed_(to) 

2582 from . import colorlib 

2583 clib = colorlib() 

2584 

2585 # Nothing to do (converted to itself) 

2586 if to == self.__class__.__name__: 

2587 return 

2588 # Transformation from CIELUV -> CIEXYZ 

2589 elif to == "CIEXYZ": 

2590 [X, Y, Z] = clib.LUV_to_XYZ(self.get("L"), self.get("U"), self.get("V"), 

2591 self.WHITEX, self.WHITEY, self.WHITEZ) 

2592 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

2593 self.__class__ = CIEXYZ 

2594 

2595 # Transformation from CIELUV -> polarLUV (HCL) 

2596 elif to in ["HCL","polarLUV"]: 

2597 [L, C, H] = clib.LUV_to_polarLUV(self.get("L"), self.get("U"), self.get("V")) 

2598 self._data_ = {"L" : L, "C" : C, "H" : H, "alpha" : self.get("alpha")} 

2599 self.__class__ = polarLUV 

2600 

2601 # The rest are transformations along a path 

2602 elif to == "CIELAB": 

2603 via = ["CIEXYZ", to] 

2604 self._transform_via_path_(via, fixup = fixup) 

2605 

2606 elif to == "RGB": 

2607 via = ["CIEXYZ", to] 

2608 self._transform_via_path_(via, fixup = fixup) 

2609 

2610 elif to == "sRGB": 

2611 via = ["CIEXYZ", "RGB", to] 

2612 self._transform_via_path_(via, fixup = fixup) 

2613 

2614 elif to == "polarLAB": 

2615 via = ["CIEXYZ", "CIELAB", to] 

2616 self._transform_via_path_(via, fixup = fixup) 

2617 

2618 elif to == "hex": 

2619 via = ["CIEXYZ", "RGB", "sRGB", to] 

2620 self._transform_via_path_(via, fixup = fixup) 

2621 

2622 elif to in ["HLS", "HSV"]: 

2623 self._ambiguous(self.__class__.__name__, to) 

2624 

2625 else: self._cannot(self.__class__.__name__, to) 

2626 

2627# ------------------------------------------------------------------- 

2628# CIEXYZ color object 

2629# ------------------------------------------------------------------- 

2630class CIEXYZ(colorobject): 

2631 """Create CIEXYZ Color Object 

2632 

2633 Creates a color object in the CIEXYZ color space. 

2634 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

2635 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

2636 :py:class:`polarLAB`, and :py:class:`hexcols`. 

2637 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

2638 

2639 Args: 

2640 X (int, float, list, numpy.array): 

2641 Numeric value(s) for X dimension. 

2642 Y (int, float, list, numpy.array): 

2643 Numeric value(s) for Y dimension. 

2644 Z (int, float, list, numpy.array): 

2645 Numeric value(s) for Z dimension. 

2646 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2647 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2648 opacity. If `None` (default) no transparency is added. 

2649 

2650 Example: 

2651 

2652 >>> from colorspace import CIEXYZ 

2653 >>> # Constructing color object with one single color via float 

2654 >>> CIEXYZ(80, 30, 10) 

2655 >>> #: Constructing object via lists 

2656 >>> CIEXYZ([10, 0], [20, 80], [40, 40]) 

2657 >>> #: Constructing object via numpy arrays 

2658 >>> from numpy import asarray 

2659 >>> CIEXYZ(asarray([10, 0]), asarray([20, 80]), asarray([40, 40])) 

2660 

2661 """ 

2662 def __init__(self, X, Y, Z, alpha = None): 

2663 

2664 # checking inputs, save inputs on object 

2665 self._data_ = {} # Dict to store the colors/color dimensions 

2666 tmp = self._colorobject_check_input_arrays_(X = X, Y = Y, Z = Z, alpha = alpha) 

2667 for key,val in tmp.items(): self._data_[key] = val 

2668 # White spot definition (the default) 

2669 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2670 

2671 

2672 def to(self, to, fixup = True): 

2673 """Transform Color Space 

2674 

2675 Allows to transform the current object into a different color space, 

2676 if possible. Converting the colors of the current object into 

2677 another color space. After calling this method, the object 

2678 will be of a different class. 

2679 

2680 Args: 

2681 to (str): Name of the color space into which the colors should be 

2682 converted (e.g., `"CIELUV"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2683 fixup (bool): Whether or not colors outside the defined rgb color space 

2684 should be corrected if necessary, defaults to `True`. 

2685 

2686 Examples: 

2687 

2688 >>> from colorspace import CIEXYZ 

2689 >>> x = CIEXYZ([ 8.5, 27.8, 46.2, 62.1], 

2690 >>> [ 4.4, 14.5, 34.1, 65.9], 

2691 >>> [27.2, 31.9, 17.2, 40.0]) 

2692 >>> x 

2693 >>> #: 

2694 >>> type(x) 

2695 >>> #: Convert colors to sRGB 

2696 >>> x.to("sRGB") 

2697 >>> x 

2698 >>> #: 

2699 >>> type(x) 

2700 >>> #: Convert from sRGB to hex 

2701 >>> x.to("hex") 

2702 >>> x 

2703 >>> #: Convert back to CIEXYZ colors. 

2704 >>> # Round-off errors due to conversion to 'hex'. 

2705 >>> x.to("CIEXYZ") 

2706 >>> x 

2707 >>> #: Extracting hex colors (returns list of str) 

2708 >>> x.colors() 

2709 

2710 """ 

2711 self._check_if_allowed_(to) 

2712 from . import colorlib 

2713 clib = colorlib() 

2714 

2715 # Nothing to do (converted to itself) 

2716 if to == self.__class__.__name__: 

2717 return 

2718 

2719 # Transformation from CIEXYZ -> CIELUV 

2720 elif to == "CIELUV": 

2721 [L, U, V] = clib.XYZ_to_LUV(self.get("X"), self.get("Y"), self.get("Z"), 

2722 self.WHITEX, self.WHITEY, self.WHITEZ) 

2723 self._data_ = {"L" : L, "U" : U, "V" : V, "alpha" : self.get("alpha")} 

2724 self.__class__ = CIELUV 

2725 

2726 # Transformation from CIEXYZ -> CIELAB 

2727 elif to == "CIELAB": 

2728 [L, A, B] = clib.XYZ_to_LAB(self.get("X"), self.get("Y"), self.get("Z"), 

2729 self.WHITEX, self.WHITEY, self.WHITEZ) 

2730 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

2731 self.__class__ = CIELAB 

2732 

2733 # Transformation from CIEXYZ -> RGB 

2734 elif to == "RGB": 

2735 [R, G, B] = clib.XYZ_to_RGB(self.get("X"), self.get("Y"), self.get("Z"), 

2736 self.WHITEX, self.WHITEY, self.WHITEZ) 

2737 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2738 self.__class__ = RGB 

2739 

2740 # The rest are transformations along a path 

2741 elif to == "polarLAB": 

2742 via = ["CIELAB", to] 

2743 self._transform_via_path_(via, fixup = fixup) 

2744 

2745 elif to in ["HCL", "polarLUV"]: 

2746 via = ["CIELUV", to] 

2747 self._transform_via_path_(via, fixup = fixup) 

2748 

2749 elif to == "sRGB": 

2750 via = ["RGB", to] 

2751 self._transform_via_path_(via, fixup = fixup) 

2752 

2753 elif to == "hex": 

2754 via = ["RGB", "sRGB", to] 

2755 self._transform_via_path_(via, fixup = fixup) 

2756 

2757 elif to in ["HLS", "HSV"]: 

2758 self._ambiguous(self.__class__.__name__, to) 

2759 

2760 else: self._cannot(self.__class__.__name__, to) 

2761 

2762 

2763class RGB(colorobject): 

2764 """Create RGB Color Object 

2765 

2766 Allows conversions to: :py:class:`CIELAB`, :py:class:`CIELUV`, 

2767 :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, :py:class:`hexcols`. 

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

2769 

2770 Args: 

2771 R (int, float, list, numpy.array): 

2772 Numeric value(s) for red intensity (`[0., 1.]`). 

2773 G (int, float, list, numpy.array): 

2774 Numeric value(s) for green intensity (`[0., 1.]`). 

2775 B (int, float, list, numpy.array): 

2776 Numeric value(s) for blue intensity (`[0., 1.]`). 

2777 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2778 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2779 opacity. If `None` (default) no transparency is added. 

2780 

2781 Example: 

2782 

2783 >>> from colorspace import RGB 

2784 >>> # Constructing color object with one single color via float 

2785 >>> RGB(1., 0.3, 0.5) 

2786 >>> #: Constructing object via lists 

2787 >>> RGB([1., 0.8], [0.5, 0.5], [0.0, 0.2]) 

2788 >>> #: Constructing object via numpy arrays 

2789 >>> from numpy import asarray 

2790 >>> RGB(asarray([1., 0.8]), asarray([0.5, 0.5]), asarray([0.0, 0.2])) 

2791 

2792 """ 

2793 

2794 def __init__(self, R, G, B, alpha = None): 

2795 

2796 # checking inputs, save inputs on object 

2797 self._data_ = {} # Dict to store the colors/color dimensions 

2798 

2799 tmp = self._colorobject_check_input_arrays_(R = R, G = G, B = B, alpha = alpha) 

2800 for key,val in tmp.items(): self._data_[key] = val 

2801 # White spot definition (the default) 

2802 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2803 

2804 

2805 def to(self, to, fixup = True): 

2806 """Transform Color Space 

2807 

2808 Allows to transform the current object into a different color space, 

2809 if possible. Converting the colors of the current object into 

2810 another color space. After calling this method, the object 

2811 will be of a different class. 

2812 

2813 Args: 

2814 to (str): Name of the color space into which the colors should be 

2815 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2816 fixup (bool): Whether or not colors outside the defined rgb color space 

2817 should be corrected if necessary, defaults to `True`. 

2818 

2819 Examples: 

2820 

2821 >>> from colorspace import RGB 

2822 >>> x = RGB([0.070, 0.520, 0.887, 0.799], 

2823 >>> [0.012, 0.015, 0.198, 0.651], 

2824 >>> [0.283, 0.323, 0.138, 0.323]) 

2825 >>> x 

2826 >>> #: 

2827 >>> type(x) 

2828 >>> #: Convert colors to CIEXYZ 

2829 >>> x.to("CIELUV") 

2830 >>> x 

2831 >>> #: 

2832 >>> type(x) 

2833 >>> #: Convert from CIELUV to HCL 

2834 >>> x.to("HCL") 

2835 >>> x 

2836 >>> # Convert back to RGB 

2837 >>> x.to("RGB") 

2838 >>> x 

2839 >>> #: Extracting hex colors (returns list of str) 

2840 >>> x.colors() 

2841 

2842 """ 

2843 self._check_if_allowed_(to) 

2844 from . import colorlib 

2845 clib = colorlib() 

2846 

2847 # Nothing to do (converted to itself) 

2848 if to == self.__class__.__name__: 

2849 return 

2850 

2851 # Transform from RGB -> sRGB 

2852 elif to == "sRGB": 

2853 [R, G, B] = clib.RGB_to_sRGB(self.get("R"), self.get("G"), self.get("B"), 

2854 self.GAMMA) 

2855 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2856 self.__class__ = sRGB 

2857 

2858 # Transform from RGB -> CIEXYZ 

2859 elif to == "CIEXYZ": 

2860 [X, Y, Z] = clib.RGB_to_XYZ(self.get("R"), self.get("G"), self.get("B"), 

2861 self.WHITEX, self.WHITEY, self.WHITEZ) 

2862 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

2863 self.__class__ = CIEXYZ 

2864 

2865 # From RGB to HLS: take direct path (not via sRGB) 

2866 elif to in ["HLS"]: 

2867 [H, L, S] = clib.RGB_to_HLS(self.get("R"), self.get("G"), self.get("B")) 

2868 self._data_ = {"H" : H, "L" : L, "S" : S, "alpha" : self.get("alpha")} 

2869 self.__class__ = HLS 

2870 

2871 # From RGB to HSV: take direct path (not via sRGB) 

2872 elif to in ["HSV"]: 

2873 [H, S, V] = clib.RGB_to_HSV(self.get("R"), self.get("G"), self.get("B")) 

2874 self._data_ = {"H" : H, "S" : S, "V" : V, "alpha" : self.get("alpha")} 

2875 self.__class__ = HSV 

2876 

2877 # The rest are transformations along a path 

2878 elif to in ["hex"]: 

2879 via = ["sRGB", to] 

2880 self._transform_via_path_(via, fixup = fixup) 

2881 

2882 elif to in ["CIELUV", "CIELAB"]: 

2883 via = ["CIEXYZ", to] 

2884 self._transform_via_path_(via, fixup = fixup) 

2885 

2886 elif to in ["HCL","polarLUV"]: 

2887 via = ["CIEXYZ", "CIELUV", to] 

2888 self._transform_via_path_(via, fixup = fixup) 

2889 

2890 elif to == "polarLAB": 

2891 via = ["CIEXYZ", "CIELAB", to] 

2892 self._transform_via_path_(via, fixup = fixup) 

2893 

2894 else: self._cannot(self.__class__.__name__, to) 

2895 

2896 

2897class sRGB(colorobject): 

2898 """Create Standard RGB (sRGB) Color Object 

2899 

2900 Allows conversions to: :py:class:`CIELAB`, :py:class:`CIELUV`, 

2901 :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, :py:class:`RGB`, 

2902 :py:class:`hexcols`. :py:class:`polarLAB` and :py:class:`polarLUV`. 

2903 

2904 Args: 

2905 R (int, float, list, numpy.array): 

2906 Numeric value(s) for red intensity (`[0., 1.]`). 

2907 G (int, float, list, numpy.array): 

2908 Numeric value(s) for green intensity (`[0., 1.]`). 

2909 B (int, float, list, numpy.array): 

2910 Numeric value(s) for blue intensity (`[0., 1.]`). 

2911 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

2912 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

2913 opacity. If `None` (default) no transparency is added. 

2914 gamma (None, float): If `None` (default) the default gamma value is used. 

2915 Can be specified to overwrite the default. 

2916 

2917 Example: 

2918 

2919 >>> from colorspace import sRGB 

2920 >>> # Constructing color object with one single color via float 

2921 >>> sRGB(1., 0.3, 0.5) 

2922 >>> #: Constructing object via lists 

2923 >>> sRGB([1., 0.8], [0.5, 0.5], [0.0, 0.2]) 

2924 >>> #: Constructing object via numpy arrays 

2925 >>> from numpy import asarray 

2926 >>> sRGB(asarray([1., 0.8]), asarray([0.5, 0.5]), asarray([0.0, 0.2])) 

2927 

2928 """ 

2929 

2930 def __init__(self, R, G, B, alpha = None, gamma = None): 

2931 

2932 # checking inputs, save inputs on object 

2933 self._data_ = {} # Dict to store the colors/color dimensions 

2934 tmp = self._colorobject_check_input_arrays_(R = R, G = G, B = B, alpha = alpha) 

2935 for key,val in tmp.items(): self._data_[key] = val 

2936 

2937 # White spot definition (the default) 

2938 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

2939 

2940 if isinstance(gamma, float): self.GAMMA = gamma 

2941 

2942 

2943 def to(self, to, fixup = True): 

2944 """Transform Color Space 

2945 

2946 Allows to transform the current object into a different color space, 

2947 if possible. Converting the colors of the current object into 

2948 another color space. After calling this method, the object 

2949 will be of a different class. 

2950 

2951 Args: 

2952 to (str): Name of the color space into which the colors should be 

2953 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

2954 fixup (bool): Whether or not colors outside the defined rgb color space 

2955 should be corrected if necessary, defaults to `True`. 

2956 

2957 Examples: 

2958 

2959 >>> from colorspace import sRGB 

2960 >>> x = sRGB([0.294, 0.749, 0.949, 0.905], 

2961 >>> [0.113, 0.129, 0.482, 0.827], 

2962 >>> [0.568, 0.603, 0.407, 0.603]) 

2963 >>> x 

2964 >>> #: 

2965 >>> type(x) 

2966 >>> #: Convert colors to CIEXYZ 

2967 >>> x.to("CIELUV") 

2968 >>> x 

2969 >>> #: 

2970 >>> type(x) 

2971 >>> #: Convert from CIELUV to HCL 

2972 >>> x.to("HCL") 

2973 >>> x 

2974 >>> #: Convert back to Standard RGB colors. 

2975 >>> x.to("sRGB") 

2976 >>> x 

2977 >>> #: Extracting hex colors (returns list of str) 

2978 >>> x.colors() 

2979 

2980 """ 

2981 self._check_if_allowed_(to) 

2982 from . import colorlib 

2983 clib = colorlib() 

2984 

2985 # Nothing to do (converted to itself) 

2986 if to == self.__class__.__name__: 

2987 return 

2988 

2989 # Transformation sRGB -> RGB 

2990 elif to == "RGB": 

2991 [R, G, B] = clib.sRGB_to_RGB(self.get("R"), self.get("G"), self.get("B"), 

2992 gamma = self.GAMMA) 

2993 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

2994 self.__class__ = RGB 

2995 

2996 # Transformation sRGB -> hex 

2997 elif to == "hex": 

2998 hex_ = clib.sRGB_to_hex(self.get("R"), self.get("G"), self.get("B"), fixup) 

2999 self._data_ = {"hex_" : hex_, "alpha" : self.get("alpha")} 

3000 self.__class__ = hexcols 

3001 

3002 # Transform from RGB -> HLS 

3003 elif to == "HLS": 

3004 [H, L, S] = clib.sRGB_to_HLS(self.get("R"), self.get("G"), self.get("B")) 

3005 self._data_ = {"H" : H, "L" : L, "S" : S, "alpha" : self.get("alpha")} 

3006 self.__class__ = HLS 

3007 

3008 # Transform from RGB -> HSV 

3009 elif to == "HSV": 

3010 [H, S, V] = clib.sRGB_to_HSV(self.get("R"), self.get("G"), self.get("B")) 

3011 self._data_ = {"H" : H, "S" : S, "V" : V, "alpha" : self.get("alpha")} 

3012 self.__class__ = HSV 

3013 

3014 # The rest are transformations along a path 

3015 elif to in ["CIEXYZ"]: 

3016 via = ["RGB", to] 

3017 self._transform_via_path_(via, fixup = fixup) 

3018 

3019 elif to in ["CIELUV", "CIELAB"]: 

3020 via = ["RGB", "CIEXYZ", to] 

3021 self._transform_via_path_(via, fixup = fixup) 

3022 

3023 elif to in ["HCL","polarLUV"]: 

3024 via = ["RGB", "CIEXYZ", "CIELUV", to] 

3025 self._transform_via_path_(via, fixup = fixup) 

3026 

3027 elif to == "polarLAB": 

3028 via = ["RGB", "CIEXYZ", "CIELAB", to] 

3029 self._transform_via_path_(via, fixup = fixup) 

3030 

3031 else: self._cannot(self.__class__.__name__, to) 

3032 

3033 

3034class CIELAB(colorobject): 

3035 """Create CIELAB Color Object 

3036 

3037 Creates a color object in the CIELAB color space. 

3038 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

3039 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

3040 :py:class:`polarLAB`, and :py:class:`hexcols`. 

3041 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

3042 

3043 Args: 

3044 L (int, float, list, numpy.array): 

3045 Numeric value(s) for L dimension. 

3046 A (int, float, list, numpy.array): 

3047 Numeric value(s) for A dimension. 

3048 B (int, float, list, numpy.array): 

3049 Numeric value(s) for B dimension. 

3050 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3051 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3052 opacity. If `None` (default) no transparency is added. 

3053 

3054 Example: 

3055 

3056 >>> from colorspace import CIELAB 

3057 >>> # Constructing color object with one single color via float 

3058 >>> CIELAB(-30, 10, 10) 

3059 >>> #: Constructing object via lists 

3060 >>> CIELAB([-30, 30], [20, 80], [40, 40]) 

3061 >>> #: Constructing object via numpy arrays 

3062 >>> from numpy import asarray 

3063 >>> CIELAB(asarray([-30, 30]), asarray([20, 80]), asarray([40, 40])) 

3064 

3065 """ 

3066 

3067 def __init__(self, L, A, B, alpha = None): 

3068 

3069 # checking inputs, save inputs on object 

3070 self._data_ = {} # Dict to store the colors/color dimensions 

3071 tmp = self._colorobject_check_input_arrays_(L = L, A = A, B = B, alpha = alpha) 

3072 for key,val in tmp.items(): self._data_[key] = val 

3073 # White spot definition (the default) 

3074 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3075 

3076 

3077 def to(self, to, fixup = True): 

3078 """Transform Color Space 

3079 

3080 Allows to transform the current object into a different color space, 

3081 if possible. 

3082 

3083 Args: 

3084 to (str): Name of the color space into which the colors should be 

3085 converted (e.g., `CIEXYZ`, `HCL`, `hex`, `RGB`, ...) 

3086 fixup (bool): Whether or not colors outside the defined rgb color space 

3087 should be corrected if necessary, defaults to True. 

3088 

3089 Returns: 

3090 No return, converts the object into a new color space and modifies 

3091 the underlying object. After calling this method the object will 

3092 be of a different class. 

3093 """ 

3094 self._check_if_allowed_(to) 

3095 from . import colorlib 

3096 clib = colorlib() 

3097 

3098 # Nothing to do (converted to itself) 

3099 if to == self.__class__.__name__: 

3100 return 

3101 

3102 # Transformations CIELAB -> CIEXYZ 

3103 elif to == "CIEXYZ": 

3104 [X, Y, Z] = clib.LAB_to_XYZ(self.get("L"), self.get("A"), self.get("B"), 

3105 self.WHITEX, self.WHITEY, self.WHITEZ) 

3106 self._data_ = {"X" : X, "Y" : Y, "Z" : Z, "alpha" : self.get("alpha")} 

3107 self.__class__ = CIEXYZ 

3108 

3109 # Transformation CIELAB -> polarLAB 

3110 elif to == "polarLAB": 

3111 [L, A, B] = clib.LAB_to_polarLAB(self.get("L"), self.get("A"), self.get("B")) 

3112 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

3113 self.__class__ = polarLAB 

3114 

3115 # The rest are transformations along a path 

3116 elif to == "CIELUV": 

3117 via = ["CIEXYZ", to] 

3118 self._transform_via_path_(via, fixup = fixup) 

3119 

3120 elif to in ["HCL","polarLUV"]: 

3121 via = ["CIEXYZ", "CIELUV", to] 

3122 self._transform_via_path_(via, fixup = fixup) 

3123 

3124 elif to == "RGB": 

3125 via = ["CIEXYZ", to] 

3126 self._transform_via_path_(via, fixup = fixup) 

3127 

3128 elif to == "sRGB": 

3129 via = ["CIEXYZ", "RGB", to] 

3130 self._transform_via_path_(via, fixup = fixup) 

3131 

3132 elif to == "hex": 

3133 via = ["CIEXYZ", "RGB", "sRGB", to] 

3134 self._transform_via_path_(via, fixup = fixup) 

3135 

3136 elif to in ["HLS", "HSV"]: 

3137 self._ambiguous(self.__class__.__name__, to) 

3138 

3139 else: self._cannot(self.__class__.__name__, to) 

3140 

3141 

3142class polarLAB(colorobject): 

3143 """Create Polar LAB Color Object 

3144 

3145 Creates a color object in the polar representation of the 

3146 :py:class:`CIELAB` color space. 

3147 Can be converted to: :py:class:`CIEXYZ`, :py:class:`CIELUV`, 

3148 :py:class:`CIELAB`, :py:class:`RGB`, :py:class:`sRGB`, 

3149 :py:class:`polarLAB`, and :py:class:`hexcols`. 

3150 Not allowed (ambiguous) are transformations to :py:class:`HSV` and :py:class:`HLS`. 

3151 

3152 Args: 

3153 L (int, float, list, numpy.array): 

3154 Numeric value(s) for L dimension. 

3155 A (int, float, list, numpy.array): 

3156 Numeric value(s) for A dimension. 

3157 B (int, float, list, numpy.array): 

3158 Numeric value(s) for B dimension. 

3159 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3160 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3161 opacity. If `None` (default) no transparency is added. 

3162 

3163 Examples: 

3164 

3165 >>> from colorspace import polarLAB 

3166 >>> cols = polarLAB([50, 80, 30], [100, 120, 140], [40, 130, 300]) 

3167 >>> cols 

3168 >>> #: Convert to hex colors 

3169 >>> cols.to("hex") 

3170 >>> cols 

3171 

3172 """ 

3173 

3174 def __init__(self, L, A, B, alpha = None): 

3175 

3176 # checking inputs, save inputs on object 

3177 self._data_ = {} # Dict to store the colors/color dimensions 

3178 tmp = self._colorobject_check_input_arrays_(L = L, A = A, B = B, alpha = alpha) 

3179 for key,val in tmp.items(): self._data_[key] = val 

3180 # White spot definition (the default) 

3181 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3182 

3183 

3184 def to(self, to, fixup = True): 

3185 """Transform Color Space 

3186 

3187 Allows to transform the current object into a different color space, 

3188 if possible. Converting the colors of the current object into 

3189 another color space. After calling this method, the object 

3190 will be of a different class. 

3191 

3192 Args: 

3193 to (str): Name of the color space into which the colors should be 

3194 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3195 fixup (bool): Whether or not colors outside the defined rgb color space 

3196 should be corrected if necessary, defaults to `True`. 

3197 

3198 Examples: 

3199 

3200 >>> from colorspace import polarLAB 

3201 >>> x = polarLAB([ 25, 45, 65, 85], 

3202 >>> [ 72, 75, 54, 31], 

3203 >>> [310, 338, 36, 92]) 

3204 >>> x 

3205 >>> #: 

3206 >>> type(x) 

3207 >>> #: Convert colors to sRGB 

3208 >>> x.to("sRGB") 

3209 >>> x 

3210 >>> #: 

3211 >>> type(x) 

3212 >>> #: Convert from sRGB to hex 

3213 >>> x.to("hex") 

3214 >>> x 

3215 >>> #: Convert back to polarLAB colors. 

3216 >>> # Round-off errors due to conversion to 'hex'. 

3217 >>> x.to("polarLAB") 

3218 >>> x 

3219 >>> #: Extracting hex colors (returns list of str) 

3220 >>> x.colors() 

3221 

3222 """ 

3223 self._check_if_allowed_(to) 

3224 from . import colorlib 

3225 clib = colorlib() 

3226 

3227 # Nothing to do (converted to itself) 

3228 if to == self.__class__.__name__: 

3229 return 

3230 

3231 # The only transformation we need is from polarLAB -> LAB 

3232 elif to == "CIELAB": 

3233 [L, A, B] = clib.polarLAB_to_LAB(self.get("L"), self.get("A"), self.get("B")) 

3234 self._data_ = {"L" : L, "A" : A, "B" : B, "alpha" : self.get("alpha")} 

3235 self.__class__ = CIELAB 

3236 

3237 # The rest are transformationas along a path 

3238 elif to == "CIEXYZ": 

3239 via = ["CIELAB", to] 

3240 self._transform_via_path_(via, fixup = fixup) 

3241 

3242 elif to == "CIELUV": 

3243 via = ["CIELAB", "CIEXYZ", to] 

3244 self._transform_via_path_(via, fixup = fixup) 

3245 

3246 elif to in ["HCL", "polarLUV"]: 

3247 via = ["CIELAB", "CIEXYZ", "CIELUV", to] 

3248 self._transform_via_path_(via, fixup = fixup) 

3249 

3250 elif to == "RGB": 

3251 via = ["CIELAB", "CIEXYZ", to] 

3252 self._transform_via_path_(via, fixup = fixup) 

3253 

3254 elif to == "sRGB": 

3255 via = ["CIELAB", "CIEXYZ", "RGB", to] 

3256 self._transform_via_path_(via, fixup = fixup) 

3257 

3258 elif to == "hex": 

3259 via = ["CIELAB", "CIEXYZ", "RGB", "sRGB", to] 

3260 self._transform_via_path_(via, fixup = fixup) 

3261 

3262 elif to in ["HLS", "HSV"]: 

3263 self._ambiguous(self.__class__.__name__, to) 

3264 

3265 else: self._cannot(self.__class__.__name__, to) 

3266 

3267 

3268class HSV(colorobject): 

3269 """Create HSV Color Object 

3270 

3271 Creates a color object in the Hue-Saturation-Value (HSV) color space. 

3272 Can be converted to: :py:class:`RGB`, :py:class:`sRGB`, :py:class:`HLS`, 

3273 and :py:class:`hexcols`. 

3274 Not allowed (ambiguous) are transformations to :py:class:`CIEXYZ`, 

3275 :py:class:`CIELUV`, :py:class:`CIELAB`, :py:class:`polarLUV`, and 

3276 :py:class:`polarLAB`. 

3277 

3278 Args: 

3279 H (int, float, list, numpy.array): 

3280 Numeric value(s) for Hue dimension. 

3281 S (int, float, list, numpy.array): 

3282 Numeric value(s) for Saturation dimension. 

3283 V (int, float, list, numpy.array): 

3284 Numeric value(s) for Value dimension. 

3285 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3286 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3287 opacity. If `None` (default) no transparency is added. 

3288 

3289 Examples: 

3290 

3291 >>> #: Constructing object via numpy arrays 

3292 >>> from colorspace import HSV 

3293 >>> # Constructing color object with one single color via float 

3294 >>> HSV(150, 150, 10) 

3295 >>> #: Constructing object via lists 

3296 >>> HSV([150, 150, 10], [1.5, 0, 1.5], [0.1, 0.7, 0.1]) 

3297 >>> #: Constructing object via numpy arrays 

3298 >>> from numpy import asarray 

3299 >>> cols = HSV(asarray([150, 150, 150]), 

3300 >>> asarray([1.5, 0, 1.5]), 

3301 >>> asarray([0.1, 0.7, 0.1])) 

3302 >>> cols 

3303 >>> #: Converting to RGB 

3304 >>> cols.to("RGB") 

3305 >>> cols 

3306 """ 

3307 

3308 def __init__(self, H, S, V, alpha = None): 

3309 

3310 # checking inputs, save inputs on object 

3311 self._data_ = {} # Dict to store the colors/color dimensions 

3312 tmp = self._colorobject_check_input_arrays_(H = H, S = S, V = V, alpha = alpha) 

3313 for key,val in tmp.items(): self._data_[key] = val 

3314 # White spot definition (the default) 

3315 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3316 

3317 

3318 def to(self, to, fixup = True): 

3319 """Transform Color Space 

3320 

3321 Allows to transform the current object into a different color space, 

3322 if possible. Converting the colors of the current object into 

3323 another color space. After calling this method, the object 

3324 will be of a different class. 

3325 

3326 Args: 

3327 to (str): Name of the color space into which the colors should be 

3328 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3329 fixup (bool): Whether or not colors outside the defined rgb color space 

3330 should be corrected if necessary, defaults to `True`. 

3331 

3332 Examples: 

3333 

3334 >>> from colorspace import HSV 

3335 >>> x = HSV([ 264, 314, 8, 44], 

3336 >>> [0.80, 0.83, 0.57, 0.33], 

3337 >>> [0.57, 0.75, 0.95, 0.91]) 

3338 >>> x 

3339 >>> #: 

3340 >>> type(x) 

3341 >>> #: Convert colors to HLS 

3342 >>> x.to("HLS") 

3343 >>> x 

3344 >>> #: 

3345 >>> type(x) 

3346 >>> #: Convert colors to HSV 

3347 >>> x.to("HSV") 

3348 >>> x 

3349 >>> #: Extracting hex colors (returns list of str) 

3350 >>> x.colors() 

3351 

3352 """ 

3353 self._check_if_allowed_(to) 

3354 from . import colorlib 

3355 clib = colorlib() 

3356 

3357 # Nothing to do (converted to itself) 

3358 if to == self.__class__.__name__: 

3359 return 

3360 

3361 # The only transformation we need is back to RGB 

3362 elif to == "sRGB": 

3363 [R, G, B] = clib.HSV_to_sRGB(self.get("H"), self.get("S"), self.get("V")) 

3364 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3365 self.__class__ = sRGB 

3366 

3367 # From HLS to RGB: take direct path (not via sRGB) 

3368 elif to in ["RGB"]: 

3369 [R, G, B] = clib.HSV_to_RGB(self.get("H"), self.get("S"), self.get("V")) 

3370 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3371 self.__class__ = RGB 

3372 

3373 elif to == "hex": 

3374 via = ["sRGB", to] 

3375 self._transform_via_path_(via, fixup = fixup) 

3376 

3377 elif to == "HLS": 

3378 via = ["sRGB", to] 

3379 self._transform_via_path_(via, fixup = fixup) 

3380 

3381 elif to in ["CIEXYZ", "CIELUV", "CIELAB", "polarLUV", "HCL", "polarLAB"]: 

3382 self._ambiguous(self.__class__.__name__, to) 

3383 

3384 else: self._cannot(self.__class__.__name__, to) 

3385 

3386 

3387class HLS(colorobject): 

3388 """Create HLS Color Object 

3389 

3390 Creates a color object in the Hue-Lightness-Saturation (HLS) color space. 

3391 Can be converted to: :py:class:`RGB`, :py:class:`sRGB`, :py:class:`HSV`, 

3392 and :py:class:`hexcols`. 

3393 Not allowed (ambiguous) are transformations to :py:class:`CIEXYZ`, 

3394 :py:class:`CIELUV`, :py:class:`CIELAB`, :py:class:`polarLUV`, and 

3395 :py:class:`polarLAB`. 

3396 

3397 Args: 

3398 H (int, float, list, numpy.array): 

3399 Numeric value(s) for Hue dimension. 

3400 L (int, float, list, numpy.array): 

3401 Numeric value(s) for Lightness dimension. 

3402 S (int, float, list, numpy.array): 

3403 Numeric value(s) for Saturation dimension. 

3404 alpha (None, float, list, numpy.array): Numeric value(s) for the alpha 

3405 channel (`[0., 1.]`) where `0.` equals full transparency, `1.` full 

3406 opacity. If `None` (default) no transparency is added. 

3407 

3408 Examples: 

3409 

3410 >>> from colorspace import HLS 

3411 >>> # Constructing color object with one single color via float 

3412 >>> HLS(150, 0.1, 3) 

3413 >>> #: Constructing object via lists 

3414 >>> HLS([150, 0, 10], [0.1, 0.7, 0.1], [3, 0, 3]) 

3415 >>> #: Constructing object via numpy arrays 

3416 >>> from numpy import asarray 

3417 >>> cols = HLS(asarray([150, 0, 10]), 

3418 >>> asarray([0.1, 0.7, 0.1]), 

3419 >>> asarray([3, 0, 3])) 

3420 >>> cols 

3421 >>> #: Converting to RGB 

3422 >>> cols.to("RGB") 

3423 >>> cols 

3424 """ 

3425 

3426 def __init__(self, H, L, S, alpha = None): 

3427 

3428 # checking inputs, save inputs on object 

3429 self._data_ = {} # Dict to store the colors/color dimensions 

3430 tmp = self._colorobject_check_input_arrays_(H = H, L = L, S = S, alpha = None) 

3431 for key,val in tmp.items(): self._data_[key] = val 

3432 # White spot definition (the default) 

3433 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3434 

3435 

3436 def to(self, to, fixup = True): 

3437 """Transform Color Space 

3438 

3439 Allows to transform the current object into a different color space, 

3440 if possible. Converting the colors of the current object into 

3441 another color space. After calling this method, the object 

3442 will be of a different class. 

3443 

3444 Args: 

3445 to (str): Name of the color space into which the colors should be 

3446 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"hex"`, `"sRGB"`, ...). 

3447 fixup (bool): Whether or not colors outside the defined rgb color space 

3448 should be corrected if necessary, defaults to `True`. 

3449 

3450 Examples: 

3451 

3452 >>> from colorspace import HLS 

3453 >>> x = HLS([264, 314, 8, 44], 

3454 >>> [0.34, 0.44, 0.68, 0.75], 

3455 >>> [0.67, 0.71, 0.84, 0.62]) 

3456 >>> x 

3457 >>> #: 

3458 >>> type(x) 

3459 >>> #: Convert colors to HSV 

3460 >>> x.to("HSV") 

3461 >>> x 

3462 >>> #: 

3463 >>> type(x) 

3464 >>> #: Convert colors to HLS 

3465 >>> x.to("HLS") 

3466 >>> x 

3467 >>> #: Extracting hex colors (returns list of str) 

3468 >>> x.colors() 

3469 

3470 """ 

3471 self._check_if_allowed_(to) 

3472 from . import colorlib 

3473 clib = colorlib() 

3474 

3475 # Nothing to do (converted to itself) 

3476 if to == self.__class__.__name__: 

3477 return 

3478 

3479 # The only transformation we need is back to RGB 

3480 elif to == "sRGB": 

3481 [R, G, B] = clib.HLS_to_sRGB(self.get("H"), self.get("L"), self.get("S")) 

3482 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3483 self.__class__ = sRGB 

3484 

3485 # From HSV to RGB: take direct path (not via sRGB) 

3486 elif to in ["RGB"]: 

3487 [R, G, B] = clib.HLS_to_RGB(self.get("H"), self.get("L"), self.get("S")) 

3488 self._data_ = {"R" : R, "G" : G, "B" : B, "alpha" : self.get("alpha")} 

3489 self.__class__ = RGB 

3490 

3491 elif to == "hex": 

3492 via = ["sRGB", to] 

3493 self._transform_via_path_(via, fixup = fixup) 

3494 

3495 elif to == "HSV": 

3496 via = ["sRGB", to] 

3497 self._transform_via_path_(via, fixup = fixup) 

3498 

3499 elif to in ["CIEXYZ", "CIELUV", "CIELAB", "polarLUV", "HCL", "polarLAB"]: 

3500 self._ambiguous(self.__class__.__name__, to) 

3501 

3502 else: self._cannot(self.__class__.__name__, to) 

3503 

3504 

3505class hexcols(colorobject): 

3506 """Create Hex Color Object 

3507 

3508 Creates a color object using hex colors (str). 

3509 Can be converted to all other color spaces: :py:class:`CIELAB`, 

3510 :py:class:`CIELUV`, :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, 

3511 :py:class:`RGB`, :py:class:`polarLAB`, :py:class:`polarLUV`, and 

3512 :py:class:`sRGB`. 

3513 

3514 Args: 

3515 hex_ (str, list of str, numpy.ndarray of type str): 

3516 Hex colors. Only six and eight digit hex colors are allowed (e.g., 

3517 `#000000` or `#00000050` if with alpha channel). If invalid hex 

3518 colors are provided the object will raise an exception. Invalid hex 

3519 colors will be handled as `numpy.nan`. 

3520 

3521 Examples: 

3522 

3523 >>> from colorspace import hexcols 

3524 >>> # Creating hex color object from string 

3525 >>> hexcols("#cecece") 

3526 >>> #: Creating hex color object from list of strings 

3527 >>> hexcols(["#ff0000", "#00ff00"]) 

3528 >>> #: Creating hex colors via numpy array 

3529 >>> from numpy import asarray 

3530 >>> cols = hexcols(asarray(["#ff000030", "#00ff0030",  

3531 >>> "#FFFFFF", "#000"])) 

3532 >>> cols 

3533 >>> #: Convert hex colors to another color space (CIEXYZ) 

3534 >>> cols.to("CIEXYZ") 

3535 >>> cols 

3536 >>> #: Picking 7 hex colors from the Green-Orange 

3537 >>> # diverging palette for demonstrating standard representation 

3538 >>> # in jupyter engine and standard print. 

3539 >>> from colorspace import diverging_hcl 

3540 >>> cols2 = hexcols(diverging_hcl("Green-Orange")(7)) 

3541 >>> cols2 # jupyter HTML representation 

3542 >>> #: 

3543 >>> print(cols2) # default representation 

3544 """ 

3545 

3546 def __init__(self, hex_): 

3547 

3548 from colorspace import check_hex_colors 

3549 import numpy as np 

3550 

3551 # If hex_ is str, convert to list 

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

3553 hex_ = check_hex_colors(hex_) 

3554 

3555 self._data_ = {} # Dict to store the colors/color dimensions 

3556 

3557 # This is the one step where we extract transparency from 

3558 # hex colors once we enter the world of colorobjects. 

3559 def get_alpha(hex_): 

3560 # Trying to extract char 7:9, leave None if color is None 

3561 hex_ = [None if (x is None or len(x) < 9) else x[7:9] for x in hex_] 

3562 return [None if x is None else int(x, 16) / 255 for x in hex_] 

3563 

3564 # Remove apha if any 

3565 def remove_alpha(hex_): 

3566 return [None if x is None else x[:7] if len(x) > 7 else x for x in hex_] 

3567 

3568 # Forwarding input 'hex_' to check_hex_colors which will throw 

3569 # an error if we do not understand this input type. 

3570 tmp = np.asarray(get_alpha(hex_), dtype = "float") 

3571 # Remove alpha from 9-digit hex if any, convert to ndarray 

3572 self._data_["hex_"] = np.asarray(remove_alpha(hex_), dtype = object) 

3573 # Store alpha (if any) 

3574 if not np.all(np.isnan(tmp)): self._data_["alpha"] = tmp 

3575 

3576 # White spot definition (the default) 

3577 self.set_whitepoint(X = 95.047, Y = 100.000, Z = 108.883) 

3578 

3579 

3580 def to(self, to, fixup = True): 

3581 """Transform Color Space 

3582 

3583 Allows to transform the current object into a different color space, 

3584 if possible. 

3585 

3586 Allows to transform the current object into a different color space, 

3587 if possible. Converting the colors of the current object into 

3588 another color space. After calling this method, the object 

3589 will be of a different class. 

3590 

3591 Args: 

3592 to (str): Name of the color space into which the colors should be 

3593 converted (e.g., `"CIEXYZ"`, `"HCL"`, `"HSL"`, `"sRGB"`, ...). 

3594 fixup (bool): Whether or not colors outside the defined rgb color space 

3595 should be corrected if necessary, defaults to `True`. 

3596 

3597 Examples: 

3598 

3599 >>> from colorspace import hexcols 

3600 >>> x = hexcols(["#4B1D91", "#BF219A", "#F27B68", "#E7D39A"]) 

3601 >>> x 

3602 >>> #: 

3603 >>> type(x) 

3604 >>> #: Convert colors to sRGB 

3605 >>> x.to("sRGB") 

3606 >>> x 

3607 >>> #: 

3608 >>> type(x) 

3609 >>> #: Convert from sRGB to HCL 

3610 >>> x.to("HCL") 

3611 >>> x 

3612 >>> #: Convert back to hex colors. 

3613 >>> # Round-off errors due to conversion to 'hex'. 

3614 >>> x.to("hex") 

3615 >>> x 

3616 

3617 """ 

3618 self._check_if_allowed_(to) 

3619 from . import colorlib 

3620 clib = colorlib() 

3621 

3622 # Nothing to do (converted to itself) 

3623 if to in ["hex", self.__class__.__name__]: 

3624 return 

3625 

3626 # The only transformation we need is from hexcols -> sRGB 

3627 elif to == "sRGB": 

3628 [R, G, B] = clib.hex_to_sRGB([None if x is None else x[0:7] for x in self.get("hex_")]) 

3629 alpha = self.get("alpha") 

3630 self._data_ = {"R": R, "G": G, "B": B} 

3631 if alpha is not None: self._data_["alpha"] = alpha 

3632 self.__class__ = sRGB 

3633 

3634 # The rest are transformations along a path 

3635 elif to == "RGB": 

3636 via = ["sRGB", to] 

3637 self._transform_via_path_(via, fixup = fixup) 

3638 

3639 elif to in ["HLS", "HSV"]: 

3640 via = ["sRGB", to] 

3641 self._transform_via_path_(via, fixup = fixup) 

3642 

3643 elif to in ["CIEXYZ"]: 

3644 via = ["sRGB", "RGB", to] 

3645 self._transform_via_path_(via, fixup = fixup) 

3646 

3647 elif to in ["CIELUV", "CIELAB"]: 

3648 via = ["sRGB", "RGB", "CIEXYZ", to] 

3649 self._transform_via_path_(via, fixup = fixup) 

3650 

3651 elif to in ["HCL", "polarLUV"]: 

3652 via = ["sRGB", "RGB", "CIEXYZ", "CIELUV", to] 

3653 self._transform_via_path_(via, fixup = fixup) 

3654 

3655 elif to in "polarLAB": 

3656 via = ["sRGB", "RGB", "CIEXYZ", "CIELAB", to] 

3657 self._transform_via_path_(via, fixup = fixup) 

3658 

3659 else: self._cannot(self.__class__.__name__, to) 

3660 

3661 def _repr_html_(self): 

3662 """_repr_html_() 

3663 

3664 Standard HTML representation of the object when using 

3665 the jupyter engine. Will display the colors as html list, 

3666 thanks to @matteoferla (github) for the idea and contribution. 

3667 """ 

3668 from colorspace import contrast_ratio 

3669 

3670 # ul style 

3671 su = {"font-size": "0.5em", "list-style": "none", "display": "flex", 

3672 "padding": "0 0 0.5em 0", "text-align": "center"} 

3673 # li style 

3674 sl = {"width": "5.75em", "height": "5.75em", "padding": "0.25em", 

3675 "display": "inline-block", "margin": "0 0.25em 0 0", 

3676 "border": "0.5px solid gray"} 

3677 

3678 # Getting list of hex colors 

3679 cols = self.colors() 

3680 

3681 dict2style = lambda d: ';'.join(map(':'.join, d.items())) 

3682 

3683 res = f"<ul class=\"colorspace-hexcols\" style=\"{dict2style(su)}\">\n" 

3684 for i in range(len(self)): 

3685 # Calculating contrast ratio to decide text color 

3686 cw = contrast_ratio("#FFF", bg = cols[i])[0] 

3687 cb = contrast_ratio("#000", bg = cols[i])[0] 

3688 sl["color"] = "white" if cw > cb else "black" 

3689 sl["background-color"] = cols[i] 

3690 res += f"<li style=\"{dict2style(sl)}\">{cols[i]}</li>\n" 

3691 

3692 res += "</ul>\n" 

3693 return res 

3694 

3695def compare_colors(a, b, exact = False, _all = True, atol = None): 

3696 """Compare Sets of Colors 

3697 

3698 Compares two sets of colors based on two color objects. The objects 

3699 provided on argument `a` and `b` must inherit from `colorobject`. 

3700 This can be any of the following classes: :py:class:`CIELAB`, 

3701 :py:class:`CIELUV`, :py:class:`CIEXYZ`, :py:class:`HLS`, :py:class:`HSV`, 

3702 :py:class:`RGB`, :py:class:`hexcols`, :py:class:`polarLAB`, 

3703 :py:class:`polarLUV`, or :py:class:`sRGB`. 

3704 

3705 Args: 

3706 a (colorobject): Object which inherits from `colorobject`. 

3707 b (colorobject): Object which inherits from `colorobject`. 

3708 exact (bool): Default `False`, check for colors being nearly equal 

3709 (see `atol`). If set to `True` the coordinates must be identical. 

3710 Note: in case `a` and `b` are hex colors 

3711 (colorspace.colorlib.hexcols) strings will always be matched exactly. 

3712 _all (bool): Default `True`; the function will return `True` if 

3713 all colors are identical/nearly equal. If set to `False` the return 

3714 will be a list of bool containing `True` and `False` for each 

3715 pair of colors. 

3716 atol (None or float): Absolute tolerance for the distance measure 

3717 between two colors to be considered as nearly equal (must be > 0 if set). 

3718 Only used if `exact = False`, else `atol = 1e-6` is used. If set 

3719 to `None` the tolerance will automatically be set depending on the 

3720 type of the objects. Defaults to None. 

3721 

3722 

3723 Returns: 

3724 bool, list: Returns `True` if all colors of `a` are exactly equal or 

3725 nearly equal (see arguments) to the colors in object `b`. If `_all = 

3726 False`, a list of bool is returned indicating pair-wise comparison 

3727 of all colors in `a` and `b`. 

3728 

3729 Example: 

3730 

3731 >>> from colorspace import RGB, hexcols, compare_colors 

3732 >>> 

3733 >>> # Three RGB colors 

3734 >>> a = RGB([0.5, 0.5], [0.1, 0.1], [0.9, 0.9]) 

3735 >>> b = RGB([0.5, 0.5], [0.1, 0.1], [0.9, 0.91]) 

3736 >>>  

3737 >>> compare_colors(a, b) 

3738 >>> #: 

3739 >>> compare_colors(a, b, atol = 0.1) 

3740 >>> #: 

3741 >>> compare_colors(a, b, exact = True) 

3742 >>> #: 

3743 >>> compare_colors(a, b, exact = True, _all = False) 

3744 >>> 

3745 >>> #: Same example using two sets of hexcolor objects 

3746 >>> x = hexcols(["#ff00ff", "#003300"]) 

3747 >>> y = hexcols(["#ff00ff", "#003301"]) 

3748 >>> compare_colors(x, y) 

3749 >>> #: 

3750 >>> compare_colors(x, y, _all = False) 

3751 >>> 

3752 >>> #: Convert HEX to HCL (polarLUV) and back, compare the 

3753 >>> # resulting colors to the original ones; should be identical 

3754 >>> from copy import deepcopy 

3755 >>> z = hexcols(["#ff00ff", "#003301"]) 

3756 >>> zz = deepcopy(z) 

3757 >>> zz.to("HCL") 

3758 >>> zz 

3759 >>> #: 

3760 >>> zz.to("hex") 

3761 >>> zz 

3762 >>> #: 

3763 >>> compare_colors(z, zz) 

3764 

3765 Raises: 

3766 TypeError: If `a` or `b` are not objects of a class which inherits from 

3767 `colorobject`. 

3768 TypeError: If `a` and `b` are not of the same class. 

3769 ValueError: If `a` and `b` are not of the same length, i.e., do not contain 

3770 the same number of colors. 

3771 TypeError: If `exact` or `_all` are not bool. 

3772 TypeError: If `atol` is neither `None` nor float. 

3773 ValueError: If `atol` is not larger than 0. 

3774 """ 

3775 

3776 from numpy import sqrt, isclose 

3777 

3778 if not isinstance(a, colorobject): 

3779 raise TypeError("argument `a` must be an object based on colorspace.colorlib.colorobject") 

3780 if not isinstance(b, colorobject): 

3781 raise TypeError("argument `b` must be an object based on colorspace.colorlib.colorobject") 

3782 if not type(a) == type(b): 

3783 raise TypeError("Input `a` and `b` not of same type") 

3784 if not a.length() == b.length(): 

3785 raise ValueError("Objects do not contain the same number of colors") 

3786 if not isinstance(exact, bool): 

3787 raise TypeError("argument `exact` must be bool") 

3788 if not isinstance(_all, bool): 

3789 raise TypeError("argument `_all` must be bool") 

3790 if not isinstance(atol, float) and not isinstance(atol, type(None)): 

3791 raise TypeError("argument `atol` must be float or None") 

3792 if atol is not None and atol <= 0: 

3793 raise ValueError("argument `atol` must be > 0.") 

3794 

3795 if exact: atol = 1e-6 

3796 

3797 def distance(a, b): 

3798 dist = 0. # Start with zero distance 

3799 for n in list(a._data_.keys()): 

3800 tmpa = a.get(n) 

3801 tmpb = b.get(n) 

3802 # Both None and that is our alpha channel, no judgement 

3803 if tmpa is None and tmpb is None and n == "alpha": 

3804 continue 

3805 # Bot not none, calc Eudlidean distance 

3806 elif not tmpa is None and not tmpb is None: 

3807 dist += (tmpa[0] - tmpb[0])**2.0 

3808 # One missing? Penalize by + 100 

3809 else: 

3810 dist += 100. 

3811 

3812 return sqrt(dist) 

3813 

3814 # Compare hex colors; always on string level 

3815 if isinstance(a, hexcols): 

3816 # Getting coordinates 

3817 res = [a[i].get("hex_")[0].upper() == b[i].get("hex_")[0].upper() for i in range(0, a.length())] 

3818 # Calculate absolute difference between coordinates R/G/B[/alpha]. 

3819 # Threading alpha like another coordinates as all coordinates are scaled [0-1]. 

3820 elif isinstance(a, RGB) or isinstance(a, sRGB) or \ 

3821 isinstance(a, HLS) or isinstance(a, HSV): 

3822 # HEX precision in RGB coordinates is about sqrt((1./255.)**2 * 3) / 2 = 0.003396178 

3823 if not atol: atol = 0.005 

3824 res = [distance(a[i], b[i]) for i in range(0, a.length())] 

3825 res = isclose(res, 0, atol = atol) 

3826 # HCL or polarLUV (both return instance polarLUV) 

3827 # TODO(enhancement): Calculating the Euclidean distance on HCL and (if 

3828 # available) alpha which itself is in [0, 1]. Should be weighted 

3829 # differently (scaled distance)? 

3830 elif isinstance(a, polarLUV) or isinstance(a, CIELUV) or isinstance(a, CIELAB) or \ 

3831 isinstance(a, polarLAB) or isinstance(a, CIEXYZ): 

3832 

3833 if not atol: atol = 1 

3834 res = [distance(a[i], b[i]) for i in range(0, a.length())] 

3835 res = isclose(res, 0, atol = atol) 

3836 

3837 

3838 # If _all is True: check if all elements are True 

3839 if _all: 

3840 from numpy import all 

3841 res = all(res) 

3842 return res 

3843 

3844 

3845 

3846