Belle II Software development
th2hex.py
1
8
9import ROOT
10
11import math
12import numpy as np
13
14
15def TH2Hex(name,
16 title,
17 n_x_bins,
18 x_lower_bound,
19 x_upper_bound,
20 n_y_bins,
21 y_lower_bound,
22 y_upper_bound,
23 y_orientation=False):
24 """Constructor for a two dimensional ROOT histogram TH2Poly setup with hexagonal bins.
25
26 In contrast to TH2Poly.Honeycomb this does not leave any space in the histogram range uncovered
27 and allows for different scales in the x and y direction.
28
29 Parameters
30 ----------
31 name : str
32 The name of the histogram to identify the object in ROOT.
33 title : str
34 Title of the histogram
35 n_x_bins : float or int
36 Number of hexagons in the x direction to cover the whole range
37 x_lower_bound : float
38 The lower bound of the histogram in the x direction.
39 x_upper_bound : float
40 The upper bound of the histogram in the x direction.
41 n_y_bins : float or int
42 Number of hexagons in the y direction to cover the whole range
43 y_lower_bound : float
44 The lower bound of the histogram in the y direction.
45 y_upper_bound : float
46 The upper bound of the histogram in the x direction.
47 y_orientation : bool, optional
48 Switch whether the hexagones should be primarily aligned along the y coordinate
49 instead of the x coordinate, which is the default
50
51 Returns
52 -------
53 ROOT.TH2Poly
54 Two dimensional histogram populated with
55 """
56
57 # Create the polygon histogram
58 hex_histogram = ROOT.TH2Poly(name,
59 title,
60 x_lower_bound,
61 x_upper_bound,
62 y_lower_bound,
63 y_upper_bound)
64
65 # Construct points of a hexagon with unit radius
66 # Go clockwise such that root understands what the interal of the polygon is
67 pi = math.pi
68
69 unit_radius_hex_xs = np.array([math.sin(2.0 * pi * i / 6.0) for i in range(-2, 4)])
70 unit_radius_hex_ys = np.array([math.cos(2.0 * pi * i / 6.0) for i in range(-2, 4)])
71
72 # Move the hex to a reference position such that the lower left corner (8 o'clock position) is at zero
73 unit_radius_hex_xs -= unit_radius_hex_xs[0]
74 unit_radius_hex_ys -= unit_radius_hex_ys[0]
75
76 # Shrink the hexagon to unit width
77 hex_x_width = unit_radius_hex_xs[3] - unit_radius_hex_ys[0]
78 hex_y_width = unit_radius_hex_ys[2] - unit_radius_hex_ys[0]
79 hex_y_protrusion = unit_radius_hex_ys[2] - unit_radius_hex_ys[1]
80
81 unit_width_hex_xs = unit_radius_hex_xs / hex_x_width
82 unit_width_hex_ys = unit_radius_hex_ys / hex_y_width
83
84 # Small hack to cover the other orientation of the hexagons with the same code by swapping x, y
85 if y_orientation:
86 n_x_bins, n_y_bins = n_y_bins, n_x_bins
87 x_lower_bound, y_lower_bound = y_lower_bound, x_lower_bound
88 x_upper_bound, y_upper_bound = y_upper_bound, x_upper_bound
89
90 # Expand to the bin width accounting for the protrusion in the y - direction.
91 n_y_bins_protrusion_correction = hex_y_protrusion / hex_y_width
92
93 base_x_bin_width = float(x_upper_bound - x_lower_bound) / n_x_bins
94 base_y_bin_width = float(y_upper_bound - y_lower_bound) / (n_y_bins - n_y_bins_protrusion_correction)
95
96 base_hex_xs = unit_width_hex_xs * base_x_bin_width
97 base_hex_ys = unit_width_hex_ys * base_y_bin_width
98 base_y_protrusion = base_hex_ys[2] - base_hex_ys[1]
99
100 # Assume the lower edges of the bins
101 x_bin_edges = np.linspace(x_lower_bound, x_upper_bound, n_x_bins + 1)
102 even_x_lower_bin_edges = x_bin_edges[:-1]
103 # Note: The odd rows have on hexagon more and are displaced by half a bin width
104 odd_x_lower_bin_edges = x_bin_edges - base_x_bin_width / 2.0
105
106 y_bin_edges = np.linspace(y_lower_bound, y_upper_bound + base_y_protrusion, n_y_bins + 1)
107 y_lower_bin_edges = y_bin_edges[:-1]
108
109 # Construct the hexagonal bins in the histogram
110 for i_y_bin, y_lower_bin_edge in enumerate(y_lower_bin_edges):
111 if i_y_bin % 2:
112 x_lower_bin_edges = odd_x_lower_bin_edges
113 else:
114 x_lower_bin_edges = even_x_lower_bin_edges
115
116 for x_lower_bin_edge in x_lower_bin_edges:
117 hex_xs = base_hex_xs + x_lower_bin_edge
118 hex_ys = base_hex_ys + y_lower_bin_edge
119
120 # Swap back x and y introduced by the hack above
121 if y_orientation:
122 hex_histogram.AddBin(6, hex_ys, hex_xs)
123 else:
124 hex_histogram.AddBin(6, hex_xs, hex_ys)
125
126 return hex_histogram
127
128
129def test():
130 # Test plot of a two dimensional gaus distribution with hex binning.
131 n_data = 1000000
132 normal_distributed_x_values = np.random.randn(n_data)
133 normal_distributed_y_values = 2.0 * np.random.randn(n_data)
134 weights = np.ones_like(normal_distributed_y_values)
135
136 n_x_bins = 100
137 x_lower_bound = -3
138 x_upper_bound = 3
139
140 n_y_bins = 100
141 y_lower_bound = -6
142 y_upper_bound = 6
143
144 hex_histogram = TH2Hex("hex_normal",
145 "Two dimensional normal distribution in a hexagonal binning",
146 n_x_bins,
147 x_lower_bound,
148 x_upper_bound,
149 n_y_bins,
150 y_lower_bound,
151 y_upper_bound,
152 y_orientation=True)
153
154 hex_histogram.FillN(n_data, normal_distributed_x_values, normal_distributed_y_values, weights)
155
156 root_palette = {
157 "deepsea": 51, # Deep sea color map
158 "grey": 52, # Grey scale
159 "darkbody": 53, # Dark body radiator
160 "hue": 54, # Weird hue from blue over grey to yellow
161 "rainbow": 55, # A nicer rain bow color map
162 "invdarkbody": 56, # Inverted dark body radiator
163 }
164
165 ROOT.gStyle.SetPalette(root_palette["rainbow"])
166
167 hex_histogram.Draw("colz")
168 input()
169
170
171if __name__ == "__main__":
172 test()