Coverage for python / lsst / images / serialization / _dtypes.py: 58%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-30 09:07 +0000

1# This file is part of lsst-images. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12from __future__ import annotations 

13 

14__all__ = ( 

15 "FloatType", 

16 "IntegerType", 

17 "NumberType", 

18 "SignedIntegerType", 

19 "UnsignedIntegerType", 

20 "is_integer", 

21) 

22 

23import enum 

24from typing import Literal, TypeGuard 

25 

26import numpy as np 

27import numpy.typing as npt 

28 

29 

30class NumberType(enum.StrEnum): 

31 """Enumeration of array values types supported by the library.""" 

32 

33 bool = enum.auto() 

34 uint8 = enum.auto() 

35 uint16 = enum.auto() 

36 uint32 = enum.auto() 

37 uint64 = enum.auto() 

38 int8 = enum.auto() 

39 int16 = enum.auto() 

40 int32 = enum.auto() 

41 int64 = enum.auto() 

42 float32 = enum.auto() 

43 float64 = enum.auto() 

44 

45 def to_numpy(self) -> type: 

46 """Convert an enumeration member to the corresponding numpy scalar 

47 type object. 

48 

49 Returns 

50 ------- 

51 type 

52 Numpy scalar type, e.g. `numpy.int16`. Note that this inherits 

53 from `type`, not `numpy.dtype` (though a `numpy.dtype` instance 

54 can always be constructed from it). 

55 """ 

56 return getattr(np, self.value) 

57 

58 @classmethod 

59 def from_numpy(cls, dtype: npt.DTypeLike) -> NumberType: 

60 """Construct an enumeration member from anything that can be coerced 

61 to `numpy.dtype`. 

62 

63 Parameters 

64 ---------- 

65 dtype 

66 Object convertible to `numpy.dtype`. 

67 """ 

68 datatype, shape = cls.from_numpy_with_shape(dtype) 

69 if shape: 

70 raise TypeError(f"{dtype} is not a scalar type.") 

71 return datatype 

72 

73 @classmethod 

74 def from_numpy_with_shape(cls, dtype: npt.DTypeLike) -> tuple[NumberType, tuple[int, ...]]: 

75 """Construct an enumeration member and extract a shape from anything 

76 that can be coerced to `numpy.dtype`. 

77 

78 Parameters 

79 ---------- 

80 dtype 

81 Object convertible to `numpy.dtype`. 

82 

83 Returns 

84 ------- 

85 `NumberType` 

86 Enumeration member. 

87 `tuple` [`int`, ...] 

88 Shape as a `tuple` of `int`. 

89 """ 

90 dtype = np.dtype(dtype) 

91 shape = () 

92 if dtype.shape: 

93 shape = dtype.shape 

94 dtype = dtype.base 

95 return cls(dtype.name), shape 

96 

97 def require_integer(self) -> IntegerType: 

98 """Raise `TypeError` if this enumeration does not represent an 

99 integer type, and return it if it does. 

100 """ 

101 if is_integer(self): 

102 return self 

103 raise TypeError(f"{self} is not an integer type.") 

104 

105 

106type UnsignedIntegerType = ( 

107 Literal[NumberType.bool] 

108 | Literal[NumberType.uint8] 

109 | Literal[NumberType.uint16] 

110 | Literal[NumberType.uint32] 

111 | Literal[NumberType.uint64] 

112) 

113 

114type SignedIntegerType = ( 

115 Literal[NumberType.int8] 

116 | Literal[NumberType.int16] 

117 | Literal[NumberType.int32] 

118 | Literal[NumberType.int64] 

119) 

120 

121 

122type IntegerType = SignedIntegerType | UnsignedIntegerType 

123 

124type FloatType = Literal[NumberType.float32] | Literal[NumberType.float64] 

125 

126 

127def is_integer(t: NumberType) -> TypeGuard[IntegerType]: 

128 """Test whether a `NumberType` corresponds to an integer type.""" 

129 return np.dtype(t.to_numpy()).kind in ("i", "u")