
COSMIC RAYS DETECTION - ASTROPHYSICS AT YOUR FINGERTIPS
In July 2023 I, Muqtasid Baig, participated in a 2-week summer camp conducted by the Institute for High Energy Physics - Barcelona, Spain. I have captured my gratifying experience in the form of the following article:
​
​​
​
​
​
​
​
​
​
​
​
​
​
​
Star explosions, black holes, and even the sun release particles and energy that fall under the category of cosmic rays. Upon striking the atmosphere, a shower of new particles is generated, including Muons, which I detected by building a special circuit at the Barcelona International Youth Science Challenge, or BIYSC. Here’s a brief journey of the project.
​​
​
​
​
​
​
​
​​
​
​
​
​​
Starting off with theory, we understood that Muons pass through everything, hence can’t be detected directly. However, a material called ‘scintillator’ glows (releasing light) when Muons pass through it. The Muons travel through it, passing their energy to all electrons in atoms they encounter along the way. These electrons jump up to a high energy level, then come back down, releasing this energy in the form of light, which is what we actually detect.
Muons also decay quite fast for them to reach the ground, however, we learnt that due to time dilation, they “experience time slowly” from our perspective, hence they decay after longer, allowing them to reach us. From the Muons perspective, the distance to the ground seems shorter. Basically weird stuff happens when going really fast, hence they can reach us. I was able to recall the derivation for the equation that explains this using a simple example, which I shared with others.
​
​
​
​
​
​
​
​
​
​
​
​
Afterwards, we designed our circuit. The plan was to have a circuit board, on top of which we have a silicon PM(photo-multiplier), a fancy word for light detector. We cut out the plastic scintillator with the right size, to later place on top of the PM. We then build a circuit with a bunch of resistors, capacitors and wires, connected to an output that leads to a machine that graphs voltage as a function of time(oscilloscope). In the end we cover all of this with a box that allows no external light in, and that's it.
What this basically does is, a Muon would pass through the detector, causing the scintillator inside to release light. The PM detects this light, and an electric signal is generated, which is shown by the oscilloscope.
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
We built the circuit, connecting components with wires, and soldering(melting metal to join components strongly) certain points.
​
​
​
​
​
​
​
A picture of our circuit
​
​
​​
​
​
​
​
​
​
​
We designed parts of the outer box that would enclose the circuit, holding it together and allowing no light in.
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
This is the image of the oscilloscope. The peak you see is an electric signal received by the detector. This means it detected a Muon.
Remember, we enclose our detector within a box that allows no external light, because we only want the PM to detect light from the scintillator.
To figure out the impact of the sun on the number of Muon detections, we pointed 2 detectors stacked on top of each other directly at the sun and certain angles away from it, observing the Muon count. We used 2 detectors so that we would only consider 2 peaks on the scope as a Muon, since it is possible for one detector to send a signal to the scope due to nearby electromagnetic noise, even though no Muon passed through. Using 2 detectors reduces the chances of such false positives.
​
​
​
​
​
​
​
​
​
​
We made the setup to hold detectors stacked on top of each other at different angles.​ Here is a picture of our group experimenting.
​
​
​
​
​
​
​
​
​
​
​
​​​
and sample results
​
​
​
​
​
​
​
​
​
​
To automate the process of looking at the scope and counting the peaks, I had an interesting Idea. Although it wasn’t implemented fully by the end, we could make it work to a certain extent.
We’ve got two detectors, so the scope will plot the voltage as a function of time for 2 signals from the 2 detectors. When we get a peak for both at the same time, that's a Muon.
​
​
​
​
​
​
​
​
​
​
What if we took a video of the oscilloscope screen, then wrote a code which does the following-
-
Splits the frames of the video
​
​
​
​
​
​
​
​
​
​
​
​
​
​​​
​
​
​
​
2. Goes through each frame
3. Checks all the pixels along a specific minimum height, above which if the plot goes, it’s counted as a peak
We’ll have to do the third step for the blue line and the yellow line. The minimum height for the yellow would be a little less than for the blue, since the detector corresponding to the yellow line usually produced a weaker signal.
​
​​
​
​​​​
What we see above is the,
Line 1- An empty list is made
Line 2- The code is going to go through each frame
Line 3- The code goes through every pixel along the minimum height for the yellow peak, and if it finds a certain number of pixels with the color of the yellow line, it means the yellow line must have crossed that height, hence it counts it as a yellow peak.
Then, the code goes through every pixel along the minimum height for the blue peak, and if it finds a certain number of pixels with the color of the blue line, it means the blue line must have crossed that height, hence it counts it as a blue peak.
Line 4- When both these peaks are detected in the frame, 1 is added to the empty list at the start, else 0 is added.
In the end, we have something like this:
​
​
​
​
​
​
​
​
​​​
​
​
​
​
In this big list, each 0 or 1 corresponds to a frame in the video, 1 meaning the frame has both peaks, 0 meaning it does not.
Everytime peaks show up, they remain for about half a second, that means the one incident of both the peaks showing up together would remain for many frames. So to count the peaks, here’s the final step.
4. Count number of simultaneous peaks from list
The code goes through the list and counts a group of 21 or more back to back 1’s as a single simultaneous peak, considering back to back 1’s as 1’s with a gap of less than two 0’s in between.
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​​
​
The result:
​
​
​
​
​
​
​
​
The code was tested on small samples of 1 minute, however, certain issues were faced with large recordings like the one’s required for the experiment we performed. Due to time constraints the code was not implemented on large recordings, however, it was certainly amazing to see peaks getting counted automatically even for small samples, with a code written in such a short amount of time.
The entire code:
​
1st part
# Version 3
import cv2
import os
import numpy as np
import time
def read_frames(video_path, num_frames=1000):
"""
Reads frames from a video file.
Parameters:
video_path (str): Path to the video file.
num_frames (int): Number of frames to read.
Returns:
list: List of frames read from the video.
"""
vid = cv2.VideoCapture(video_path)
if not vid.isOpened():
print("Error: Could not open video.")
return []
frames = []
for _ in range(num_frames):
check, frame = vid.read()
if not check:
print("End of video or error reading a frame.")
break
frames.append(frame)
vid.release()
return frames
​
​2nd part
def process_frames(frames, heightY, heightB, thresholdY, thresholdB, min_count):
"""
Processes frames to check specified pixel conditions.
Parameters:
frames (list): List of frames to process.
heightY (int): Y-coordinate for the first pixel condition.
heightB (int): Y-coordinate for the second pixel condition.
thresholdY (int): Threshold for the first pixel condition.
thresholdB (int): Threshold for the second pixel condition.
min_count (int): Minimum count of pixels satisfying the condition.
Returns:
list: Result of processing each frame as 1 or 0.
"""
result = []
for frame in frames:
counter = np.sum((frame[heightY, :, 0] > thresholdY) & (frame[heightB, :, 2] > thresholdB))
result.append(1 if counter >= min_count else 0)
return result
​
3rd part
def analyze_results(result, min_streak=21, max_gap=2):
"""
Analyzes the result array to find the number of peaks.
Parameters:
result (list): Processed result of frames.
min_streak (int): Minimum streak length to qualify as a peak.
max_gap (int): Maximum gap allowed between streaks.
Returns:
int: Number of peaks found.
"""
simul_peaks = 0
i = 0
n = len(result)
while i < n:
if result[i] == 1:
count = 1
while i + 1 < n and result[i + 1] == 1:
count += 1
i += 1
gap = 0
while i + 1 < n and result[i + 1] == 0 and gap < max_gap:
gap += 1
i += 1
if i + 1 < n and result[i + 1] == 1:
count += 1
i += 1
while i + 1 < n and result[i + 1] == 1:
count += 1
i += 1
if count >= min_streak:
simul_peaks += 1
i += 1
return simul_peaks
​
4th part
# Main execution
if __name__ == "__main__":
# Path to the video file
video_path = r'C:\Users\Asus\BIYSC\sample3.MOV'
# Read frames from video
start_time = time.time()
frames = read_frames(video_path)
load_time = time.time()
print(f"Time to read and load frames: {load_time - start_time:.2f} seconds")
if len(frames) == 0:
print("No frames were captured from the video.")
else:
print(f"Total number of frames captured: {len(frames)}")
print(f"Shape of the first frame: {frames[0].shape}")
# First condition processing
heightY, heightB = 345, 335
result = process_frames(frames, heightY, heightB, 180, 170, 1)
process_time_1 = time.time()
print(f"Time for first condition processing: {process_time_1 - load​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
​​



















