Belle II Software  release-08-01-10
th2hex.py
1 
8 
9 import ROOT
10 
11 import math
12 import numpy as np
13 
14 
15 def 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 
129 def 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 
171 if __name__ == "__main__":
172  test()