@rfkit/spectrum-analyzer 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,7 @@ export declare const DEFAULT_SPECTRUM_CONFIG: {
10
10
  readonly processing: {
11
11
  readonly enableWaterfall: false;
12
12
  readonly enableMetrics: false;
13
+ readonly enableFluorescence: false;
13
14
  };
14
15
  readonly outputPoints: 1001;
15
16
  readonly outputRange: {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;CAa1B,CAAC;AAEX,eAAO,MAAM,cAAc;;;;sCAIA,MAAM;0CACF,MAAM;qDACK,MAAM;;;;wCAKnB,MAAM;CAEzB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;CAc1B,CAAC;AAEX,eAAO,MAAM,cAAc;;;;sCAIA,MAAM;0CACF,MAAM;qDACK,MAAM;;;;wCAKnB,MAAM;CAEzB,CAAC"}
@@ -19,6 +19,8 @@ export default class SpectrumAnalyzer {
19
19
  protected processTimes: number;
20
20
  protected lastProcessTime: number;
21
21
  protected cachedExceedingDatas: TemplateOverData[];
22
+ protected fluorescenceData?: Map<number, number>[];
23
+ protected fluorescenceMaxCount: number;
22
24
  private readonly onSpectrumUpdate?;
23
25
  constructor(config: Partial<SpectrumConfig>);
24
26
  /**
@@ -137,6 +139,11 @@ export default class SpectrumAnalyzer {
137
139
  * @param {boolean} isOver - 数据是否处理完成。
138
140
  */
139
141
  private processDataPoints;
142
+ /**
143
+ * 在原始数据上更新荧光谱统计数据,确保使用正确的索引系统
144
+ * 优化版本:减少不必要的计算和内存访问
145
+ */
146
+ private updateFluorescenceStats;
140
147
  private isAllValid;
141
148
  /**
142
149
  * 通知频谱数据更新,调用回调函数。
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAEjB,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,SAAS,CAAC,MAAM,EAAE,QAAQ,CACxB,IAAI,CAAC,cAAc,EAAE,UAAU,GAAG,iBAAiB,GAAG,kBAAkB,CAAC,CAC1E,CAA2B;IAC5B,OAAO,CAAC,QAAQ,CAAqB;IAErC,SAAS,CAAC,iBAAiB,EAAG,YAAY,CAAC;IAC3C,SAAS,CAAC,mBAAmB,EAAG,OAAO,CAAC;IAExC,SAAS,CAAC,QAAQ,EAAG,uBAAuB,CAAC;IAC7C,SAAS,CAAC,OAAO,EAAG,mBAAmB,CAAC;IACxC,SAAS,CAAC,OAAO,EAAG,mBAAmB,CAAC;IACxC,SAAS,CAAC,OAAO,EAAG,mBAAmB,CAAC;IACxC,SAAS,CAAC,YAAY,EAAG,YAAY,CAAC;IACtC,SAAS,CAAC,mBAAmB,EAAG,YAAY,CAAC;IAC7C,SAAS,CAAC,aAAa,EAAG,uBAAuB,EAAE,CAAC;IAEpD,SAAS,CAAC,aAAa,EAAG,WAAW,CAAC;IAEtC,SAAS,CAAC,cAAc,EAAG,YAAY,CAAC;IACxC,SAAS,CAAC,mBAAmB,EAAG,uBAAuB,EAAE,CAAC;IAC1D,SAAS,CAAC,YAAY,EAAG,MAAM,CAAC;IAEhC,SAAS,CAAC,SAAS,EAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,YAAY,EAAG,MAAM,CAAC;IAChC,SAAS,CAAC,eAAe,EAAG,MAAM,CAAC;IACnC,SAAS,CAAC,oBAAoB,EAAE,gBAAgB,EAAE,CAAM;IAExD,SAAS,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACnD,SAAS,CAAC,oBAAoB,SAAK;IAEnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAqC;gBAE3D,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC;IAqB3C;;;OAGG;IACI,OAAO,CAAC,EACb,IAAI,EACJ,SAAS,EACT,aAAiB,EACjB,MAAU,EACX,EAAE,YAAY,GAAG,IAAI;IAmEtB;;;OAGG;IACI,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI;IA2BxD;;;OAGG;IACI,gBAAgB,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,GAAG,IAAI;IAkCzD;;;OAGG;IACI,sBAAsB,CAAC,sBAAsB,EAAE,OAAO,GAAG,IAAI;IAgBpE;;;OAGG;IACI,gBAAgB,CAAC,gBAAgB,EAAE,uBAAuB,EAAE,GAAG,IAAI;IA4B1E;;;OAGG;IACI,WAAW,CAAC,IAAI,EAAE,uBAAuB,GAAG,IAAI;IAkBvD;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAkB3C;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAkB3C;;;OAGG;IACI,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAkB3C;;OAEG;IACI,KAAK,IAAI,IAAI;IAoEpB;;;OAGG;IACI,OAAO,IAAI,QAAQ,CAAC,kBAAkB,CAAC;IA6B9C;;;OAGG;IACI,qBAAqB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IAU1E;;;;;OAKG;IACI,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW;IAoBnE;;;OAGG;IACI,eAAe,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,GAAG,IAAI;IA2B5D;;;OAGG;IACI,sBAAsB,CAAC,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,GAAG,IAAI;IAwBnE;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAsC9B;;;OAGG;IACH,SAAS,CAAC,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB,CAAC;IAkE5D;;OAEG;IACH,SAAS,CAAC,2BAA2B,IAAI,IAAI;IA+B7C;;;;OAIG;IACH,SAAS,CAAC,mBAAmB,CAC3B,IAAI,EAAE,uBAAuB,EAC7B,UAAU,EAAE,uBAAuB,GAClC,IAAI;IAsBP;;;OAGG;IACH,SAAS,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAalD;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAShE;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAkEzB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IA+B/B,OAAO,CAAC,UAAU;IASlB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgBrB;;;OAGG;IACI,aAAa,IAAI,QAAQ,CAAC,YAAY,CAAC;CAoB/C"}
@@ -19,7 +19,7 @@ export declare const resample: ({ realData, antennaFactorData, antennaFactorSwit
19
19
  realOutputData: TimestampedFloat32Array;
20
20
  srcIndexCache: Uint32Array;
21
21
  };
22
- export declare const resampleMultiple: ({ antennaFactorData, antennaFactorSwitch, outputPoints, realData, maxData, minData, avgData, templateData, backgroundNoiseData }: {
22
+ export declare const resampleMultiple: ({ antennaFactorData, antennaFactorSwitch, outputPoints, realData, maxData, minData, avgData, templateData, backgroundNoiseData, fluorescenceData }: {
23
23
  antennaFactorData: Float32Array;
24
24
  antennaFactorSwitch?: boolean;
25
25
  outputPoints: number;
@@ -29,6 +29,7 @@ export declare const resampleMultiple: ({ antennaFactorData, antennaFactorSwitch
29
29
  avgData?: Float32Array;
30
30
  templateData?: Float32Array;
31
31
  backgroundNoiseData?: Float32Array;
32
+ fluorescenceData?: Map<number, number>[];
32
33
  }) => {
33
34
  realOutputData: TimestampedFloat32Array;
34
35
  maxOutputData: Float32Array;
@@ -37,6 +38,7 @@ export declare const resampleMultiple: ({ antennaFactorData, antennaFactorSwitch
37
38
  templateOutputData: Float32Array;
38
39
  backgroundNoiseOutputData: Float32Array;
39
40
  srcIndexCache: Uint32Array;
41
+ fluorescenceOutputData: Map<number, number>[];
40
42
  };
41
43
  export declare const findExceedingDatas: ({ realData, maxData, minData, templateData }: {
42
44
  realData: TimestampedFloat32Array;
@@ -53,4 +55,13 @@ export declare const findExceedingDatasIncremental: ({ realData, maxData, minDat
53
55
  endIndex: number;
54
56
  previousSegments?: TemplateOverData[];
55
57
  }) => TemplateOverData[];
58
+ /**
59
+ * 防抖函数
60
+ * @param fn 需要防抖的函数
61
+ * @param delay 延迟时间(毫秒)
62
+ * @returns 带有cancel方法的防抖函数
63
+ */
64
+ export declare const debounce: <T extends (...args: unknown[]) => unknown>(fn: T, delay?: number) => ((...args: Parameters<T>) => void) & {
65
+ cancel: () => void;
66
+ };
56
67
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGzE,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,SAMF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,oEAKtB;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,iBAAiB,EAAE,YAAY,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB,KAAG;IAAE,cAAc,EAAE,uBAAuB,CAAC;IAAC,aAAa,EAAE,WAAW,CAAA;CA6DxE,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,kIAU9B;IACD,iBAAiB,EAAE,YAAY,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,mBAAmB,CAAC,EAAE,YAAY,CAAC;CACpC,KAAG;IACF,cAAc,EAAE,uBAAuB,CAAC;IACxC,aAAa,EAAE,YAAY,CAAC;IAC5B,aAAa,EAAE,YAAY,CAAC;IAC5B,aAAa,EAAE,YAAY,CAAC;IAC5B,kBAAkB,EAAE,YAAY,CAAC;IACjC,yBAAyB,EAAE,YAAY,CAAC;IACxC,aAAa,EAAE,WAAW,CAAC;CA6I5B,CAAC;AA+IF,eAAO,MAAM,kBAAkB,GAAI,8CAKhC;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;CAC5B,KAAG,gBAAgB,EAanB,CAAC;AAGF,eAAO,MAAM,6BAA6B,GAAI,sFAQ3C;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACvC,KAAG,gBAAgB,EAgBnB,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGzE,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,EACD,QAAQ,YAAY,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,SAMF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,oEAKtB;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,iBAAiB,EAAE,YAAY,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB,KAAG;IAAE,cAAc,EAAE,uBAAuB,CAAC;IAAC,aAAa,EAAE,WAAW,CAAA;CA6DxE,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,oJAc9B;IACD,iBAAiB,EAAE,YAAY,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,mBAAmB,CAAC,EAAE,YAAY,CAAC;IACnC,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC1C,KAAG;IACF,cAAc,EAAE,uBAAuB,CAAC;IACxC,aAAa,EAAE,YAAY,CAAC;IAC5B,aAAa,EAAE,YAAY,CAAC;IAC5B,aAAa,EAAE,YAAY,CAAC;IAC5B,kBAAkB,EAAE,YAAY,CAAC;IACjC,yBAAyB,EAAE,YAAY,CAAC;IACxC,aAAa,EAAE,WAAW,CAAC;IAC3B,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAiK/C,CAAC;AA+IF,eAAO,MAAM,kBAAkB,GAAI,8CAKhC;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;CAC5B,KAAG,gBAAgB,EAanB,CAAC;AAGF,eAAO,MAAM,6BAA6B,GAAI,sFAQ3C;IACD,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACvC,KAAG,gBAAgB,EAgBnB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,CAAC,EACL,QAAO,MAAkB,KACxB,CAAC,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,IAAI,CAAA;CAqB3D,CAAC"}
@@ -15,6 +15,7 @@ export interface ProcessInput {
15
15
  export interface ProcessingConfig {
16
16
  readonly enableWaterfall: boolean;
17
17
  readonly enableMetrics: boolean;
18
+ readonly enableFluorescence: boolean;
18
19
  }
19
20
  export interface SpectrumConfig {
20
21
  maxPoints?: number;
@@ -58,6 +59,8 @@ export interface SpectrumOutputData extends SpectrumData {
58
59
  scanProgress?: number;
59
60
  processTimes?: number;
60
61
  templateOverData?: TemplateOverData[];
62
+ fluorescenceData?: Map<number, number>[];
63
+ fluorescenceMaxCount?: number;
61
64
  }
62
65
  export interface TemplateOverData {
63
66
  maxValue: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;CAChE;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,mBAAmB,CAAC,EAAE,YAAY,CAAC;IACnC,aAAa,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,aAAa,EAAE,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACvC;AAGD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/SpectrumAnalyzer/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;CAChE;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,mBAAmB,CAAC,EAAE,YAAY,CAAC;IACnC,aAAa,CAAC,EAAE,uBAAuB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,aAAa,EAAE,WAAW,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACtC,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACzC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAGD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
package/index.d.ts CHANGED
@@ -3,6 +3,6 @@ import SpectrumAnalyzer from './core/SpectrumAnalyzer';
3
3
  export type { LevelStreamConfig, LevelStreamOutputData } from './core/LevelStreamAnalyzer/types';
4
4
  export { ERROR_MESSAGES, SPECTRUM } from './core/SpectrumAnalyzer/constants';
5
5
  export { DataValidationError, IndexOutOfBoundsError, SpectrumError } from './core/SpectrumAnalyzer/errors';
6
- export type { BandwidthConfig, FrequencySegment, ProcessInput, ProcessingConfig, SpectrumConfig, SpectrumData, SpectrumOutputData, TimestampedFloat32Array } from './core/SpectrumAnalyzer/types';
6
+ export type { BandwidthConfig, FrequencySegment, ProcessInput, ProcessingConfig, ScanSegment, SpectrumConfig, SpectrumData, SpectrumOutputData, TemplateOverData, TimestampedFloat32Array } from './core/SpectrumAnalyzer/types';
7
7
  export { SpectrumAnalyzer, LevelStreamAnalyzer };
8
8
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,4BAA4B,CAAC;AAC7D,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAG7E,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACd,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,4BAA4B,CAAC;AAC7D,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAG7E,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACd,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC"}
package/index.js CHANGED
@@ -1 +1 @@
1
- const LEVEL_STREAM={DEFAULT_CACHE_TIME:15e3,DEFAULT_GRANULARITY:10,DEFAULT_RANGE:[-20,100]};const DEFAULT_LEVEL_STREAM_CONFIG={cacheTime:LEVEL_STREAM.DEFAULT_CACHE_TIME,granularity:LEVEL_STREAM.DEFAULT_GRANULARITY,range:LEVEL_STREAM.DEFAULT_RANGE,onLevelStreamUpdate:data=>{}};class LevelStreamAnalyzer{config;spectrumData=[];probabilityData=new Map;count=0;constructor(config){this.config={...DEFAULT_LEVEL_STREAM_CONFIG,...config}}reset(){this.spectrumData=[];this.probabilityData=new Map;this.count=0}setConfig(config){this.config={...DEFAULT_LEVEL_STREAM_CONFIG,...this.config,...config};if(config.cacheTime&&this.config.cacheTime!==config.cacheTime||config.granularity&&this.config.granularity!==config.granularity)this.reset();if(config.range)this.outputData()}process(level){this.removeExpiredData();this.addNewData(level);this.updateProbability(level);this.outputData()}updateProbability(level){const{granularity}=this.config;this.count++;const bin=Math.round(level/granularity)*granularity;let binCount=this.probabilityData.get(bin)||0;binCount++;this.probabilityData.set(bin,binCount)}getAll(){return this.probabilityData}removeExpiredData(){const now=Date.now();const{cacheTime}=this.config;if(cacheTime<=0){this.spectrumData=[];return}if(0===this.spectrumData.length)return;let left=0;let right=this.spectrumData.length-1;while(left<=right){const mid=Math.floor((left+right)/2);if(now-this.spectrumData[mid].timestamp>=cacheTime)left=mid+1;else right=mid-1}this.spectrumData=this.spectrumData.slice(left)}addNewData(level){const now=Date.now();this.spectrumData.push({value:level,timestamp:now})}outputData(){const{range,granularity,onLevelStreamUpdate}=this.config;const rangeMin=range[0];const rangeMax=range[1];const probabilityRangeData=new Array(Math.round((rangeMax-rangeMin)/granularity)+1).fill(0);for(const[bin,count]of this.probabilityData)if(bin>=rangeMin&&bin<=rangeMax){const index=Math.round((rangeMax-bin)/granularity);if(this.count>0)probabilityRangeData[index]=count/this.count*100}onLevelStreamUpdate?.({probabilityRangeData:new Float32Array(probabilityRangeData),spectrumData:new Float32Array(this.spectrumData.map(item=>item.value))})}}const SPECTRUM={INITIAL_VALUE:Number.NaN,WATERFALL_MAX_FRAMES:100,OUTPUT_POINTS:1001};const DEFAULT_SPECTRUM_CONFIG={maxPoints:SPECTRUM.OUTPUT_POINTS,waterfallMaxFrames:SPECTRUM.WATERFALL_MAX_FRAMES,initialValue:SPECTRUM.INITIAL_VALUE,processing:{enableWaterfall:false,enableMetrics:false},outputPoints:SPECTRUM.OUTPUT_POINTS,outputRange:{start:0,end:SPECTRUM.OUTPUT_POINTS}};const ERROR_MESSAGES={EMPTY_SEGMENTS:"频段配置不能为空",INVALID_CONFIG:"必须且只能配置 segments 或 bandwidthConfig 其中之一",EMPTY_BANDWIDTH:"bandwidthConfig 不能为空",INVALID_SEGMENT:index=>`无效的段索引: ${index}`,INDEX_OUT_OF_BOUNDS:index=>`索引超出范围: ${index}`,INVALID_ANTENNA_FACTOR_LENGTH:points=>`天线因子数据长度必须等于实时数据长度 (${points})`,INVALID_ANTENNA_FACTOR:"天线因子数据必须是有效的正数",INVALID_SAMPLING_RANGE:"采样范围无效",INVALID_MAX_POINTS:"点数必须大于0",INVALID_LENGTH:expected=>`频率占用度数据长度不匹配,期望长度为 ${expected}`};class SpectrumError extends Error{code;details;constructor(message,code,details){super(message),this.code=code,this.details=details;this.name="SpectrumError"}}class DataValidationError extends SpectrumError{constructor(message,details){super(message,"DATA_VALIDATION_ERROR",details)}}class IndexOutOfBoundsError extends SpectrumError{index;constructor(message,index,details){super(message,"INDEX_OUT_OF_BOUNDS_ERROR",{index,...details||{}}),this.index=index}}const arrayKeepAttribute=(source,target)=>{if(void 0!==source.max)target.max=source.max;if(void 0!==source.maxIndex)target.maxIndex=source.maxIndex;if(void 0!==source.timestamp)target.timestamp=source.timestamp;if(void 0!==source.progress)target.progress=source.progress};const resample=({realData,antennaFactorData,antennaFactorSwitch=false,outputPoints})=>{const realDataLength=realData.length;const isLessThanDataLength=realDataLength<=outputPoints;const outputLength=isLessThanDataLength?realDataLength:outputPoints;const srcIndexCache=new Uint32Array(outputLength);const realOutputData=new Float32Array(outputLength);realOutputData.timestamp=realData.timestamp;if(isLessThanDataLength){if(!antennaFactorSwitch){for(let i=0;i<realDataLength;i++)srcIndexCache[i]=i;realOutputData.set(realData);return{realOutputData,srcIndexCache}}for(let i=0;i<realDataLength;i++){srcIndexCache[i]=i;realOutputData[i]=realData[i]+antennaFactorData[i]}return{realOutputData,srcIndexCache}}const ratio=realDataLength/outputPoints;let pos=ratio/2;for(let i=0;i<outputLength;i++){const start=Math.floor(pos);const end=Math.min(Math.floor(pos+ratio),realDataLength);let maxValue=realData[start];let maxIndex=start;for(let j=start+1;j<end;j++)if(realData[j]>maxValue){maxValue=realData[j];maxIndex=j}realOutputData[i]=antennaFactorSwitch?maxValue+antennaFactorData[maxIndex]:maxValue;srcIndexCache[i]=maxIndex;pos+=ratio}return{realOutputData,srcIndexCache}};const resampleMultiple=({antennaFactorData,antennaFactorSwitch=false,outputPoints,realData,maxData,minData,avgData,templateData,backgroundNoiseData})=>{const realDataLength=realData.length;const isLessThanDataLength=realDataLength<=outputPoints;const outputLength=isLessThanDataLength?realDataLength:outputPoints;const hasMaxData=maxData&&maxData.length>0;const hasMinData=minData&&minData.length>0;const hasAvgData=avgData&&avgData.length>0;const hasTemplateData=templateData&&templateData.length>0;const hasBackgroundNoiseData=backgroundNoiseData&&backgroundNoiseData.length>0;const applyAntennaFactor=antennaFactorSwitch?(value,index)=>value+antennaFactorData[index]:value=>value;const realOutputData=new Float32Array(outputLength);realOutputData.timestamp=realData.timestamp;const maxOutputData=new Float32Array(hasMaxData?outputLength:0);const minOutputData=new Float32Array(hasMinData?outputLength:0);const avgOutputData=new Float32Array(hasAvgData?outputLength:0);const templateOutputData=new Float32Array(hasTemplateData?outputLength:0);const backgroundNoiseOutputData=new Float32Array(hasBackgroundNoiseData?outputLength:0);const srcIndexCache=new Uint32Array(outputLength);if(isLessThanDataLength){for(let i=0;i<realDataLength;i++)srcIndexCache[i]=i;if(antennaFactorSwitch)for(let i=0;i<realDataLength;i++){const antennaFactor=antennaFactorData[i];realOutputData[i]=realData[i]+antennaFactor;if(hasMaxData)maxOutputData[i]=maxData[i]+antennaFactor;if(hasMinData)minOutputData[i]=minData[i]+antennaFactor;if(hasAvgData)avgOutputData[i]=avgData[i]+antennaFactor;if(hasTemplateData)templateOutputData[i]=templateData[i]+antennaFactor;if(hasBackgroundNoiseData)backgroundNoiseOutputData[i]=backgroundNoiseData[i]+antennaFactor}else{realOutputData.set(realData);if(hasMaxData)maxOutputData.set(maxData);if(hasMinData)minOutputData.set(minData);if(hasAvgData)avgOutputData.set(avgData);if(hasTemplateData)templateOutputData.set(templateData);if(hasBackgroundNoiseData)backgroundNoiseOutputData.set(backgroundNoiseData)}}else{const ratio=realDataLength/outputPoints;let pos=ratio/2;for(let i=0;i<outputLength;i++){const start=Math.floor(pos);const end=Math.min(Math.floor(pos+ratio),realDataLength);let realMaxVal=realData[start];let realMaxIdx=start;let maxVal=hasMaxData?maxData[start]:0;let maxIdx=start;let minVal=hasMinData?minData[start]:0;let minIdx=start;for(let j=start+1;j<end;j++){if(realData[j]>realMaxVal){realMaxVal=realData[j];realMaxIdx=j}if(hasMaxData&&maxData[j]>maxVal){maxVal=maxData[j];maxIdx=j}if(hasMinData&&minData[j]<minVal){minVal=minData[j];minIdx=j}}realOutputData[i]=applyAntennaFactor(realMaxVal,realMaxIdx);srcIndexCache[i]=realMaxIdx;if(hasMaxData)maxOutputData[i]=applyAntennaFactor(maxVal,maxIdx);if(hasMinData)minOutputData[i]=applyAntennaFactor(minVal,minIdx);if(hasAvgData){const avgValue=avgData[realMaxIdx];avgOutputData[i]=applyAntennaFactor(avgValue,realMaxIdx)}if(hasTemplateData){const templateValue=templateData[realMaxIdx];templateOutputData[i]=applyAntennaFactor(templateValue,realMaxIdx)}if(hasBackgroundNoiseData){const backgroundNoiseValue=backgroundNoiseData[realMaxIdx];backgroundNoiseOutputData[i]=applyAntennaFactor(backgroundNoiseValue,realMaxIdx)}pos+=ratio}}return{realOutputData,maxOutputData,minOutputData,avgOutputData,templateOutputData,backgroundNoiseOutputData,srcIndexCache}};const findExceedingDatasCore=({realData,maxData,minData,templateData,startIndex=0,endIndex,usePreallocation=false})=>{const{timestamp}=realData;const actualEndIndex=endIndex??realData.length;const segments=[];let start=-1;let sum=0;let count=0;if(usePreallocation&&0===startIndex&&actualEndIndex===realData.length){const exceedingFlags=new Uint8Array(realData.length);for(let i=0;i<realData.length;i++)exceedingFlags[i]=realData[i]>templateData[i]?1:0;for(let i=0;i<realData.length;i++)if(exceedingFlags[i]){if(-1===start){start=i;sum=realData[i];count=1}else{sum+=realData[i];count++}}else if(-1!==start){const end=i-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}start=-1}}else for(let i=startIndex;i<Math.min(actualEndIndex,realData.length);i++){const isExceeding=realData[i]>templateData[i];if(isExceeding){if(-1===start){start=i;sum=realData[i];count=1}else{sum+=realData[i];count++}}else if(-1!==start){const end=i-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}start=-1}}if(-1!==start){const end=actualEndIndex-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}}return segments};const findExceedingDatas=({realData,maxData,minData,templateData})=>{if(!templateData||0===templateData.length)return[];return findExceedingDatasCore({realData,maxData,minData,templateData,usePreallocation:true})};const findExceedingDatasIncremental=({realData,maxData,minData,templateData,startIndex,endIndex,previousSegments=[]})=>{if(!templateData||0===templateData.length||startIndex>=endIndex)return previousSegments;const newSegments=findExceedingDatasCore({realData,maxData,minData,templateData,startIndex,endIndex});return[...previousSegments,...newSegments]};class SpectrumAnalyzer{config=DEFAULT_SPECTRUM_CONFIG;segments=[];antennaFactorData;antennaFactorSwitch;realData;maxData;minData;avgData;templateData;backgroundNoiseData;waterfallData;srcIndexCache;realOutputData;waterfallOutputData;scanProgress;lastIndex;processTimes;lastProcessTime;cachedExceedingDatas=[];onSpectrumUpdate;constructor(config){const{onSpectrumUpdate,maxPoints}=config;this.onSpectrumUpdate=onSpectrumUpdate;this.config={...DEFAULT_SPECTRUM_CONFIG,...config,processing:{...DEFAULT_SPECTRUM_CONFIG.processing,...config.processing},outputRange:{start:config.outputRange?.start??0,end:config.outputRange?.end??maxPoints??SPECTRUM.OUTPUT_POINTS}};this.reset()}process({data,timestamp,segmentOffset=0,offset=0}){const processStartTime=performance.now();try{if(!data?.length)return;let index=offset;if(this.segments.length){const segment=this.segments[segmentOffset];if(!segment)throw new DataValidationError(ERROR_MESSAGES.INVALID_SEGMENT(segmentOffset));index=segment.startIndex+offset;this.scanProgress=(index+data.length)/this.config.maxPoints}else if(data.length!==this.config.maxPoints)this.updateMaxPoints(data.length);this.validateInput(data,index);const{maxPoints}=this.config;const endIndex=index+data.length;this.lastIndex=endIndex;const isOver=endIndex>=maxPoints||endIndex<this.lastIndex;this.realData.set(data,index);this.realData.timestamp=timestamp;this.processDataPoints(data,index,isOver);const processedData=this.resampleDataSeries();if(isOver)this.updateWaterfallData(this.realData,processedData.realData);const templateOverData=this.updateTemplateOverData(isOver,isOver?void 0:{startIndex:index,endIndex:index+data.length});this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData,scanProgress:this.scanProgress,processTimes:this.processTimes,...templateOverData&&{templateOverData}})}catch(error){throw error instanceof Error?error:new Error(String(error))}finally{this.lastProcessTime=performance.now()-processStartTime}}initializeSegments(segments){if(!segments?.length)throw new DataValidationError(ERROR_MESSAGES.EMPTY_SEGMENTS);let totalPoints=0;this.segments=segments.map(segment=>{const{startFrequency,stopFrequency,stepFrequency}=segment;const pointCount=Math.floor((stopFrequency-startFrequency)*1e3/stepFrequency)+1;const startIndex=totalPoints;totalPoints+=pointCount;return{startFrequency,stopFrequency,stepFrequency,pointCount,startIndex}});this.updateMaxPoints(totalPoints)}setAntennaFactor(d){const{antennaFactorData,config:{maxPoints}}=this;let data=new Float32Array(d);if(data.length!==maxPoints){data=new Float32Array(data).subarray(0,maxPoints);console.warn(ERROR_MESSAGES.INVALID_ANTENNA_FACTOR_LENGTH(maxPoints))}let hasInvalid=false;for(let i=0;i<data.length;i++){const value=data[i];const isNiceFinite=Number.isFinite(value)&&value>0;if(!isNiceFinite)hasInvalid=true;antennaFactorData[i]=isNiceFinite?value:0}if(hasInvalid)console.warn(ERROR_MESSAGES.INVALID_ANTENNA_FACTOR);if(this.antennaFactorSwitch)this.setAntennaFactorSwitch(this.antennaFactorSwitch)}setAntennaFactorSwitch(newAntennaFactorSwitch){const isChange=this.antennaFactorSwitch!==newAntennaFactorSwitch;this.antennaFactorSwitch=newAntennaFactorSwitch;if(isChange){const processedData=this.resampleDataSeries();this.resampleWaterfallOutputData();this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData})}}setWaterfallData(newWaterfallData){if(!(newWaterfallData?.[0]?.length>0))return;const{processing}=this.config;let{waterfallMaxFrames}=this.config;if(!processing.enableWaterfall)return;waterfallMaxFrames=newWaterfallData.length;this.config={...this.config,waterfallMaxFrames};this.waterfallData=newWaterfallData;this.resampleWaterfallOutputData();this.notifySpectrumUpdate({waterfallData:this.waterfallOutputData})}setRealData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);newData.timestamp=data.timestamp;this.realData=newData;const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setMaxData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.maxData=newData;this.maxData.allValid=this.isAllValid(this.maxData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setMinData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.minData=newData;this.minData.allValid=this.isAllValid(this.minData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setAvgData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.avgData=newData;this.avgData.allValid=this.isAllValid(this.avgData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}reset(){const{maxPoints,waterfallMaxFrames,processing:{enableMetrics}}=this.config;this.antennaFactorData=new Float32Array(maxPoints);const realData=new Float32Array(maxPoints);realData.timestamp=0;realData.fill(SPECTRUM.INITIAL_VALUE);this.realData=realData;this.maxData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.minData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.avgData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.templateData=new Float32Array;this.backgroundNoiseData=new Float32Array;this.waterfallData=Array.from({length:waterfallMaxFrames},()=>{const frame=new Float32Array;frame.timestamp=0;frame.fill(SPECTRUM.INITIAL_VALUE);return frame});this.waterfallOutputData=Array.from({length:waterfallMaxFrames},()=>{const frame=new Float32Array;frame.timestamp=0;frame.fill(SPECTRUM.INITIAL_VALUE);return frame});this.srcIndexCache=new Uint32Array(maxPoints);this.scanProgress=0;this.lastIndex=0;this.processTimes=0;this.lastProcessTime=0;this.cachedExceedingDatas=[];this.notifySpectrumUpdate({realData:this.realData,maxData:this.maxData,minData:this.minData,avgData:this.avgData,templateData:this.templateData,backgroundNoiseData:this.backgroundNoiseData,srcIndexCache:this.srcIndexCache,processTimes:0,scanProgress:0})}getData(){const{realData,maxData,minData,avgData,templateData,backgroundNoiseData,srcIndexCache,waterfallData,processTimes}=this;return{realData,maxData,minData,avgData,templateData,backgroundNoiseData,srcIndexCache,waterfallData,processTimes}}getPerformanceMetrics(){return{lastProcessTime:this.lastProcessTime,dataPoints:this.config.maxPoints,waterfallFrames:this.waterfallData.length,isInitialized:this.realData.length>0,memoryUsage:this.calculateMemoryUsage()}}updateSamplingRange(start,end){if(start<0||start>=end)throw new DataValidationError(ERROR_MESSAGES.INVALID_SAMPLING_RANGE);this.config={...this.config,outputRange:{start:Math.floor(start),end:Math.ceil(end)}};const processedData=this.resampleDataSeries();this.resampleWaterfallOutputData();this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData});return this.srcIndexCache}setTemplateData(data){if(!data||0===data.length){this.templateData=new Float32Array(0);return}this.templateData=new Float32Array(data);const{srcIndexCache,config:{outputPoints}}=this;const templateOutputData=new Float32Array(outputPoints);for(let i=0;i<outputPoints;i++)templateOutputData[i]=this.templateData[srcIndexCache[i]];const templateOverData=this.updateTemplateOverData(true);this.notifySpectrumUpdate({templateData:templateOutputData,templateOverData})}setBackgroundNoiseData(data){if(!data||0===data.length){this.backgroundNoiseData=new Float32Array(0);return}this.backgroundNoiseData=new Float32Array(data);const{srcIndexCache,config:{outputPoints}}=this;const backgroundNoiseOutputData=new Float32Array(outputPoints);for(let i=0;i<outputPoints;i++)backgroundNoiseOutputData[i]=this.backgroundNoiseData[srcIndexCache[i]];this.notifySpectrumUpdate({backgroundNoiseData:backgroundNoiseOutputData})}updateTemplateOverData(forceFullCalculation=false,incrementalRange){if(!this.templateData||0===this.templateData.length){this.cachedExceedingDatas=[];return[]}let templateOverData;if(forceFullCalculation||!incrementalRange){templateOverData=findExceedingDatas({realData:this.realData,maxData:this.maxData,minData:this.minData,templateData:this.templateData});this.cachedExceedingDatas=templateOverData}else{this.cachedExceedingDatas=findExceedingDatasIncremental({realData:this.realData,maxData:this.maxData,minData:this.minData,templateData:this.templateData,startIndex:incrementalRange.startIndex,endIndex:incrementalRange.endIndex,previousSegments:this.cachedExceedingDatas});templateOverData=this.cachedExceedingDatas}return templateOverData}resampleDataSeries(){const{antennaFactorData,antennaFactorSwitch,maxData,minData,avgData,templateData,backgroundNoiseData,config:{maxPoints,outputPoints}}=this;const activeAntennaFactorData=antennaFactorSwitch?antennaFactorData:new Float32Array(maxPoints);const{realOutputData,srcIndexCache,maxOutputData,minOutputData,avgOutputData,templateOutputData,backgroundNoiseOutputData}=resampleMultiple({antennaFactorData:activeAntennaFactorData,antennaFactorSwitch,outputPoints,realData:this.getOutputData(),maxData:maxData&&maxData.length>0?this.getOutputData(maxData):void 0,minData:minData&&minData.length>0?this.getOutputData(minData):void 0,avgData:avgData&&avgData.length>0?this.getOutputData(avgData):void 0,templateData:templateData&&templateData.length>0?this.getOutputData(templateData):void 0,backgroundNoiseData:backgroundNoiseData&&backgroundNoiseData.length>0?this.getOutputData(backgroundNoiseData):void 0});this.srcIndexCache=srcIndexCache;return{realData:realOutputData,maxData:maxOutputData,minData:minOutputData,avgData:avgOutputData,templateData:templateOutputData,backgroundNoiseData:backgroundNoiseOutputData,srcIndexCache}}resampleWaterfallOutputData(){const{antennaFactorData,antennaFactorSwitch,waterfallData,config:{maxPoints,processing:{enableWaterfall},outputPoints}}=this;if(!enableWaterfall)return;const activeAntennaFactorData=antennaFactorSwitch?antennaFactorData:new Float32Array(maxPoints);this.waterfallOutputData=waterfallData.map(frame=>{const realData=this.getOutputData(frame);const{realOutputData}=resampleMultiple({realData,antennaFactorData:activeAntennaFactorData,antennaFactorSwitch,outputPoints});return realOutputData})}updateWaterfallData(data,outputData){const{waterfallMaxFrames,processing}=this.config;if(!processing.enableWaterfall)return;if(this.waterfallData.length>=waterfallMaxFrames){this.waterfallData.shift();this.waterfallOutputData.shift()}const newData=new Float32Array(data.length);newData.set(data);newData.timestamp=data.timestamp;this.waterfallData.push(newData);this.waterfallOutputData.push(outputData)}updateMaxPoints(maxPoints){if(this.config.maxPoints===maxPoints)return;this.config={...this.config,maxPoints,outputRange:{start:0,end:maxPoints}};this.reset()}validateInput(data,index){if(index<0||index+data.length>this.config.maxPoints)throw new IndexOutOfBoundsError(ERROR_MESSAGES.INDEX_OUT_OF_BOUNDS(index),index)}processDataPoints(data,index,isOver){const{maxData,minData,avgData,config:{processing:{enableMetrics}}}=this;if(!enableMetrics)return;if(isOver){this.processTimes+=1;if(!maxData.allValid)maxData.allValid=this.isAllValid(maxData);if(!minData.allValid)minData.allValid=this.isAllValid(minData);if(!avgData.allValid)avgData.allValid=this.isAllValid(avgData)}let allValid=true;const length=data.length;for(let i=0;i<length;i++)if(!Number.isFinite(data[i])){allValid=false;break}for(let i=0;i<length;i++){const dataIndex=index+i;const value=data[i];if(!allValid&&!Number.isFinite(value))continue;const oldMax=maxData[dataIndex];if(value>oldMax||!maxData.allValid&&Number.isNaN(oldMax))maxData[dataIndex]=value;const oldMin=minData[dataIndex];if(value<oldMin||!minData.allValid&&Number.isNaN(oldMin))minData[dataIndex]=value;const oldAvg=avgData[dataIndex];if(0===this.processTimes||Number.isNaN(oldAvg))avgData[dataIndex]=value;else avgData[dataIndex]=oldAvg+(value-oldAvg)/(this.processTimes+1)}}isAllValid(arr){for(let i=0;i<arr.length;i++)if(!Number.isFinite(arr[i]))return false;return true}notifySpectrumUpdate(processedData){this.onSpectrumUpdate?.(processedData)}calculateMemoryUsage(){const arraySize=this.config.maxPoints*Float32Array.BYTES_PER_ELEMENT;const baseArrayMemory=4*arraySize;const rawWaterfallMemory=this.waterfallData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);const outputWaterfallMemory=this.waterfallOutputData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);return baseArrayMemory+rawWaterfallMemory+outputWaterfallMemory}getOutputData(data){const{outputRange:{start,end}}=this.config;const sourceData=data??this.realData;const outputData=sourceData.subarray(start,end);outputData.timestamp=sourceData.timestamp;return outputData}getAllRawData(){const{realData,maxData,minData,avgData,templateData,backgroundNoiseData,waterfallData}=this;return{realData,maxData,minData,avgData,templateData,backgroundNoiseData,waterfallData}}}export{DataValidationError,ERROR_MESSAGES,IndexOutOfBoundsError,LevelStreamAnalyzer,SPECTRUM,SpectrumAnalyzer,SpectrumError};
1
+ const LEVEL_STREAM={DEFAULT_CACHE_TIME:15e3,DEFAULT_GRANULARITY:10,DEFAULT_RANGE:[-20,100]};const DEFAULT_LEVEL_STREAM_CONFIG={cacheTime:LEVEL_STREAM.DEFAULT_CACHE_TIME,granularity:LEVEL_STREAM.DEFAULT_GRANULARITY,range:LEVEL_STREAM.DEFAULT_RANGE,onLevelStreamUpdate:data=>{}};class LevelStreamAnalyzer{config;spectrumData=[];probabilityData=new Map;count=0;constructor(config){this.config={...DEFAULT_LEVEL_STREAM_CONFIG,...config}}reset(){this.spectrumData=[];this.probabilityData=new Map;this.count=0}setConfig(config){this.config={...DEFAULT_LEVEL_STREAM_CONFIG,...this.config,...config};if(config.cacheTime&&this.config.cacheTime!==config.cacheTime||config.granularity&&this.config.granularity!==config.granularity)this.reset();if(config.range)this.outputData()}process(level){this.removeExpiredData();this.addNewData(level);this.updateProbability(level);this.outputData()}updateProbability(level){const{granularity}=this.config;this.count++;const bin=Math.round(level/granularity)*granularity;let binCount=this.probabilityData.get(bin)||0;binCount++;this.probabilityData.set(bin,binCount)}getAll(){return this.probabilityData}removeExpiredData(){const now=Date.now();const{cacheTime}=this.config;if(cacheTime<=0){this.spectrumData=[];return}if(0===this.spectrumData.length)return;let left=0;let right=this.spectrumData.length-1;while(left<=right){const mid=Math.floor((left+right)/2);if(now-this.spectrumData[mid].timestamp>=cacheTime)left=mid+1;else right=mid-1}this.spectrumData=this.spectrumData.slice(left)}addNewData(level){const now=Date.now();this.spectrumData.push({value:level,timestamp:now})}outputData(){const{range,granularity,onLevelStreamUpdate}=this.config;const rangeMin=range[0];const rangeMax=range[1];const probabilityRangeData=new Array(Math.round((rangeMax-rangeMin)/granularity)+1).fill(0);for(const[bin,count]of this.probabilityData)if(bin>=rangeMin&&bin<=rangeMax){const index=Math.round((rangeMax-bin)/granularity);if(this.count>0)probabilityRangeData[index]=count/this.count*100}onLevelStreamUpdate?.({probabilityRangeData:new Float32Array(probabilityRangeData),spectrumData:new Float32Array(this.spectrumData.map(item=>item.value))})}}const SPECTRUM={INITIAL_VALUE:Number.NaN,WATERFALL_MAX_FRAMES:100,OUTPUT_POINTS:1001};const DEFAULT_SPECTRUM_CONFIG={maxPoints:SPECTRUM.OUTPUT_POINTS,waterfallMaxFrames:SPECTRUM.WATERFALL_MAX_FRAMES,initialValue:SPECTRUM.INITIAL_VALUE,processing:{enableWaterfall:false,enableMetrics:false,enableFluorescence:false},outputPoints:SPECTRUM.OUTPUT_POINTS,outputRange:{start:0,end:SPECTRUM.OUTPUT_POINTS}};const ERROR_MESSAGES={EMPTY_SEGMENTS:"频段配置不能为空",INVALID_CONFIG:"必须且只能配置 segments 或 bandwidthConfig 其中之一",EMPTY_BANDWIDTH:"bandwidthConfig 不能为空",INVALID_SEGMENT:index=>`无效的段索引: ${index}`,INDEX_OUT_OF_BOUNDS:index=>`索引超出范围: ${index}`,INVALID_ANTENNA_FACTOR_LENGTH:points=>`天线因子数据长度必须等于实时数据长度 (${points})`,INVALID_ANTENNA_FACTOR:"天线因子数据必须是有效的正数",INVALID_SAMPLING_RANGE:"采样范围无效",INVALID_MAX_POINTS:"点数必须大于0",INVALID_LENGTH:expected=>`频率占用度数据长度不匹配,期望长度为 ${expected}`};class SpectrumError extends Error{code;details;constructor(message,code,details){super(message),this.code=code,this.details=details;this.name="SpectrumError"}}class DataValidationError extends SpectrumError{constructor(message,details){super(message,"DATA_VALIDATION_ERROR",details)}}class IndexOutOfBoundsError extends SpectrumError{index;constructor(message,index,details){super(message,"INDEX_OUT_OF_BOUNDS_ERROR",{index,...details||{}}),this.index=index}}const arrayKeepAttribute=(source,target)=>{if(void 0!==source.max)target.max=source.max;if(void 0!==source.maxIndex)target.maxIndex=source.maxIndex;if(void 0!==source.timestamp)target.timestamp=source.timestamp;if(void 0!==source.progress)target.progress=source.progress};const resample=({realData,antennaFactorData,antennaFactorSwitch=false,outputPoints})=>{const realDataLength=realData.length;const isLessThanDataLength=realDataLength<=outputPoints;const outputLength=isLessThanDataLength?realDataLength:outputPoints;const srcIndexCache=new Uint32Array(outputLength);const realOutputData=new Float32Array(outputLength);realOutputData.timestamp=realData.timestamp;if(isLessThanDataLength){if(!antennaFactorSwitch){for(let i=0;i<realDataLength;i++)srcIndexCache[i]=i;realOutputData.set(realData);return{realOutputData,srcIndexCache}}for(let i=0;i<realDataLength;i++){srcIndexCache[i]=i;realOutputData[i]=realData[i]+antennaFactorData[i]}return{realOutputData,srcIndexCache}}const ratio=realDataLength/outputPoints;let pos=ratio/2;for(let i=0;i<outputLength;i++){const start=Math.floor(pos);const end=Math.min(Math.floor(pos+ratio),realDataLength);let maxValue=realData[start];let maxIndex=start;for(let j=start+1;j<end;j++)if(realData[j]>maxValue){maxValue=realData[j];maxIndex=j}realOutputData[i]=antennaFactorSwitch?maxValue+antennaFactorData[maxIndex]:maxValue;srcIndexCache[i]=maxIndex;pos+=ratio}return{realOutputData,srcIndexCache}};const resampleMultiple=({antennaFactorData,antennaFactorSwitch=false,outputPoints,realData,maxData,minData,avgData,templateData,backgroundNoiseData,fluorescenceData})=>{const realDataLength=realData.length;const isLessThanDataLength=realDataLength<=outputPoints;const outputLength=isLessThanDataLength?realDataLength:outputPoints;const hasMaxData=maxData&&maxData.length>0;const hasMinData=minData&&minData.length>0;const hasAvgData=avgData&&avgData.length>0;const hasTemplateData=templateData&&templateData.length>0;const hasBackgroundNoiseData=backgroundNoiseData&&backgroundNoiseData.length>0;const hasFluorescenceStats=fluorescenceData&&fluorescenceData.length>0;const applyAntennaFactor=antennaFactorSwitch?(value,index)=>value+antennaFactorData[index]:value=>value;const realOutputData=new Float32Array(outputLength);realOutputData.timestamp=realData.timestamp;const maxOutputData=new Float32Array(hasMaxData?outputLength:0);const minOutputData=new Float32Array(hasMinData?outputLength:0);const avgOutputData=new Float32Array(hasAvgData?outputLength:0);const templateOutputData=new Float32Array(hasTemplateData?outputLength:0);const backgroundNoiseOutputData=new Float32Array(hasBackgroundNoiseData?outputLength:0);let fluorescenceOutputData=new Array(hasFluorescenceStats?outputLength:0).fill(null);const srcIndexCache=new Uint32Array(outputLength);if(isLessThanDataLength){for(let i=0;i<realDataLength;i++)srcIndexCache[i]=i;if(antennaFactorSwitch)for(let i=0;i<realDataLength;i++){const antennaFactor=antennaFactorData[i];realOutputData[i]=realData[i]+antennaFactor;if(hasMaxData)maxOutputData[i]=maxData[i]+antennaFactor;if(hasMinData)minOutputData[i]=minData[i]+antennaFactor;if(hasAvgData)avgOutputData[i]=avgData[i]+antennaFactor;if(hasTemplateData)templateOutputData[i]=templateData[i]+antennaFactor;if(hasBackgroundNoiseData)backgroundNoiseOutputData[i]=backgroundNoiseData[i]+antennaFactor}else{realOutputData.set(realData);if(hasMaxData)maxOutputData.set(maxData);if(hasMinData)minOutputData.set(minData);if(hasAvgData)avgOutputData.set(avgData);if(hasTemplateData)templateOutputData.set(templateData);if(hasBackgroundNoiseData)backgroundNoiseOutputData.set(backgroundNoiseData)}if(hasFluorescenceStats)fluorescenceOutputData=fluorescenceData}else{const ratio=realDataLength/outputPoints;let pos=ratio/2;for(let i=0;i<outputLength;i++){const start=Math.floor(pos);const end=Math.min(Math.floor(pos+ratio),realDataLength);let realMaxVal=realData[start];let realMaxIdx=start;let maxVal=hasMaxData?maxData[start]:0;let maxIdx=start;let minVal=hasMinData?minData[start]:0;let minIdx=start;for(let j=start+1;j<end;j++){if(realData[j]>realMaxVal){realMaxVal=realData[j];realMaxIdx=j}if(hasMaxData&&maxData[j]>maxVal){maxVal=maxData[j];maxIdx=j}if(hasMinData&&minData[j]<minVal){minVal=minData[j];minIdx=j}}realOutputData[i]=applyAntennaFactor(realMaxVal,realMaxIdx);srcIndexCache[i]=realMaxIdx;if(hasMaxData)maxOutputData[i]=applyAntennaFactor(maxVal,maxIdx);if(hasMinData)minOutputData[i]=applyAntennaFactor(minVal,minIdx);if(hasAvgData){const avgValue=avgData[realMaxIdx];avgOutputData[i]=applyAntennaFactor(avgValue,realMaxIdx)}if(hasTemplateData){const templateValue=templateData[realMaxIdx];templateOutputData[i]=applyAntennaFactor(templateValue,realMaxIdx)}if(hasBackgroundNoiseData){const backgroundNoiseValue=backgroundNoiseData[realMaxIdx];backgroundNoiseOutputData[i]=applyAntennaFactor(backgroundNoiseValue,realMaxIdx)}if(hasFluorescenceStats)fluorescenceOutputData[i]=fluorescenceData[realMaxIdx];pos+=ratio}}return{realOutputData,maxOutputData,minOutputData,avgOutputData,templateOutputData,backgroundNoiseOutputData,srcIndexCache,fluorescenceOutputData}};const findExceedingDatasCore=({realData,maxData,minData,templateData,startIndex=0,endIndex,usePreallocation=false})=>{const{timestamp}=realData;const actualEndIndex=endIndex??realData.length;const segments=[];let start=-1;let sum=0;let count=0;if(usePreallocation&&0===startIndex&&actualEndIndex===realData.length){const exceedingFlags=new Uint8Array(realData.length);for(let i=0;i<realData.length;i++)exceedingFlags[i]=realData[i]>templateData[i]?1:0;for(let i=0;i<realData.length;i++)if(exceedingFlags[i]){if(-1===start){start=i;sum=realData[i];count=1}else{sum+=realData[i];count++}}else if(-1!==start){const end=i-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}start=-1}}else for(let i=startIndex;i<Math.min(actualEndIndex,realData.length);i++){const isExceeding=realData[i]>templateData[i];if(isExceeding){if(-1===start){start=i;sum=realData[i];count=1}else{sum+=realData[i];count++}}else if(-1!==start){const end=i-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}start=-1}}if(-1!==start){const end=actualEndIndex-1;if(end-start+1>=2){const avgValue=sum/count;const middleIndex=start+(end-start>>1);segments.push({maxValue:maxData[middleIndex],minValue:minData[middleIndex],avgValue,timestamp,startIndex:start,endIndex:end})}}return segments};const findExceedingDatas=({realData,maxData,minData,templateData})=>{if(!templateData||0===templateData.length)return[];return findExceedingDatasCore({realData,maxData,minData,templateData,usePreallocation:true})};const findExceedingDatasIncremental=({realData,maxData,minData,templateData,startIndex,endIndex,previousSegments=[]})=>{if(!templateData||0===templateData.length||startIndex>=endIndex)return previousSegments;const newSegments=findExceedingDatasCore({realData,maxData,minData,templateData,startIndex,endIndex});return[...previousSegments,...newSegments]};const debounce=(fn,delay=1e3/30)=>{let timer=null;const debouncedFunction=(...args)=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>{fn(...args)},delay)};debouncedFunction.cancel=()=>{if(timer){clearTimeout(timer);timer=null}};return debouncedFunction};class SpectrumAnalyzer{config=DEFAULT_SPECTRUM_CONFIG;segments=[];antennaFactorData;antennaFactorSwitch;realData;maxData;minData;avgData;templateData;backgroundNoiseData;waterfallData;srcIndexCache;realOutputData;waterfallOutputData;scanProgress;lastIndex;processTimes;lastProcessTime;cachedExceedingDatas=[];fluorescenceData;fluorescenceMaxCount=0;onSpectrumUpdate;constructor(config){const{onSpectrumUpdate,maxPoints}=config;this.onSpectrumUpdate=onSpectrumUpdate;this.config={...DEFAULT_SPECTRUM_CONFIG,...config,processing:{...DEFAULT_SPECTRUM_CONFIG.processing,...config.processing},outputRange:{start:config.outputRange?.start??0,end:config.outputRange?.end??maxPoints??SPECTRUM.OUTPUT_POINTS}};this.reset()}process({data,timestamp,segmentOffset=0,offset=0}){const processStartTime=performance.now();try{if(!data?.length)return;let index=offset;if(this.segments.length){const segment=this.segments[segmentOffset];if(!segment)throw new DataValidationError(ERROR_MESSAGES.INVALID_SEGMENT(segmentOffset));index=segment.startIndex+offset;this.scanProgress=(index+data.length)/this.config.maxPoints}else if(data.length!==this.config.maxPoints)this.updateMaxPoints(data.length);this.validateInput(data,index);const{maxPoints}=this.config;const endIndex=index+data.length;this.lastIndex=endIndex;const isOver=endIndex>=maxPoints||endIndex<this.lastIndex;this.realData.set(data,index);this.realData.timestamp=timestamp;this.processDataPoints(data,index,isOver);const processedData=this.resampleDataSeries();if(isOver)this.updateWaterfallData(this.realData,processedData.realData);const templateOverData=this.updateTemplateOverData(isOver,isOver?void 0:{startIndex:index,endIndex:index+data.length});this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData,scanProgress:this.scanProgress,processTimes:this.processTimes,...templateOverData&&{templateOverData}})}catch(error){throw error instanceof Error?error:new Error(String(error))}finally{this.lastProcessTime=performance.now()-processStartTime}}initializeSegments(segments){if(!segments?.length)throw new DataValidationError(ERROR_MESSAGES.EMPTY_SEGMENTS);let totalPoints=0;this.segments=segments.map(segment=>{const{startFrequency,stopFrequency,stepFrequency}=segment;const frequencyRange=stopFrequency-startFrequency;const stepCount=1e3*frequencyRange/stepFrequency;const pointCount=Math.round(stepCount)+1;const startIndex=totalPoints;totalPoints+=pointCount;return{startFrequency,stopFrequency,stepFrequency,pointCount,startIndex}});this.updateMaxPoints(totalPoints)}setAntennaFactor(d){const{antennaFactorData,config:{maxPoints}}=this;let data=new Float32Array(d);if(data.length!==maxPoints){data=new Float32Array(data).subarray(0,maxPoints);console.warn(ERROR_MESSAGES.INVALID_ANTENNA_FACTOR_LENGTH(maxPoints))}let hasInvalid=false;for(let i=0;i<data.length;i++){const value=data[i];const isNiceFinite=Number.isFinite(value)&&value>0;if(!isNiceFinite)hasInvalid=true;antennaFactorData[i]=isNiceFinite?value:0}if(hasInvalid)console.warn(ERROR_MESSAGES.INVALID_ANTENNA_FACTOR);if(this.antennaFactorSwitch)this.setAntennaFactorSwitch(this.antennaFactorSwitch)}setAntennaFactorSwitch(newAntennaFactorSwitch){const isChange=this.antennaFactorSwitch!==newAntennaFactorSwitch;this.antennaFactorSwitch=newAntennaFactorSwitch;if(isChange){const processedData=this.resampleDataSeries();this.resampleWaterfallOutputData();this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData})}}setWaterfallData(newWaterfallData){if(!(newWaterfallData?.[0]?.length>0))return;const{processing}=this.config;let{waterfallMaxFrames}=this.config;if(!processing.enableWaterfall)return;waterfallMaxFrames=newWaterfallData.length;this.config={...this.config,waterfallMaxFrames};this.waterfallData=newWaterfallData;this.resampleWaterfallOutputData();this.notifySpectrumUpdate({waterfallData:this.waterfallOutputData})}setRealData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);newData.timestamp=data.timestamp;this.realData=newData;const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setMaxData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.maxData=newData;this.maxData.allValid=this.isAllValid(this.maxData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setMinData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.minData=newData;this.minData.allValid=this.isAllValid(this.minData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}setAvgData(data){if(!(data?.length>0)||data.length!==this.config.maxPoints)return;const newData=new Float32Array(data.length);newData.set(data);this.avgData=newData;this.avgData.allValid=this.isAllValid(this.avgData);const processedData=this.resampleDataSeries();this.notifySpectrumUpdate(processedData)}reset(){const{maxPoints,waterfallMaxFrames,processing:{enableMetrics,enableFluorescence}}=this.config;this.antennaFactorData=new Float32Array(maxPoints);const realData=new Float32Array(maxPoints);realData.timestamp=0;realData.fill(SPECTRUM.INITIAL_VALUE);this.realData=realData;this.maxData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.minData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.avgData=new Float32Array(enableMetrics?maxPoints:0).fill(SPECTRUM.INITIAL_VALUE);this.templateData=new Float32Array;this.backgroundNoiseData=new Float32Array;this.waterfallData=Array.from({length:waterfallMaxFrames},()=>{const frame=new Float32Array;frame.timestamp=0;frame.fill(SPECTRUM.INITIAL_VALUE);return frame});this.waterfallOutputData=Array.from({length:waterfallMaxFrames},()=>{const frame=new Float32Array;frame.timestamp=0;frame.fill(SPECTRUM.INITIAL_VALUE);return frame});this.fluorescenceData=enableFluorescence?Array.from({length:maxPoints},()=>new Map):[];this.fluorescenceMaxCount=0;this.srcIndexCache=new Uint32Array(maxPoints);this.scanProgress=0;this.lastIndex=0;this.processTimes=0;this.lastProcessTime=0;this.cachedExceedingDatas=[];this.notifySpectrumUpdate({realData:this.realData,maxData:this.maxData,minData:this.minData,avgData:this.avgData,templateData:this.templateData,backgroundNoiseData:this.backgroundNoiseData,srcIndexCache:this.srcIndexCache,processTimes:0,scanProgress:0})}getData(){const{realData,maxData,minData,avgData,templateData,backgroundNoiseData,fluorescenceData,fluorescenceMaxCount,srcIndexCache,waterfallData,processTimes}=this;return{realData,maxData,minData,avgData,templateData,backgroundNoiseData,srcIndexCache,waterfallData,processTimes,fluorescenceData,fluorescenceMaxCount}}getPerformanceMetrics(){return{lastProcessTime:this.lastProcessTime,dataPoints:this.config.maxPoints,waterfallFrames:this.waterfallData.length,isInitialized:this.realData.length>0,memoryUsage:this.calculateMemoryUsage()}}updateSamplingRange(start,end){if(start<0||start>=end)throw new DataValidationError(ERROR_MESSAGES.INVALID_SAMPLING_RANGE);this.config={...this.config,outputRange:{start:Math.floor(start),end:Math.ceil(end)}};const processedData=this.resampleDataSeries();this.resampleWaterfallOutputData();this.notifySpectrumUpdate({...processedData,waterfallData:this.waterfallOutputData});return this.srcIndexCache}setTemplateData(data){if(!data||0===data.length){this.templateData=new Float32Array(0);return}this.templateData=new Float32Array(data);const{srcIndexCache,config:{outputPoints}}=this;const templateOutputData=new Float32Array(outputPoints);for(let i=0;i<outputPoints;i++)templateOutputData[i]=this.templateData[srcIndexCache[i]];const templateOverData=this.updateTemplateOverData(true);this.notifySpectrumUpdate({templateData:templateOutputData,templateOverData})}setBackgroundNoiseData(data){if(!data||0===data.length){this.backgroundNoiseData=new Float32Array(0);return}this.backgroundNoiseData=new Float32Array(data);const{srcIndexCache,config:{outputPoints}}=this;const backgroundNoiseOutputData=new Float32Array(outputPoints);for(let i=0;i<outputPoints;i++)backgroundNoiseOutputData[i]=this.backgroundNoiseData[srcIndexCache[i]];this.notifySpectrumUpdate({backgroundNoiseData:backgroundNoiseOutputData})}updateTemplateOverData(forceFullCalculation=false,incrementalRange){if(!this.templateData||0===this.templateData.length){this.cachedExceedingDatas=[];return[]}let templateOverData;if(forceFullCalculation||!incrementalRange){templateOverData=findExceedingDatas({realData:this.realData,maxData:this.maxData,minData:this.minData,templateData:this.templateData});this.cachedExceedingDatas=templateOverData}else{this.cachedExceedingDatas=findExceedingDatasIncremental({realData:this.realData,maxData:this.maxData,minData:this.minData,templateData:this.templateData,startIndex:incrementalRange.startIndex,endIndex:incrementalRange.endIndex,previousSegments:this.cachedExceedingDatas});templateOverData=this.cachedExceedingDatas}return templateOverData}resampleDataSeries(){const{antennaFactorData,antennaFactorSwitch,maxData,minData,avgData,templateData,backgroundNoiseData,config:{maxPoints,outputPoints}}=this;const activeAntennaFactorData=antennaFactorSwitch?antennaFactorData:new Float32Array(maxPoints);const{realOutputData,srcIndexCache,maxOutputData,minOutputData,avgOutputData,templateOutputData,backgroundNoiseOutputData,fluorescenceOutputData}=resampleMultiple({antennaFactorData:activeAntennaFactorData,antennaFactorSwitch,outputPoints,realData:this.getOutputData(),maxData:maxData&&maxData.length>0?this.getOutputData(maxData):void 0,minData:minData&&minData.length>0?this.getOutputData(minData):void 0,avgData:avgData&&avgData.length>0?this.getOutputData(avgData):void 0,templateData:templateData&&templateData.length>0?this.getOutputData(templateData):void 0,backgroundNoiseData:backgroundNoiseData&&backgroundNoiseData.length>0?this.getOutputData(backgroundNoiseData):void 0,fluorescenceData:this.config.processing.enableFluorescence?this.fluorescenceData:void 0});this.srcIndexCache=srcIndexCache;return{realData:realOutputData,maxData:maxOutputData,minData:minOutputData,avgData:avgOutputData,templateData:templateOutputData,backgroundNoiseData:backgroundNoiseOutputData,fluorescenceData:fluorescenceOutputData,fluorescenceMaxCount:this.fluorescenceMaxCount,srcIndexCache}}resampleWaterfallOutputData(){const{antennaFactorData,antennaFactorSwitch,waterfallData,config:{maxPoints,processing:{enableWaterfall},outputPoints}}=this;if(!enableWaterfall)return;const activeAntennaFactorData=antennaFactorSwitch?antennaFactorData:new Float32Array(maxPoints);this.waterfallOutputData=waterfallData.map(frame=>{const realData=this.getOutputData(frame);const{realOutputData}=resampleMultiple({realData,antennaFactorData:activeAntennaFactorData,antennaFactorSwitch,outputPoints});return realOutputData})}updateWaterfallData(data,outputData){const{waterfallMaxFrames,processing}=this.config;if(!processing.enableWaterfall)return;if(this.waterfallData.length>=waterfallMaxFrames){this.waterfallData.shift();this.waterfallOutputData.shift()}const newData=new Float32Array(data.length);newData.set(data);newData.timestamp=data.timestamp;this.waterfallData.push(newData);this.waterfallOutputData.push(outputData)}updateMaxPoints(maxPoints){if(this.config.maxPoints===maxPoints)return;this.config={...this.config,maxPoints,outputRange:{start:0,end:maxPoints}};this.reset()}validateInput(data,index){if(index<0||index+data.length>this.config.maxPoints)throw new IndexOutOfBoundsError(ERROR_MESSAGES.INDEX_OUT_OF_BOUNDS(index),index)}processDataPoints(data,index,isOver){const{maxData,minData,avgData,config:{processing:{enableMetrics}}}=this;if(!enableMetrics)return;if(isOver){this.processTimes+=1;if(!maxData.allValid)maxData.allValid=this.isAllValid(maxData);if(!minData.allValid)minData.allValid=this.isAllValid(minData);if(!avgData.allValid)avgData.allValid=this.isAllValid(avgData)}let allValid=true;const length=data.length;for(let i=0;i<length;i++)if(!Number.isFinite(data[i])){allValid=false;break}for(let i=0;i<length;i++){const dataIndex=index+i;const value=data[i];if(!allValid&&!Number.isFinite(value))continue;const oldMax=maxData[dataIndex];if(value>oldMax||!maxData.allValid&&Number.isNaN(oldMax))maxData[dataIndex]=value;const oldMin=minData[dataIndex];if(value<oldMin||!minData.allValid&&Number.isNaN(oldMin))minData[dataIndex]=value;const oldAvg=avgData[dataIndex];if(0===this.processTimes||Number.isNaN(oldAvg))avgData[dataIndex]=value;else avgData[dataIndex]=oldAvg+(value-oldAvg)/(this.processTimes+1);this.updateFluorescenceStats(dataIndex,value)}}updateFluorescenceStats(dataIndex,value){const{processing}=this.config;if(!processing.enableFluorescence||!this.fluorescenceData)return;if(!Number.isFinite(value))return;const level=Math.round(value);let levelMap=this.fluorescenceData[dataIndex];if(!levelMap){levelMap=new Map;this.fluorescenceData[dataIndex]=levelMap}const currentCount=levelMap.get(level)??0;const newCount=currentCount+1;levelMap.set(level,newCount);if(newCount>this.fluorescenceMaxCount)this.fluorescenceMaxCount=newCount}isAllValid(arr){for(let i=0;i<arr.length;i++)if(!Number.isFinite(arr[i]))return false;return true}notifySpectrumUpdate(processedData){this.onSpectrumUpdate?.(processedData)}calculateMemoryUsage(){const arraySize=this.config.maxPoints*Float32Array.BYTES_PER_ELEMENT;const baseArrayMemory=4*arraySize;const rawWaterfallMemory=this.waterfallData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);const outputWaterfallMemory=this.waterfallOutputData.reduce((sum,frame)=>sum+frame.length*Float32Array.BYTES_PER_ELEMENT,0);return baseArrayMemory+rawWaterfallMemory+outputWaterfallMemory}getOutputData(data){const{outputRange:{start,end}}=this.config;const sourceData=data??this.realData;const outputData=sourceData.subarray(start,end);outputData.timestamp=sourceData.timestamp;return outputData}getAllRawData(){const{realData,maxData,minData,avgData,templateData,backgroundNoiseData,waterfallData}=this;return{realData,maxData,minData,avgData,templateData,backgroundNoiseData,waterfallData}}}export{DataValidationError,ERROR_MESSAGES,IndexOutOfBoundsError,LevelStreamAnalyzer,SPECTRUM,SpectrumAnalyzer,SpectrumError};
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "types": "index.d.ts",
6
6
  "author": "Hxgh",
7
7
  "license": "MIT",
8
- "version": "0.1.21",
8
+ "version": "0.1.23",
9
9
  "private": false,
10
10
  "keywords": [
11
11
  "spectrum-analyzer",