{"id":4687,"date":"2025-07-19T00:25:31","date_gmt":"2025-07-19T00:25:31","guid":{"rendered":"https:\/\/techtrendfeed.com\/?p=4687"},"modified":"2025-07-19T00:25:31","modified_gmt":"2025-07-19T00:25:31","slug":"exploratory-knowledge-evaluation-gamma-spectroscopy-in-python-half-2","status":"publish","type":"post","link":"https:\/\/techtrendfeed.com\/?p=4687","title":{"rendered":"Exploratory Knowledge Evaluation: Gamma Spectroscopy in Python (Half 2)"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p class=\"has-text-align-left wp-block-paragraph\"><a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/towardsdatascience.com\/exploratory-data-analysis-gamma-spectroscopy-in-python\/\"> half<\/a>, I did an exploratory information evaluation of the gamma spectroscopy information. We have been in a position to see that utilizing a contemporary scintillation detector, we can&#8217;t solely see that the article is radioactive. With a gamma spectrum, we&#8217;re additionally in a position to inform <em>why<\/em> it&#8217;s radioactive and what sort of isotopes the article comprises.<\/p>\n<p class=\"wp-block-paragraph\">On this half, we are going to go additional, and I&#8217;ll present  make and practice a machine studying mannequin for detecting radioactive components.<\/p>\n<p class=\"wp-block-paragraph\">Earlier than we start, an <strong>necessary<\/strong> <strong>warning<\/strong>. All information information collected for this text can be found on Kaggle, and readers can practice and take a look at their ML fashions with out having actual {hardware}. If you wish to take a look at <em>actual objects<\/em>, do it at your individual threat. I did my assessments with sources that may be legally discovered and bought, like classic uranium glass or previous watches with radium dial paint. Please verify your native legal guidelines and skim security pointers about dealing with radioactive supplies. Sources used on this take a look at should not critically harmful, however nonetheless should be dealt with with care!<\/p>\n<p class=\"wp-block-paragraph\">Now, let\u2019s get began! I&#8217;ll present  gather the information, practice the mannequin, and run it utilizing a <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/102.radiacode.com\/DmitriiE\">Radiacode<\/a> scintillation detector. For these readers who do not need Radiacode {hardware}, the hyperlink to the datasource is added on the finish of the article.<\/p>\n<h2 class=\"wp-block-heading\">Methodology<\/h2>\n<p class=\"wp-block-paragraph\">This text will comprise a number of components:<\/p>\n<ol class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">I&#8217;ll briefly clarify what a gamma spectrum is and the way we are able to use it.<\/li>\n<li class=\"wp-block-list-item\">We are going to gather the information for our ML mannequin. I&#8217;ll present the code for accumulating the spectra utilizing the Radiacode system.<\/li>\n<li class=\"wp-block-list-item\">We are going to practice the mannequin and management its accuracy.<\/li>\n<li class=\"wp-block-list-item\">Lastly, I&#8217;ll make an HTMX-based internet frontend for the mannequin, and we are going to see the ends in real-time.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Let\u2019s get into it!<\/p>\n<h2 class=\"wp-block-heading\">1. Gamma Spectrum<\/h2>\n<p class=\"wp-block-paragraph\">It is a brief recap of the <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/towardsdatascience.com\/exploratory-data-analysis-gamma-spectroscopy-in-python\/\">first half<\/a>, and for extra particulars, I extremely suggest studying it first.<\/p>\n<p class=\"wp-block-paragraph\">Why is the gamma spectrum so attention-grabbing? Some objects round us will be barely radioactive. Its sources differ from the naturally occurring radiation of granite within the buildings to the radium in some classic watches or the thorium in trendy thoriated tungsten rods. A <em>Geiger counter<\/em> solely reveals us the variety of radioactive particles that have been detected. A <em>scintillation detector<\/em> reveals us not solely the variety of particles but additionally their energies. It is a essential distinction\u2014it turned out that totally different radioactive supplies emit gamma rays with totally different energies, and every materials has its personal \u201cfootprint.\u201d<\/p>\n<p class=\"wp-block-paragraph\">As a primary instance, I purchased this pendant within the Chinese language store:<\/p>\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/20250712_193719-1024x307.jpg\" alt=\"\" class=\"wp-image-608787\" style=\"width:454px;height:auto\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">It was marketed as an \u201cion-generating,\u201d so I already suspected that the pendant might be barely radioactive (an ionizing radiation, as its title suggests, can produce ions). Certainly, as we are able to see on the meter display, its radioactivity degree is about 1,20 <em>\u00b5Sv<\/em>\/h, which is 12 instances increased than the background (0,1 <em>\u00b5Sv<\/em>\/h). It isn&#8217;t loopy excessive and akin to a degree on an airplane through the flight, however it&#8217;s nonetheless statistically vital \ud83d\ude09<\/p>\n<p class=\"wp-block-paragraph\">Nevertheless, by solely observing the worth, we can&#8217;t inform <em>why<\/em> the article is radioactive. A gamma spectrum will present us what isotopes are inside the article:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/06\/rad10-1024x508.png\" alt=\"\" class=\"wp-image-605501\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">On this instance, the pendant comprises <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/en.wikipedia.org\/wiki\/Thorium-232\">thorium-232<\/a>, and a thorium decay chain produces radium and actinium. As we are able to see on the graph, the actinium-228 peak is nicely seen on the spectrum.<\/p>\n<p class=\"wp-block-paragraph\">As a second instance, let\u2019s say we now have discovered this piece of rock:<\/p>\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/uraninite-1.jpg\" alt=\"\" class=\"wp-image-608650\" style=\"width:217px;height:auto\"\/><figcaption class=\"wp-element-caption\">Picture supply <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/en.wikipedia.org\/wiki\/Uraninite\">Wikipedia<\/a><\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">That is uraninite, a mineral that comprises lots of uranium dioxide. Such specimens will be present in some areas of Germany, the Czech Republic, or the US. If we get it within the mineral store, it in all probability has a label on it. However within the area, it\u2019s normally not the case \ud83d\ude09 With a gamma spectrum, we are able to see a picture like this:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/uraninite-sp-1024x336.png\" alt=\"\" class=\"wp-image-608655\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">By evaluating the peaks with identified isotopes, we are able to inform that the rock comprises uranium, however, for instance, not thorium.<\/p>\n<p class=\"wp-block-paragraph\">A bodily rationalization of the gamma spectrum can be fascinating. As we are able to see on the graph beneath, gamma rays are literally photons and belong to the identical spectrum as seen gentle:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/spectrum-1024x435.png\" alt=\"\" class=\"wp-image-608941\"\/><figcaption class=\"wp-element-caption\">Electromagnetic spectrum, Picture supply <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/en.wikipedia.org\/wiki\/Electromagnetic_radiation\">Wikipedia<\/a><\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">When some folks assume that radioactive objects are glowing in the dead of night, it\u2019s truly true! Each radioactive materials is certainly glowing with its personal distinctive \u201ccoloration,\u201d however within the very far and non-visible to the human eye a part of the spectrum.<\/p>\n<p class=\"wp-block-paragraph\">A second fascinating factor is that solely 10-20 years in the past, gamma-spectroscopy was obtainable just for establishments and large labs (in the perfect case, some used crystals with unknown high quality might be discovered on eBay). These days, because of developments in electronics, a scintillation detector will be bought for the worth of a mid-range smartphone.<\/p>\n<p class=\"wp-block-paragraph\">Now, let\u2019s return to our mission. As we are able to see from the 2 examples above, the spectra of various objects are totally different. Let\u2019s create a machine studying mannequin that may mechanically detect varied components.<\/p>\n<h2 class=\"wp-block-heading\">2. Accumulating the Knowledge<\/h2>\n<p class=\"wp-block-paragraph\">As readers can guess, our first problem is <strong>accumulating the samples<\/strong>. I&#8217;m not a nuclear establishment, and I don\u2019t have entry to the calibrated take a look at sources like cesium or strontium. Nevertheless, for our process, it&#8217;s not required, and a few supplies will be legally discovered and bought. For instance, americium continues to be utilized in smoke detectors; radium was utilized in portray the watch dials earlier than the Nineteen Sixties; uranium was extensively utilized in glass manufacturing earlier than the Nineteen Fifties, and thoriated tungsten rods are nonetheless produced right now and will be bought from Amazon. Even the pure uranium ore will be bought within the mineral retailers; nevertheless, it requires a bit extra security precautions. And a benefit of gamma-spectroscopy is that we don\u2019t must disassemble or break the gadgets, and the method is usually secure.<\/p>\n<p class=\"wp-block-paragraph\">The second problem is <strong>accumulating the information<\/strong>. For those who work in e-commerce, then it\u2019s normally not an issue, and each SQL request will return hundreds of thousands of data. Alas, within the \u201cactual world,\u201d it may be rather more difficult. Particularly if you wish to make a database of the radioactive supplies. In our case, accumulating each spectrum requires 10-20 minutes. For each take a look at object, it could be good to have a minimum of 10 data. As we are able to see, the method can take hours, and having hundreds of thousands of data shouldn&#8217;t be a sensible choice.<\/p>\n<p class=\"wp-block-paragraph\">For getting the spectrum information, I shall be utilizing a <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/102.radiacode.com\/DmitriiE\">Radiacode 103G<\/a> scintillation detector and an open-source <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/github.com\/cdump\/radiacode\">radiacode<\/a> library.<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/06\/rad1-1024x236.jpg\" alt=\"\" class=\"wp-image-605484\"\/><figcaption class=\"wp-element-caption\">Radiacode detector, Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">A gamma spectrum will be exported in XML format utilizing the official Radiacode Android app, however the guide course of is simply too gradual and tedious. As a substitute, I created a Python script that collects the spectra utilizing random time intervals:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from radiacode import RadiaCode, RawData, Spectrum\n\n\ndef read_forever(rc: RadiaCode):\n    \"\"\" Learn information from the system \"\"\"\n    whereas True:\n        interval_sec = random.randint(10*60, 30*60)\n        read_spectrum(rc, interval_sec)\n\ndef read_spectrum(rc: RadiaCode, interval: int):\n    \"\"\" Learn and save spectrum \"\"\"\n    rc.spectrum_reset()\n\n    # Learn\n    dt = datetime.datetime.now()\n    filename = dt.strftime(\"spectrum-%YpercentmpercentdpercentHpercentMpercentS.json\")\n    logging.debug(f\"Making spectrum for {interval \/\/ 60} min\")\n\n    # Wait\n    t_start = time.monotonic()\n    whereas time.monotonic() - t_start &lt; interval:\n        show_device_data(rc)\n        time.sleep(0.4)\n\n    # Save\n    spectrum: Spectrum = rc.spectrum()\n    spectrum_save(spectrum, filename)\n\ndef show_device_data(rc: RadiaCode):\n    \"\"\" Get CPS (counts per second) values \"\"\"\n    information = rc.data_buf()\n    for file in information:\n        if isinstance(file, RawData):\n            log_str = f\"CPS: {int(file.count_rate)}\"\n            logging.debug(log_str)\n\ndef spectrum_save(spectrum: Spectrum, filename: str):\n    \"\"\" Save  spectrum information to log \"\"\"\n    duration_sec = spectrum.period.total_seconds()\n    information = {\n            \"a0\": spectrum.a0,\n            \"a1\": spectrum.a1,\n            \"a2\": spectrum.a2,\n            \"counts\": spectrum.counts,\n            \"period\": duration_sec,\n    }\n    with open(filename, \"w\") as f_out:\n        json.dump(information, f_out, indent=4)\n        logging.debug(f\"File '{filename}' saved\")\n\n\nrc = RadiaCode()\napp.read_forever()<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Some error dealing with is omitted right here for readability causes. A hyperlink to the total supply code will be discovered on the finish of the article.<\/p>\n<p class=\"wp-block-paragraph\">As we are able to see, I randomly choose the time between 10 and half-hour, gather the gamma spectrum information, and put it aside to a JSON file. Now, I solely want to position a Radiacode detector close to the article and go away the script operating for a number of hours. Because of this, 10-20 JSON information shall be saved. I additionally must repeat the method for each pattern I&#8217;ve. As a remaining output, 100-200 information will be collected. It\u2019s nonetheless not hundreds of thousands, however as we are going to see, it&#8217;s sufficient for our process.<\/p>\n<h2 class=\"wp-block-heading\">3. Coaching the Mannequin<\/h2>\n<p class=\"wp-block-paragraph\">When the information from the earlier step is prepared, we are able to begin coaching the mannequin. As a reminder, all information can be found on Kaggle, and readers are welcome to make their very own fashions as nicely.<\/p>\n<p class=\"wp-block-paragraph\">First, let\u2019s preprocess the information and extract the options we wish to use.<\/p>\n<h3 class=\"wp-block-heading\">3.1 Knowledge Load<\/h3>\n<p class=\"wp-block-paragraph\">When the information is collected, we should always have some spectrum information saved in JSON format. A person file appears to be like like this:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-json\">{\n    \"a0\": 24.524023056030273,\n    \"a1\": 2.2699732780456543,\n    \"a2\": 0.0004327862989157,\n    \"counts\": [ 48, 52, , ..., 0, 35],\n    \"period\": 1364.0\n}\n<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Right here, the \u201c<em>counts\u201d array is the <\/em>precise spectrum information. Completely different detectors might have totally different codecs; a Radiacode returns the information within the type of a 1024-channel array. Calibration constants [a0, a1, a2] permit us to transform the channel quantity into the vitality in keV (kiloelectronvolt).<\/p>\n<p class=\"wp-block-paragraph\">First, let\u2019s make a technique to load the spectrum from a file:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">@dataclass\nclass Spectrum:\n    \"\"\" Radiation spectrum measurement information \"\"\"\n\n    period: int\n    a0: float\n    a1: float\n    a2: float\n    counts: checklist[int]\n\n    def channel_to_energy(self, ch: int) -&gt; float:\n        \"\"\" Convert channel quantity to the vitality degree \"\"\"\n        return self.a0 + self.a1 * ch + self.a2 * ch**2\n\n    def energy_to_channel(self, e: float):\n        \"\"\" Convert vitality to the channel quantity (inverse E = a0 + a1*C + a2 C^2) \"\"\"\n        c = self.a0 - e\n        return int(\n            (np.sqrt(self.a1**2 - 4 * self.a2 * c) - self.a1) \/ (2 * self.a2)\n        )\n\n\ndef load_spectrum_json(filename: str) -&gt; Spectrum:\n    \"\"\" Load spectrum from a json file \"\"\"\n    with open(filename) as f_in:\n        information = json.load(f_in)\n        return Spectrum(\n            a0=information[\"a0\"], a1=information[\"a1\"], a2=information[\"a2\"],\n            counts=information[\"counts\"],\n            period=int(information[\"duration\"]),\n        )<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Now, we are able to draw it with Matplotlib:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">import matplotlib.pyplot as plt\n\ndef draw_simple_spectrum(spectrum: Spectrum, title: Non-obligatory[str] = None):\n    \"\"\" Draw spectrum obtained from the Radiacode \"\"\"\n    fig, ax = plt.subplots(figsize=(12, 3))\n    ax.spines[\"top\"].set_color(\"lightgray\")\n    ax.spines[\"right\"].set_color(\"lightgray\")\n    counts = spectrum.counts\n    vitality = [spectrum.channel_to_energy(x) for x in range(len(counts))]\n    # Bars\n    ax.bar(vitality, counts, width=3.0, label=\"Counts\")\n    # X values\n    ticks_x = [\n       spectrum.channel_to_energy(ch) for ch in range(0, len(counts), len(counts) \/\/ 20)\n    ]\n    labels_x = [f\"{ch:.1f}\" for ch in ticks_x]\n    ax.set_xticks(ticks_x, labels=labels_x)\n    ax.set_xlim(vitality[0], vitality[-1])\n    plt.ylim(0, None)\n    title_str = \"Gamma-spectrum\" if title is None else title\n    ax.set_title(title_str)\n    ax.set_xlabel(\"Vitality, keV\")\n    plt.legend()\n    fig.tight_layout()\n\n\nsp = load_spectrum_json(\"thorium-20250617012217.json\")\ndraw_simple_spectrum(sp)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The output appears to be like like this:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/thorium_spectrum-1024x250.png\" alt=\"\" class=\"wp-image-609561\"\/><figcaption class=\"wp-element-caption\">Thorium spectrum, picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">What can we see right here?<\/p>\n<p class=\"wp-block-paragraph\">As was talked about earlier than, from a regular Geiger counter, we are able to get solely the variety of detected particles. It tells us if the article is radioactive or not, however no more. From a scintillation detector, we are able to get the variety of particles grouped by their energies, which is virtually a ready-to-use histogram! A radioactive decay itself is random, so the longer the gathering time, the \u201csmoother\u201d the graph.<\/p>\n<h3 class=\"wp-block-heading\">3.2 Knowledge Remodel<\/h3>\n<p class=\"wp-block-paragraph\"><strong>3.2.1 Normalization<\/strong><br \/>Let\u2019s take a look at the spectrum once more:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/thorium_spectrum-1024x250.png\" alt=\"\" class=\"wp-image-609561\"\/><\/figure>\n<p class=\"wp-block-paragraph\">Right here, the information was collected for about 10 minutes, and the vertical axis comprises the variety of detected particles. This method has a easy drawback: the variety of particles shouldn&#8217;t be a relentless. It relies on each the gathering time and the \u201cpower\u201d of the supply. It signifies that we might not have 600 particles like on this graph, however 60 or 6000. We will additionally see that the information is a bit noisy. That is particularly seen with a \u201cweak\u201d supply and a brief assortment time.<\/p>\n<p class=\"wp-block-paragraph\">To eradicate these points, I made a decision to make use of a two-step pipeline. First, I utilized the <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/docs.scipy.org\/doc\/scipy\/reference\/generated\/scipy.signal.savgol_filter.html\">Savitzky-Golay filter<\/a> to scale back the noise:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from scipy.sign import savgol_filter\n\ndef smooth_data(information: np.array) -&gt; np.array:\n    \"\"\" Apply 1D smoothing filter to the information array \"\"\"\n    window_size = 10\n    data_out = savgol_filter(\n        information,\n        window_length=window_size,\n        polyorder=2,\n    )\n    return np.clip(data_out, a_min=0, a_max=None)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">It&#8217;s particularly helpful for spectra with brief assortment instances, the place the peaks should not so nicely seen.<\/p>\n<p class=\"wp-block-paragraph\">Second, I normalized a NumPy array to 0..1 by merely dividing its values by the utmost.<\/p>\n<p class=\"wp-block-paragraph\">A remaining \u201cnormalize\u201d technique appears to be like like this:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def normalize(spectrum: Spectrum) -&gt; Spectrum:\n    \"\"\" Normalize information to the vertical vary of 0..1 \"\"\"\n    # Easy information\n    counts = np.array(spectrum.counts).astype(np.float64)\n    counts = smooth_data(counts)\n\n    # Normalize\n    val_norm = counts.max()\n    return Spectrum(\n        period=spectrum.period,\n        a0 = spectrum.a0,\n        a1 = spectrum.a1,\n        a2 = spectrum.a2,\n        counts = counts\/val_norm\n    )<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Because of this, spectra from totally different sources now have an analogous scale:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/normalization-1024x524.png\" alt=\"\" class=\"wp-image-610038\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">As we are able to additionally see, the distinction between the 2 samples is sort of seen.<\/p>\n<p class=\"wp-block-paragraph\"><strong>3.2.2 Knowledge Augmentation<\/strong><br \/>Technically, we&#8217;re prepared to coach the mannequin. Nevertheless, as we noticed within the \u201cAccumulating the information\u201d half, the dataset is fairly small \u2013 I could have solely 100-200 information in complete. The answer is to reinforce the information by including extra artificial samples.<\/p>\n<p class=\"wp-block-paragraph\">As a easy method, I made a decision so as to add some noise to the unique spectra. However how a lot noise ought to we add? I chosen a 680 keV channel as a reference worth, as a result of this half has no attention-grabbing isotopes. Then I added a noise with 50% of the amplitude of that channel. A <em>np.clip<\/em> name ensures that the information values should not damaging (for the quantity of detected particles, it doesn&#8217;t make bodily sense).<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def add_noise(spectrum: Spectrum) -&gt; Spectrum:\n    \"\"\" Add random noise to the spectrum \"\"\"\n    counts = np.array(spectrum.counts)    \n    ch_empty = spectrum.energy_to_channel(680.0)\n    val_norm = counts[ch_empty]\n\n    ampl = val_norm \/ 2\n    noise = np.random.regular(0, ampl, counts.form)\n    data_out = np.clip(counts + noise, min=0)\n    return Spectrum(\n        period=spectrum.period,\n        a0 = spectrum.a0,\n        a1 = spectrum.a1,\n        a2 = spectrum.a2,\n        counts = data_out\n    )\n\nsp = load_spectrum_json(\"thorium-20250617012217.json\")\nsp = add_noise(normalize(sp))\ndraw_simple_spectrum(sp, filename)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The output appears to be like like this:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/sp_with_noise-1024x250.png\" alt=\"\" class=\"wp-image-610045\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">As we are able to see, the noise degree shouldn&#8217;t be that massive, so it doesn&#8217;t distort the peaks. On the similar time, it provides some variety to the information.<\/p>\n<p class=\"wp-block-paragraph\">A extra refined method will also be used. For instance, some radioactive minerals comprise thorium, uranium, or potassium in numerous proportions. It could be attainable to mix spectra of current samples to get some \u201cnew\u201d ones.<\/p>\n<p class=\"wp-block-paragraph\"><strong>3.2.3 Characteristic<\/strong> <strong>Extraction<\/strong><br \/>Technically, we are able to use all 1024 values \u201cas is\u201d as an enter for our ML mannequin. Nevertheless, this method has two issues:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">First, it&#8217;s redundant \u2013 we&#8217;re principally  solely particularly isotopes. For instance, on the final graph, there&#8217;s a good seen peak at 238 keV, which belongs to Lead-212, and a much less seen peak at 338 keV, which belongs to Actinium-228.<\/li>\n<li class=\"wp-block-list-item\">Second, it&#8217;s device-specific. I desire a mannequin to be common. Utilizing solely the energies of the chosen isotopes as enter permits us to make use of any gamma spectrometer mannequin.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Lastly, I created this checklist of isotopes:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">isotopes = [ \n    # Americium\n    (\"Am-241\", 59.5),\n    # Potassium\n    (\"K-40\", 1460.0),\n    # Radium\n    (\"Ra-226\", 186.2),\n    (\"Pb-214\", 242.0),\n    (\"Pb-214\", 295.2),\n    (\"Pb-214\", 351.9),\n    (\"Bi-214\", 609.3),\n    (\"Bi-214\", 1120.3),\n    (\"Bi-214\", 1764.5),\n    # Thorium\n    (\"Pb-212\", 238.6),\n    (\"Ac-228\", 338.2),\n    (\"TI-208\", 583.2),\n    (\"AC-228\", 911.2),\n    (\"AC-228\", 969.0),\n    # Uranium\n    (\"Th-234\", 63.3),\n    (\"Th-231\", 84.2),\n    (\"Th-234\", 92.4),\n    (\"Th-234\", 92.8),\n    (\"U-235\", 143.8),\n    (\"U-235\", 185.7),\n    (\"U-235\", 205.3),\n    (\"Pa-234m\", 766.4),\n    (\"Pa-234m\", 1000.9),\n]\n\ndef isotopes_save(filename: str):\n    \"\"\" Save isotopes checklist to a file \"\"\"\n    with open(filename, \"w\") as f_out:\n        json.dump(isotopes, f_out)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Solely spectrum values for these isotopes shall be used as enter for the mannequin. I additionally created a technique to save lots of a listing into the JSON file \u2013 will probably be used to load the mannequin later. Some isotopes, like Uranium-235, could also be current in minuscule quantities and never be virtually detectable. Readers are welcome to enhance the checklist on their very own.<\/p>\n<p class=\"wp-block-paragraph\">Now, let\u2019s create a technique that converts a Radiacode spectrum to a listing of options:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def get_features(spectrum: Spectrum, isotopes: Checklist) -&gt; np.array:\n    \"\"\" Extract options from the spectrum \"\"\"\n    energies = [energy for _, energy in isotopes]\n    information = [spectrum.counts[spectrum.energy_to_channel(energy)] for vitality in energies]\n    return np.array(information)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Virtually, we transformed the checklist of 1024 values to a NumPy array with solely 23 components, which is an effective measurement discount!<\/p>\n<h3 class=\"wp-block-heading\">3.3 Coaching<\/h3>\n<p class=\"wp-block-paragraph\">Lastly, we&#8217;re prepared to coach the ML mannequin.<\/p>\n<p class=\"wp-block-paragraph\">First, let\u2019s mix all information into one dataset. Virtually, it relies on the samples you&#8217;ve got and will appear to be this:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">all_files = [\n    (\"Americium\", glob.glob(\"..\/data\/train\/americium*.json\")),\n    (\"Radium\", glob.glob(\"..\/data\/train\/radium*.json\")),\n    (\"Thorium\", glob.glob(\"..\/data\/train\/thorium*.json\")),\n    (\"Uranium Glass\", glob.glob(\"..\/data\/train\/uraniumGlass*.json\")),\n    (\"Uranium Glaze\", glob.glob(\"..\/data\/train\/uraniumGlaze*.json\")),\n    (\"Uraninite\", glob.glob(\"..\/data\/train\/uraninite*.json\")),\n    (\"Background\", glob.glob(\"..\/data\/train\/background*.json\")),\n]\n\ndef prepare_data(augmentation: int) -&gt; Tuple[np.array, np.array]:\n    \"\"\" Put together information for coaching \"\"\"\n    x, y = [], []\n    for title, information in all_files:\n        for filename in information:\n            print(f\"Processing {filename}...\")\n            sp = normalize(load_spectrum(filename))\n            for _ in vary(augmentation):\n                sp_out = add_noise(sp)\n                x.append(get_features(sp_out, isotopes))\n                y.append(title)\n\n    return np.array(x), np.array(y)\n\n\nX_train, y_train = prepare_data(augmentation=10)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">As we are able to see, our y-values comprise names like \u201cAmericium.\u201d I&#8217;ll use a <em>LabelEncoder<\/em> to transform them into numeric values:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from sklearn.preprocessing import LabelEncoder\n\n\nle = LabelEncoder()\nle.match(y_train)\ny_train = le.remodel(y_train)\n\nprint(\"X_train:\", X_train.form)\n#&gt; (1900, 23)\n\nprint(\"y_train:\", y_train.form)\n#&gt; (1900,)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">I made a decision to make use of an open-source <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/github.com\/dmlc\/xgboost\">XGBoost<\/a> mannequin, which is predicated on gradient tree boosting (<a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/arxiv.org\/pdf\/1603.02754\">authentic paper hyperlink<\/a>). I can even use a <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.model_selection.GridSearchCV.html\">GridSearchCV<\/a> to search out optimum parameters:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from xgboost import XGBClassifier\nfrom sklearn.model_selection import GridSearchCV\n\n\nbst = XGBClassifier(n_estimators=10, max_depth=2, learning_rate=1)\nclf = GridSearchCV(\n    bst,\n    {\n        \"max_depth\": [1, 2, 3, 4],\n        \"n_estimators\": vary(2, 20),\n        \"learning_rate\": [0.001, 0.01, 0.1, 1.0, 10.0]\n    },\n    verbose=1,\n    n_jobs=1,\n    cv=3,\n)\nclf.match(X_train, y_train)\n\nprint(\"best_score:\", clf.best_score_)\n#&gt; best_score: 0.99474\n\nprint(\"best_params:\", clf.best_params_)\n#&gt; best_params: {'learning_rate': 1.0, 'max_depth': 1, 'n_estimators': 9}\n<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Final however not least, I want to save lots of the skilled mannequin:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">isotopes_save(\"..\/fashions\/V1\/isotopes.json\")\nbst.save_model(\"..\/fashions\/V1\/XGBClassifier.json\")\nnp.save(\"..\/fashions\/V1\/LabelEncoder.npy\", le.classes_)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Clearly, we want not solely the mannequin itself but additionally the checklist of isotopes and labels. If we alter one thing, the information is not going to match anymore, and the mannequin will produce rubbish, so mannequin versioning is our buddy!<\/p>\n<p class=\"wp-block-paragraph\">To <strong>confirm the outcomes<\/strong>, I want information that the mannequin didn&#8217;t \u201csee\u201d earlier than.  I already collected a number of XML information utilizing the Radiacode Android app, and only for enjoyable, I made a decision to make use of them for testing.<\/p>\n<p class=\"wp-block-paragraph\">First, I created a technique to load the information:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">import xmltodict\n\ndef load_spectrum_xml(file_path: str) -&gt; Spectrum:\n    \"\"\" Load the spectrum from a Radiacode Android app file \"\"\"\n    with open(file_path) as f_in:\n        doc = xmltodict.parse(f_in.learn())\n        consequence = doc[\"ResultDataFile\"][\"ResultDataList\"][\"ResultData\"]\n        spectrum = consequence[\"EnergySpectrum\"]\n        cal = spectrum[\"EnergyCalibration\"][\"Coefficients\"][\"Coefficient\"]\n        a0, a1, a2 = float(cal[0]), float(cal[1]), float(cal[2])\n        period = int(spectrum[\"MeasurementTime\"])\n        information = spectrum[\"Spectrum\"][\"DataPoint\"]\n        return Spectrum(\n            period=period,\n            a0=a0, a1=a1, a2=a2,\n            counts=[int(x) for x in data],\n        )<\/code><\/pre>\n<p class=\"wp-block-paragraph\">It has the identical spectra values that I used within the JSON information, with some additional information that&#8217;s not required for our process.<\/p>\n<p class=\"wp-block-paragraph\">Virtually, that is an instance of knowledge assortment. This Victorian creamer from the Nineties is 130 years previous, and belief me, you can not get this information by utilizing an SQL request \ud83d\ude42<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/uraniumGlass-1024x506.jpg\" alt=\"\" class=\"wp-image-610222\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">This uranium glass is barely radioactive (the background degree is about 0,08 \u00b5Sv\/h), but it surely\u2019s at a secure degree and can&#8217;t produce any hurt.<\/p>\n<p class=\"wp-block-paragraph\">The take a look at code itself is easy:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># Load mannequin\nbst = XGBClassifier()\nbst.load_model(\"..\/fashions\/V1\/XGBClassifier.json\")\nisotopes = isotopes_load(\"..\/fashions\/V1\/isotopes.json\")\nle = LabelEncoder()\nle.classes_ = np.load(\"..\/fashions\/V1\/LabelEncoder.npy\")\n\n# Load information\ntest_data = [\n    [\"..\/data\/test\/background1.xml\", \"..\/data\/test\/background2.xml\"],\n    [\"..\/data\/test\/thorium1.xml\", \"..\/data\/test\/thorium2.xml\"],\n    [\"..\/data\/test\/uraniumGlass1.xml\", \"..\/data\/test\/uraniumGlass2.xml\"],\n    ...\n]\n\n# Predict\nfor group in test_data:\n    information = []\n    for filename in group:\n        spectrum = load_spectrum(filename)\n        options = get_features(normalize(spectrum), isotopes)\n        information.append(options)\n\n    X_test = np.array(information)\n    preds = bst.predict(X_test)\n    preds = le.inverse_transform(preds)\n    print(preds)\n\n#&gt; ['Background' 'Background']\n#&gt; ['Thorium' 'Thorium']\n#&gt; ['Uranium Glass' 'Uranium Glass']\n#&gt; ...<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Right here, I additionally grouped the values from totally different samples and used batch prediction.<\/p>\n<p class=\"wp-block-paragraph\">As we are able to see, all outcomes are right. I used to be additionally going to make a confusion matrix, however a minimum of for my comparatively small variety of samples, all objects have been detected correctly.<\/p>\n<h2 class=\"wp-block-heading\">4. Testing<\/h2>\n<p class=\"wp-block-paragraph\">As a remaining a part of this text, let\u2019s use the mannequin in real-time with a Radiacode system. <\/p>\n<p class=\"wp-block-paragraph\">The code is nearly the identical as at the start of the article, so I\u2019ll present solely the essential components. Utilizing the <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/github.com\/cdump\/radiacode\">radiacode<\/a> library, I hook up with the system, learn the spectra as soon as per minute, and use these values to foretell the isotopes:<\/p>\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">from radiacode import RadiaCode, RealTimeData, Spectrum\nimport logging\n\n\nle = LabelEncoder()\nle.classes_ = np.load(\"..\/fashions\/V1\/LabelEncoder.npy\")\nisotopes = isotopes_load(\"..\/fashions\/V1\/isotopes.json\")\nbst = XGBClassifier()\nbst.load_model(\"..\/fashions\/V1\/XGBClassifier.json\")\n\n\ndef read_spectrum(rc: RadiaCode):\n    \"\"\" Learn spectrum information \"\"\"\n    spectrum: Spectrum = rc.spectrum()\n    logging.debug(f\"Spectrum: {spectrum.period} assortment time\")\n    consequence = predict_spectrum(spectrum)\n    logging.debug(f\"Predict: {consequence}\")\n\ndef predict_spectrum(sp: Spectrum) -&gt; str:\n    \"\"\" Predict the isotope from a spectrum \"\"\"\n    options = get_features(normalize(sp), isotopes)\n    preds = bst.predict([features])\n    return le.inverse_transform(preds)[0]\n\ndef read_cps(rc: RadiaCode):\n    \"\"\" Learn CPS (counts per second) values \"\"\"\n    information = rc.data_buf()\n    for file in information:\n        if isinstance(file, RealTimeData):\n             logging.debug(f\"CPS: {file.count_rate:.2f}\")\n\n\nif __name__ == '__main__':\n    logging.basicConfig(\n        degree=logging.DEBUG, format=\"[%(asctime)-15s] %(message)s\",\n        datefmt=\"%Y-%m-%d %H:%M:%S\"\n    )\n\n    rc = RadiaCode()\n    logging.debug(f\"ML mannequin loaded\")\n    fw_version = rc.fw_version()\n    logging.debug(f\"Gadget linked:, firmware {fw_version[1]}\")\n    rc.spectrum_reset()\n    whereas True:\n        for _ in vary(12):\n            read_cps(rc)\n            time.sleep(5.0)\n\n        read_spectrum(rc)<\/code><\/pre>\n<p class=\"wp-block-paragraph\">Right here, I learn the CPS (counts per second) values from the Radiacode each 5 seconds, simply to make sure that the system works. Each minute, I learn the spectrum and use it with the mannequin.<\/p>\n<p class=\"wp-block-paragraph\">Earlier than operating the app, I positioned the Radiacode detector close to the article:<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/radiumWatch-1024x336.jpg\" alt=\"\" class=\"wp-image-610225\"\/><figcaption class=\"wp-element-caption\">Picture by writer<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">This classic watch was made within the Nineteen Fifties, and it has radium paint on the digits. Its radiation degree is ~5 instances the background, however it&#8217;s nonetheless inside a secure degree (and it&#8217;s truly 2 instances decrease than everybody will get in an airplane throughout a flight).<\/p>\n<p class=\"wp-block-paragraph\">Now, we are able to run the code and see the ends in real-time:<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/contributor.insightmediagroup.io\/wp-content\/uploads\/2025\/07\/console-1.png\" alt=\"\" class=\"wp-image-610228\"\/><\/figure>\n<p class=\"wp-block-paragraph\">As we are able to see, the mannequin\u2019s prediction is right.<\/p>\n<p class=\"wp-block-paragraph\">Readers who don\u2019t have a Radiacode {hardware} can use uncooked log information to replay the information. The hyperlink is added to the top of the article.<\/p>\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<p class=\"wp-block-paragraph\">On this article, I defined the method of making a machine studying mannequin for predicting radioactive isotopes. I additionally examined the mannequin with some radioactive samples that may be legally bought.<\/p>\n<p class=\"wp-block-paragraph\">I additionally did an interactive HTMX frontend for the mannequin, however this text is already too lengthy. If there&#8217;s a public curiosity on this subject, this shall be revealed within the subsequent half.<\/p>\n<p class=\"wp-block-paragraph\">As for the mannequin itself, there are a number of methods for enchancment:<\/p>\n<ul class=\"wp-block-list\">\n<li class=\"wp-block-list-item\">Including extra information samples and isotopes. I\u2019m not a nuclear establishment, and my selection (from not solely monetary or authorized views, but additionally contemplating the free area in my house) is restricted. Readers who&#8217;ve entry to different isotopes and minerals are welcome to share their information, and I&#8217;ll attempt to add it to the mannequin.<\/li>\n<li class=\"wp-block-list-item\">Including extra options. On this mannequin, I normalized all spectra, and it really works nicely. Nevertheless, on this method, we lose the details about the radioactivity degree of the objects. For instance, the uranium glass has a a lot decrease radiation degree in comparison with the uranium ore. To differentiate these objects extra successfully, we are able to add the radioactivity degree as a further mannequin characteristic.<\/li>\n<li class=\"wp-block-list-item\">Testing different mannequin sorts. It appears to be like promising to make use of a vector search to search out the closest embeddings. It will also be extra interpretable, and the mannequin can present a number of closest isotopes. A library like <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/github.com\/facebookresearch\/faiss\">FAISS<\/a> will be helpful for that. One other method is to make use of a deep studying mannequin, which will also be attention-grabbing to check.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">On this article, I used a\u00a0<a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/102.radiacode.com\/DmitriiE\">Radiacode<\/a>\u00a0radiation detector. It&#8217;s a good system that enables making some attention-grabbing experiments (disclaimer: I don\u2019t have any revenue or different business curiosity from its gross sales). For these readers who don\u2019t have a Radiacode {hardware}, all collected information is freely <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/www.kaggle.com\/datasets\/dmitriieliuseev\/radiation-spectra-ml-data\">obtainable on Kaggle<\/a>.<\/p>\n<p class=\"wp-block-paragraph\">The complete supply code for this text is accessible on my\u00a0<a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/www.patreon.com\/deliuseev\">Patreon web page<\/a>. This assist helps me to purchase tools or electronics for future assessments. And readers are additionally welcome to attach through\u00a0<a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/www.linkedin.com\/in\/dmitrii-eliuseev\/\">LinkedIn<\/a>, the place I periodically publish smaller posts that aren&#8217;t sufficiently big for a full article.<\/p>\n<p class=\"wp-block-paragraph\">Thanks for studying.<\/p>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>half, I did an exploratory information evaluation of the gamma spectroscopy information. We have been in a position to see that utilizing a contemporary scintillation detector, we can&#8217;t solely see that the article is radioactive. With a gamma spectrum, we&#8217;re additionally in a position to inform why it&#8217;s radioactive and what sort of isotopes the [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":4689,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55],"tags":[1455,157,4124,4125,668,1258,4126],"class_list":["post-4687","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-machine-learning","tag-analysis","tag-data","tag-exploratory","tag-gamma","tag-part","tag-python","tag-spectroscopy"],"_links":{"self":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/4687","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4687"}],"version-history":[{"count":1,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/4687\/revisions"}],"predecessor-version":[{"id":4688,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/4687\/revisions\/4688"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/media\/4689"}],"wp:attachment":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4687"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4687"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4687"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}<!-- This website is optimized by Airlift. Learn more: https://airlift.net. Template:. Learn more: https://airlift.net. Template: 69d9690a190636c2e0989534. Config Timestamp: 2026-04-10 21:18:02 UTC, Cached Timestamp: 2026-08-05 13:22:24 UTC -->