Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions BecquerelMonitor/BecquerelMonitor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Compile Include="RoiWizard\NuclideCatalog.cs" />
<Compile Include="RoiWizard\SpectralLine.cs" />
<Compile Include="RoiWizard\LineSetBuilder.cs" />
<Compile Include="RoiWizard\LineMerger.cs" />
<Compile Include="RoiWizard\SecondaryPeaks.cs" />
<Compile Include="RoiWizard\AnchorPicker.cs" />
<Compile Include="RoiWizard\ZoneCalculator.cs" />
<Compile Include="RoiWizard\SetChecker.cs" />
<Compile Include="RoiWizard\SetExporter.cs" />
<Compile Include="RoiWizard\WizardTheme.cs" />
<Compile Include="RoiWizard\CatalogCellRenderers.cs" />
<Compile Include="RoiWizard\RoiWizardStrings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>RoiWizardStrings.resx</DependentUpon>
</Compile>
<Compile Include="RoiWizard\HelpForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="RoiWizard\RoiWizardForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="RoiWizard\RoiWizardForm.Designer.cs">
<DependentUpon>RoiWizardForm.cs</DependentUpon>
</Compile>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
Expand Down Expand Up @@ -875,6 +899,10 @@
<Compile Include="XPTable\Win32\WindowStyles.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="RoiWizard\RoiWizardStrings.ru.resx" />
<EmbeddedResource Include="RoiWizard\RoiWizardStrings.resx" />
<EmbeddedResource Include="RoiWizard\help.xml" />
<EmbeddedResource Include="RoiWizard\nuclides.xml" />
<EmbeddedResource Include="AboutForm.resx">
<DependentUpon>AboutForm.cs</DependentUpon>
</EmbeddedResource>
Expand Down
10 changes: 10 additions & 0 deletions BecquerelMonitor/MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions BecquerelMonitor/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,71 @@ public void ShowNuclideSetForm()
form.ShowDialog();
}

void RoiWizardToolStripMenuItem_Click(object sender, EventArgs e)
{
this.ShowRoiWizardForm();
}

// Окно мастера — плавающая док-панель на общем dockPanel1: её можно пристыковать,
// сгруппировать с другими панелями и убрать в автоскрытие булавкой, как любую
// панель приложения. Экземпляр один: закрытие прячет панель (HideOnClose),
// повторный вызов из меню возвращает её со всеми настройками.
RoiWizard.RoiWizardForm roiWizardForm;

public void ShowRoiWizardForm()
{
if (this.roiWizardForm == null || this.roiWizardForm.IsDisposed)
{
this.roiWizardForm = new RoiWizard.RoiWizardForm(this.RoiWizardResolution);
// Размер плавающего окна берётся у самой формы: её Size уже подогнан
// шрифтом темы (AutoScaleMode.Font укрупняет разметку), и плавающее
// окно отдаёт содержимому ровно столько же, сколько форма имеет в
// клиентской области. Жёсткие числа здесь обрезали бы содержимое.
System.Drawing.Size want = this.roiWizardForm.Size;
System.Drawing.Rectangle work = Screen.FromControl(this).WorkingArea;
want = new System.Drawing.Size(Math.Min(want.Width, work.Width),
Math.Min(want.Height, work.Height));
System.Drawing.Rectangle bounds = new System.Drawing.Rectangle(
work.X + Math.Max(0, (work.Width - want.Width) / 2),
work.Y + Math.Max(0, (work.Height - want.Height) / 2),
want.Width, want.Height);
this.roiWizardForm.Show(this.dockPanel1, bounds);
}
else
{
this.roiWizardForm.Show(this.dockPanel1);
this.roiWizardForm.Activate();
}
}

// Разрешение для мастера — из родной FWHM-калибровки активного спектра, приведённой
// к тому виду, в котором его ждёт мастер: R в процентах на 662 кэВ. Калибровка
// задана в каналах, поэтому ширина переводится в энергию так же, как в
// DCPeakDetectionView: по краям окна ±FWHM/2.
double RoiWizardResolution()
{
DocEnergySpectrum document = this.ActiveDocument;
if (document == null || document.ActiveResultData == null)
{
return 0;
}
ResultData active = document.ActiveResultData;
EnergySpectrum spectrum = active.EnergySpectrum;
if (spectrum == null || spectrum.EnergyCalibration == null || active.FwhmCalibration == null)
{
return 0;
}
double channel = spectrum.EnergyCalibration.EnergyToChannel(662.0, maxChannels: spectrum.NumberOfChannels);
double fwhmChannels = active.FwhmCalibration.ChannelToFwhm(channel);
if (!(fwhmChannels > 0))
{
return 0;
}
double left = spectrum.EnergyCalibration.ChannelToEnergy(channel - fwhmChannels / 2.0);
double right = spectrum.EnergyCalibration.ChannelToEnergy(channel + fwhmChannels / 2.0);
return right > left ? (right - left) / 662.0 * 100.0 : 0;
}

// Token: 0x06000A73 RID: 2675 RVA: 0x0003E2F4 File Offset: 0x0003C4F4
Rectangle GetTotalBound()
{
Expand Down
6 changes: 6 additions & 0 deletions BecquerelMonitor/MainForm.resx
Original file line number Diff line number Diff line change
Expand Up @@ -5444,4 +5444,10 @@
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="RoiWizardToolStripMenuItem.Size" type="System.Drawing.Size, System.Drawing">
<value>224, 22</value>
</data>
<data name="RoiWizardToolStripMenuItem.Text" xml:space="preserve">
<value>ROI and nuclide set builder...</value>
</data>
</root>
3 changes: 3 additions & 0 deletions BecquerelMonitor/MainForm.ru.resx
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,7 @@
<data name="SpecUtilsToolStripMenuItem.Text" xml:space="preserve">
<value>Импорт при помощи SpectUtils..</value>
</data>
<data name="RoiWizardToolStripMenuItem.Text" xml:space="preserve">
<value>Конструктор ROI и наборов нуклидов...</value>
</data>
</root>
184 changes: 184 additions & 0 deletions BecquerelMonitor/RoiWizard/AnchorPicker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Globalization;

namespace BecquerelMonitor.RoiWizard
{
// Якорная линия: ровно одна запись набора получает IsAnchor = true. Найдя её в спектре,
// BecqMoni сажает остальные линии набора на табличные позиции и подгоняет амплитуды
// (библиотечный фит). Без якоря механизм не запускается вовсе.
public static class AnchorPicker
{
// Хороший якорь — сильная И одинокая линия: сосед внутри FWHM смещает центроид
// найденного пика, и совпадение с табличной энергией перестаёт быть надёжным.
// Правило даёт 2614.5 для ряда Th-232 и 609.3 для Ra-226.
// Сколько якорных линий помечать по умолчанию. LibraryPeakFitter перебирает все
// записи с IsAnchor, берёт сдвиг калибровки с сильнейшей по SNR и требует, чтобы
// с найденным пиком совпала хотя бы одна (допуск 0.5·FWHM). Единственный якорь —
// единственная точка отказа: не нашёлся 2614.5 — набор молчит целиком.
public const int DefaultCount = 3;

// Несколько якорей по тому же правилу: сильные и одинокие γ-линии, по убыванию
// интенсивности. Одинокие идут первыми — у них центроид найденного пика не смещён
// соседом; если одиноких не хватает, добираются просто сильные.
public static List<SpectralLine> PickMany(IList<SpectralLine> lines,
ResolutionModel resolution, int count)
{
List<SpectralLine> picked = new List<SpectralLine>();
if (lines == null || lines.Count == 0 || count <= 0)
{
return picked;
}

double max = MaxGammaIntensity(lines);
List<SpectralLine> lonely = new List<SpectralLine>();
List<SpectralLine> rest = new List<SpectralLine>();
foreach (SpectralLine line in lines)
{
if (line.Type != LineType.Gamma || line.Intensity < 0.2 * max)
{
continue;
}
if (IsLonely(line, lines, resolution, max))
{
lonely.Add(line);
}
else
{
rest.Add(line);
}
}
lonely.Sort(ByIntensityDesc);
rest.Sort(ByIntensityDesc);

foreach (SpectralLine line in lonely)
{
if (picked.Count >= count)
{
break;
}
picked.Add(line);
}
foreach (SpectralLine line in rest)
{
if (picked.Count >= count)
{
break;
}
picked.Add(line);
}
if (picked.Count == 0)
{
// γ-линий в наборе нет вовсе — та же оговорка, что и у Pick
SpectralLine fallback = Strongest(lines, LineType.Xray);
if (fallback != null)
{
picked.Add(fallback);
}
}
return picked;
}

static int ByIntensityDesc(SpectralLine a, SpectralLine b)
{
return b.Intensity.CompareTo(a.Intensity);
}

static double MaxGammaIntensity(IList<SpectralLine> lines)
{
double max = 0.0;
foreach (SpectralLine line in lines)
{
if (line.Type == LineType.Gamma && line.Intensity > max)
{
max = line.Intensity;
}
}
return max;
}

public static SpectralLine Pick(IList<SpectralLine> lines, ResolutionModel resolution)
{
if (lines == null || lines.Count == 0)
{
return null;
}
// Порог 0.2·max считается по ОДНИМ γ-линиям. Интенсивности ХРИ условные
// (Kα1 = 100), и если брать максимум по всем линиям, то у слабо-γ нуклида
// рядом с ХРИ свинца все настоящие γ уходят ниже порога.
double max = 0.0;
foreach (SpectralLine line in lines)
{
if (line.Type == LineType.Gamma && line.Intensity > max)
{
max = line.Intensity;
}
}

SpectralLine best = null;
SpectralLine bestLonely = null;
foreach (SpectralLine line in lines)
{
if (line.Type != LineType.Gamma || line.Intensity < 0.2 * max)
{
continue;
}
if (best == null || line.Intensity > best.Intensity)
{
best = line;
}
if (IsLonely(line, lines, resolution, max) &&
(bestLonely == null || line.Intensity > bestLonely.Intensity))
{
bestLonely = line;
}
}
SpectralLine pick = bestLonely ?? best;
if (pick != null)
{
return pick;
}
// Фолбэк только на настоящие линии распада: у ХРИ интенсивность условная,
// у вторичных положение — эмпирическая поправка. Якорь на таком маркере
// означал бы, что LibraryPeakFitter сажает весь набор по нефизической опоре.
return Strongest(lines, LineType.Xray);
}

static SpectralLine Strongest(IList<SpectralLine> lines, LineType type)
{
SpectralLine pick = null;
foreach (SpectralLine line in lines)
{
if (line.Type == type && (pick == null || line.Intensity > pick.Intensity))
{
pick = line;
}
}
return pick;
}

// Годится ли линия в якоря: набор без якоря библиотечный фит не запускает вовсе,
// а якорь на ХРИ или вторичном маркере хуже отсутствия — фит «найдёт» опору там,
// где её физически нет.
public static bool IsAcceptable(SpectralLine line)
{
return line != null && (line.Type == LineType.Gamma || line.Type == LineType.Xray);
}

static bool IsLonely(SpectralLine line, IList<SpectralLine> lines,
ResolutionModel resolution, double max)
{
double window = resolution.Fwhm(line.Energy);
foreach (SpectralLine other in lines)
{
if (!ReferenceEquals(other, line) && other.Intensity >= 0.05 * max &&
Math.Abs(other.Energy - line.Energy) < window)
{
return false;
}
}
return true;
}
}

}
Loading