Coverage for kwave/utils/data.py: 13%

96 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-24 12:06 -0700

1from datetime import datetime 

2from math import floor 

3 

4import numpy as np 

5from beartype import beartype as typechecker 

6from beartype.typing import Optional, Tuple, Union 

7 

8from kwave.data import Vector 

9from kwave.utils.typing import NUMERIC_WITH_COMPLEX 

10 

11 

12@typechecker 

13def get_smallest_possible_type( 

14 max_array_val: Union[NUMERIC_WITH_COMPLEX, Vector], target_type_group: str, default: Optional[str] = None 

15) -> Union[str, None]: 

16 """ 

17 Returns the smallest possible type for the given array. 

18 Args: 

19 max_array_val: The maximum value in the array. 

20 target_type_group: The type group to search for the smallest possible type. 

21 default: The default type to return if no type is found. 

22 

23 Returns: 

24 The smallest possible type for the given array. 

25 

26 """ 

27 

28 types = {"uint", "int"} 

29 assert target_type_group in types 

30 

31 for bit_count in [8, 16, 32]: 

32 type_ = f"{target_type_group}{bit_count}" 

33 if max_array_val < intmax(type_): 

34 return type_ 

35 

36 type_ = default 

37 return type_ 

38 

39 

40@typechecker 

41def intmax(dtype: str) -> int: 

42 """ 

43 Returns the maximum value for the given integer type. 

44 

45 Args: 

46 dtype: The integer type. 

47 

48 Returns 

49 The maximum value for the given integer type. 

50 

51 """ 

52 

53 return np.iinfo(getattr(np, dtype)).max 

54 

55 

56@typechecker 

57def scale_time(seconds: Union[int, float]) -> str: 

58 """ 

59 Converts an integer number of seconds into hours, minutes, 

60 and seconds, and returns a string with this information. 

61 

62 Args: 

63 seconds: number of seconds 

64 

65 Returns: 

66 String of scaled time. 

67 

68 """ 

69 

70 # switch to calculating years, weeks, and days if larger than 100 hours 

71 if seconds > (60 * 60 * 100): 

72 years = floor(seconds / (60 * 60 * 24 * 365)) 

73 seconds = seconds - years * 60 * 60 * 24 * 365 

74 weeks = floor(seconds / (60 * 60 * 24 * 7)) 

75 seconds = seconds - weeks * 60 * 60 * 24 * 7 

76 days = floor(seconds / (60 * 60 * 24)) 

77 seconds = seconds - days * 60 * 60 * 24 

78 else: 

79 years = 0 

80 weeks = 0 

81 days = 0 

82 

83 # calculate hours, minutes, and seconds 

84 hours = floor(seconds / (60 * 60)) 

85 seconds = seconds - hours * 60 * 60 

86 minutes = floor(seconds / 60) 

87 seconds = seconds - minutes * 60 

88 

89 # write out as a string, to keep the output manageable, only the largest 

90 # three units are written 

91 if years > 0: 

92 time = f"{years} years, {weeks} weeks, and {days} days" 

93 elif weeks > 0: 

94 time = f"{weeks} weeks, {days} days, and {hours} hours" 

95 elif days > 0: 

96 time = f"{days} days, {hours} hours, and {minutes} min" 

97 elif hours > 0: 

98 seconds = np.round(seconds, 4) 

99 if np.abs(seconds - int(seconds)) < 1e-4: 

100 seconds = int(seconds) 

101 time = f"{hours}hours {minutes}min {seconds}s" 

102 elif minutes > 0: 

103 seconds = np.round(seconds, 4) 

104 if np.abs(seconds - int(seconds)) < 1e-4: 

105 seconds = int(seconds) 

106 time = f"{minutes}min {seconds}s" 

107 else: 

108 precision = 10 # manually tuned number 

109 seconds = round(seconds, precision) 

110 time = f"{seconds}s" 

111 return time 

112 

113 

114@typechecker 

115def scale_SI(x: Union[float, np.ndarray]) -> Tuple[str, Union[int, float], str, str]: 

116 """ 

117 Scale a number to the nearest SI unit prefix. 

118 

119 Args: 

120 x: The number to scale. 

121 

122 Returns: 

123 A tuple containing a string of the scaled number, a numeric scaling factor, the prefix, and the unit. 

124 

125 """ 

126 

127 # force the input to be a scalar 

128 x = np.max(x) 

129 

130 # check for a negative input 

131 if x < 0: 

132 x = -x 

133 negative = True 

134 else: 

135 negative = False 

136 

137 if x == 0: 

138 # if x is zero, don't scale 

139 x_sc = x 

140 prefix = "" 

141 prefix_fullname = "" 

142 scale = 1 

143 

144 elif x < 1: 

145 # update index and input 

146 x_sc = x * 1e3 

147 sym_index = 1 

148 

149 # find scaling parameter 

150 while x_sc < 1 and sym_index < 8: 

151 x_sc = x_sc * 1e3 

152 sym_index = sym_index + 1 

153 

154 # define SI unit scalings 

155 units = { 

156 1: ("m", "milli", 1e3), 

157 2: ("u", "micro", 1e6), 

158 3: ("n", "nano", 1e9), 

159 4: ("p", "pico", 1e12), 

160 5: ("f", "femto", 1e15), 

161 6: ("a", "atto", 1e18), 

162 7: ("z", "zepto", 1e21), 

163 8: ("y", "yocto", 1e24), 

164 } 

165 prefix, prefix_fullname, scale = units[sym_index] 

166 

167 elif x >= 1000: 

168 # update index and input 

169 x_sc = x * 1e-3 

170 sym_index = 1 

171 

172 # find scaling parameter 

173 while x_sc >= 1000 and sym_index < 8: 

174 x_sc = x_sc * 1e-3 

175 sym_index = sym_index + 1 

176 

177 # define SI unit scalings 

178 units = { 

179 1: ("k", "kilo", 1e-3), 

180 2: ("M", "mega", 1e-6), 

181 3: ("G", "giga", 1e-9), 

182 4: ("T", "tera", 1e-12), 

183 5: ("P", "peta", 1e-15), 

184 6: ("E", "exa", 1e-18), 

185 7: ("Z", "zetta", 1e-21), 

186 8: ("Y", "yotta", 1e-24), 

187 } 

188 prefix, prefix_fullname, scale = units[sym_index] 

189 

190 else: 

191 # if x is between 1 and 1000, don't scale 

192 x_sc = x 

193 prefix = "" 

194 prefix_fullname = "" 

195 scale = 1 

196 

197 # form scaling into a string 

198 round_decimals = 6 # TODO this needs to be tuned 

199 x_sc = x_sc.round(round_decimals) 

200 if (x_sc - int(x_sc)) < (0.1**round_decimals): 

201 # avoid values like X.0, instead have only X 

202 x_sc = int(x_sc) 

203 x_sc = f"-{x_sc}{prefix}" if negative else f"{x_sc}{prefix}" 

204 return x_sc, scale, prefix, prefix_fullname 

205 

206 

207def get_date_string() -> str: 

208 return datetime.now().strftime("%d-%b-%Y-%H-%M-%S")