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