@lumy-pack/scene-sieve 0.0.7 → 0.0.8

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.
package/dist/cli.mjs CHANGED
@@ -51,7 +51,7 @@ import { join } from "path";
51
51
  function getTempWorkspaceDir(sessionId) {
52
52
  return join(TEMP_BASE_DIR, `${WORKSPACE_PREFIX}${sessionId}`);
53
53
  }
54
- var APP_NAME, DEFAULT_COUNT, DEFAULT_THRESHOLD, DEFAULT_FPS, DEFAULT_SCALE, DEFAULT_QUALITY, DEFAULT_MAX_FRAMES, NORMALIZATION_PERCENTILE, WORKSPACE_PREFIX, TEMP_BASE_DIR, FRAME_OUTPUT_EXTENSION, FRAME_FILENAME_PATTERN, OPENCV_BATCH_SIZE, DBSCAN_ALPHA, DBSCAN_MIN_PTS, IOU_THRESHOLD, DECAY_LAMBDA, ANIMATION_FRAME_THRESHOLD, MATCH_DISTANCE_THRESHOLD, PIXELDIFF_GAUSSIAN_KERNEL, PIXELDIFF_BINARY_THRESHOLD, PIXELDIFF_CONTOUR_MIN_AREA, PIXELDIFF_SAMPLE_SPACING;
54
+ var APP_NAME, DEFAULT_COUNT, DEFAULT_THRESHOLD, DEFAULT_FPS, DEFAULT_SCALE, DEFAULT_QUALITY, DEFAULT_MAX_FRAMES, NORMALIZATION_LOGISTIC_K, NORMALIZATION_ALPHA, NORMALIZATION_MAD_COEFFICIENT, NORMALIZATION_MIN_SAMPLE_SIZE, WORKSPACE_PREFIX, TEMP_BASE_DIR, FRAME_OUTPUT_EXTENSION, FRAME_FILENAME_PATTERN, OPENCV_BATCH_SIZE, DBSCAN_ALPHA, DBSCAN_MIN_PTS, IOU_THRESHOLD, DECAY_LAMBDA, ANIMATION_FRAME_THRESHOLD, MATCH_DISTANCE_THRESHOLD, PIXELDIFF_GAUSSIAN_KERNEL, PIXELDIFF_BINARY_THRESHOLD, PIXELDIFF_CONTOUR_MIN_AREA, PIXELDIFF_SAMPLE_SPACING;
55
55
  var init_constants = __esm({
56
56
  "src/constants.ts"() {
57
57
  "use strict";
@@ -62,7 +62,10 @@ var init_constants = __esm({
62
62
  DEFAULT_SCALE = 720;
63
63
  DEFAULT_QUALITY = 80;
64
64
  DEFAULT_MAX_FRAMES = 300;
65
- NORMALIZATION_PERCENTILE = 0.9;
65
+ NORMALIZATION_LOGISTIC_K = 3;
66
+ NORMALIZATION_ALPHA = 0.4;
67
+ NORMALIZATION_MAD_COEFFICIENT = 1.4826;
68
+ NORMALIZATION_MIN_SAMPLE_SIZE = 10;
66
69
  WORKSPACE_PREFIX = `${APP_NAME}-`;
67
70
  TEMP_BASE_DIR = tmpdir();
68
71
  FRAME_OUTPUT_EXTENSION = ".jpg";
@@ -873,6 +876,49 @@ var init_input_resolver = __esm({
873
876
  }
874
877
  });
875
878
 
879
+ // src/utils/math.ts
880
+ function normalizeScores(items) {
881
+ if (items.length === 0) return [];
882
+ const safeScores = items.map(
883
+ (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
884
+ );
885
+ const positiveScores = safeScores.filter((s) => s > 0);
886
+ if (positiveScores.length === 0) return safeScores;
887
+ const sorted = [...positiveScores].sort((a, b) => a - b);
888
+ if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
889
+ const min = sorted[0];
890
+ const max = sorted[sorted.length - 1];
891
+ if (max === min) return safeScores.map((s) => s > 0 ? 1 : 0);
892
+ return safeScores.map(
893
+ (s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
894
+ );
895
+ }
896
+ const median = sorted[Math.floor(sorted.length / 2)];
897
+ const absoluteDiffs = positiveScores.map((v) => Math.abs(v - median));
898
+ const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
899
+ const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
900
+ const logisticZ = safeScores.map((s) => {
901
+ if (s <= 0) return 0;
902
+ if (scale === 0) return 1;
903
+ const z = (s - median) / scale;
904
+ return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
905
+ });
906
+ const cdf = safeScores.map((s) => {
907
+ if (s <= 0) return 0;
908
+ const rank = sorted.findIndex((v) => v >= s);
909
+ return rank / sorted.length;
910
+ });
911
+ return logisticZ.map(
912
+ (z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
913
+ );
914
+ }
915
+ var init_math = __esm({
916
+ "src/utils/math.ts"() {
917
+ "use strict";
918
+ init_constants();
919
+ }
920
+ });
921
+
876
922
  // src/utils/min-heap.ts
877
923
  var MinHeap;
878
924
  var init_min_heap = __esm({
@@ -973,20 +1019,6 @@ function pruneTo(graph, frames, targetCount) {
973
1019
  }
974
1020
  return surviving;
975
1021
  }
976
- function normalizeScores(graph) {
977
- if (graph.length === 0) return [];
978
- const safeScores = graph.map(
979
- (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
980
- );
981
- const sorted = [...safeScores].filter((s) => s > 0).sort((a, b) => a - b);
982
- if (sorted.length === 0) return safeScores;
983
- const pIdx = Math.min(
984
- Math.floor(sorted.length * NORMALIZATION_PERCENTILE),
985
- sorted.length - 1
986
- );
987
- const refScore = sorted[pIdx];
988
- return safeScores.map((s) => Math.min(s / refScore, 1));
989
- }
990
1022
  function suppressConsecutiveRuns(graph, passingIndices, normalizedScores) {
991
1023
  const result = /* @__PURE__ */ new Set();
992
1024
  let runStart = 0;
@@ -1086,7 +1118,7 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
1086
1118
  var init_pruner = __esm({
1087
1119
  "src/core/pruner.ts"() {
1088
1120
  "use strict";
1089
- init_constants();
1121
+ init_math();
1090
1122
  init_min_heap();
1091
1123
  }
1092
1124
  });
@@ -5,7 +5,12 @@ export declare const DEFAULT_FPS = 5;
5
5
  export declare const DEFAULT_SCALE = 720;
6
6
  export declare const DEFAULT_QUALITY = 80;
7
7
  export declare const DEFAULT_MAX_FRAMES = 300;
8
- export declare const NORMALIZATION_PERCENTILE = 0.9;
8
+ export declare const NORMALIZATION_MIN_PERCENTILE = 0.1;
9
+ export declare const NORMALIZATION_MAX_PERCENTILE = 0.9;
10
+ export declare const NORMALIZATION_LOGISTIC_K = 3;
11
+ export declare const NORMALIZATION_ALPHA = 0.4;
12
+ export declare const NORMALIZATION_MAD_COEFFICIENT = 1.4826;
13
+ export declare const NORMALIZATION_MIN_SAMPLE_SIZE = 10;
9
14
  export declare const WORKSPACE_PREFIX = "scene-sieve-";
10
15
  export declare const TEMP_BASE_DIR: string;
11
16
  export declare const FRAME_OUTPUT_EXTENSION = ".jpg";
package/dist/index.cjs CHANGED
@@ -85,7 +85,10 @@ var DEFAULT_FPS = 5;
85
85
  var DEFAULT_SCALE = 720;
86
86
  var DEFAULT_QUALITY = 80;
87
87
  var DEFAULT_MAX_FRAMES = 300;
88
- var NORMALIZATION_PERCENTILE = 0.9;
88
+ var NORMALIZATION_LOGISTIC_K = 3;
89
+ var NORMALIZATION_ALPHA = 0.4;
90
+ var NORMALIZATION_MAD_COEFFICIENT = 1.4826;
91
+ var NORMALIZATION_MIN_SAMPLE_SIZE = 10;
89
92
  var WORKSPACE_PREFIX = `${APP_NAME}-`;
90
93
  var TEMP_BASE_DIR = (0, import_node_os.tmpdir)();
91
94
  var FRAME_OUTPUT_EXTENSION = ".jpg";
@@ -839,6 +842,43 @@ async function resolveInput(options, workspacePath) {
839
842
  throw new Error(`Unsupported input mode: ${options.mode}`);
840
843
  }
841
844
 
845
+ // src/utils/math.ts
846
+ function normalizeScores(items) {
847
+ if (items.length === 0) return [];
848
+ const safeScores = items.map(
849
+ (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
850
+ );
851
+ const positiveScores = safeScores.filter((s) => s > 0);
852
+ if (positiveScores.length === 0) return safeScores;
853
+ const sorted = [...positiveScores].sort((a, b) => a - b);
854
+ if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
855
+ const min = sorted[0];
856
+ const max = sorted[sorted.length - 1];
857
+ if (max === min) return safeScores.map((s) => s > 0 ? 1 : 0);
858
+ return safeScores.map(
859
+ (s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
860
+ );
861
+ }
862
+ const median = sorted[Math.floor(sorted.length / 2)];
863
+ const absoluteDiffs = positiveScores.map((v) => Math.abs(v - median));
864
+ const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
865
+ const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
866
+ const logisticZ = safeScores.map((s) => {
867
+ if (s <= 0) return 0;
868
+ if (scale === 0) return 1;
869
+ const z = (s - median) / scale;
870
+ return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
871
+ });
872
+ const cdf = safeScores.map((s) => {
873
+ if (s <= 0) return 0;
874
+ const rank = sorted.findIndex((v) => v >= s);
875
+ return rank / sorted.length;
876
+ });
877
+ return logisticZ.map(
878
+ (z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
879
+ );
880
+ }
881
+
842
882
  // src/utils/min-heap.ts
843
883
  var MinHeap = class {
844
884
  h = [];
@@ -933,20 +973,6 @@ function pruneTo(graph, frames, targetCount) {
933
973
  }
934
974
  return surviving;
935
975
  }
936
- function normalizeScores(graph) {
937
- if (graph.length === 0) return [];
938
- const safeScores = graph.map(
939
- (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
940
- );
941
- const sorted = [...safeScores].filter((s) => s > 0).sort((a, b) => a - b);
942
- if (sorted.length === 0) return safeScores;
943
- const pIdx = Math.min(
944
- Math.floor(sorted.length * NORMALIZATION_PERCENTILE),
945
- sorted.length - 1
946
- );
947
- const refScore = sorted[pIdx];
948
- return safeScores.map((s) => Math.min(s / refScore, 1));
949
- }
950
976
  function suppressConsecutiveRuns(graph, passingIndices, normalizedScores) {
951
977
  const result = /* @__PURE__ */ new Set();
952
978
  let runStart = 0;
package/dist/index.mjs CHANGED
@@ -45,7 +45,10 @@ var DEFAULT_FPS = 5;
45
45
  var DEFAULT_SCALE = 720;
46
46
  var DEFAULT_QUALITY = 80;
47
47
  var DEFAULT_MAX_FRAMES = 300;
48
- var NORMALIZATION_PERCENTILE = 0.9;
48
+ var NORMALIZATION_LOGISTIC_K = 3;
49
+ var NORMALIZATION_ALPHA = 0.4;
50
+ var NORMALIZATION_MAD_COEFFICIENT = 1.4826;
51
+ var NORMALIZATION_MIN_SAMPLE_SIZE = 10;
49
52
  var WORKSPACE_PREFIX = `${APP_NAME}-`;
50
53
  var TEMP_BASE_DIR = tmpdir();
51
54
  var FRAME_OUTPUT_EXTENSION = ".jpg";
@@ -799,6 +802,43 @@ async function resolveInput(options, workspacePath) {
799
802
  throw new Error(`Unsupported input mode: ${options.mode}`);
800
803
  }
801
804
 
805
+ // src/utils/math.ts
806
+ function normalizeScores(items) {
807
+ if (items.length === 0) return [];
808
+ const safeScores = items.map(
809
+ (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
810
+ );
811
+ const positiveScores = safeScores.filter((s) => s > 0);
812
+ if (positiveScores.length === 0) return safeScores;
813
+ const sorted = [...positiveScores].sort((a, b) => a - b);
814
+ if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
815
+ const min = sorted[0];
816
+ const max = sorted[sorted.length - 1];
817
+ if (max === min) return safeScores.map((s) => s > 0 ? 1 : 0);
818
+ return safeScores.map(
819
+ (s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
820
+ );
821
+ }
822
+ const median = sorted[Math.floor(sorted.length / 2)];
823
+ const absoluteDiffs = positiveScores.map((v) => Math.abs(v - median));
824
+ const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
825
+ const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
826
+ const logisticZ = safeScores.map((s) => {
827
+ if (s <= 0) return 0;
828
+ if (scale === 0) return 1;
829
+ const z = (s - median) / scale;
830
+ return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
831
+ });
832
+ const cdf = safeScores.map((s) => {
833
+ if (s <= 0) return 0;
834
+ const rank = sorted.findIndex((v) => v >= s);
835
+ return rank / sorted.length;
836
+ });
837
+ return logisticZ.map(
838
+ (z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
839
+ );
840
+ }
841
+
802
842
  // src/utils/min-heap.ts
803
843
  var MinHeap = class {
804
844
  h = [];
@@ -893,20 +933,6 @@ function pruneTo(graph, frames, targetCount) {
893
933
  }
894
934
  return surviving;
895
935
  }
896
- function normalizeScores(graph) {
897
- if (graph.length === 0) return [];
898
- const safeScores = graph.map(
899
- (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
900
- );
901
- const sorted = [...safeScores].filter((s) => s > 0).sort((a, b) => a - b);
902
- if (sorted.length === 0) return safeScores;
903
- const pIdx = Math.min(
904
- Math.floor(sorted.length * NORMALIZATION_PERCENTILE),
905
- sorted.length - 1
906
- );
907
- const refScore = sorted[pIdx];
908
- return safeScores.map((s) => Math.min(s / refScore, 1));
909
- }
910
936
  function suppressConsecutiveRuns(graph, passingIndices, normalizedScores) {
911
937
  const result = /* @__PURE__ */ new Set();
912
938
  let runStart = 0;
@@ -48,7 +48,10 @@ var DEFAULT_FPS = 5;
48
48
  var DEFAULT_SCALE = 720;
49
49
  var DEFAULT_QUALITY = 80;
50
50
  var DEFAULT_MAX_FRAMES = 300;
51
- var NORMALIZATION_PERCENTILE = 0.9;
51
+ var NORMALIZATION_LOGISTIC_K = 3;
52
+ var NORMALIZATION_ALPHA = 0.4;
53
+ var NORMALIZATION_MAD_COEFFICIENT = 1.4826;
54
+ var NORMALIZATION_MIN_SAMPLE_SIZE = 10;
52
55
  var WORKSPACE_PREFIX = `${APP_NAME}-`;
53
56
  var TEMP_BASE_DIR = tmpdir();
54
57
  var FRAME_OUTPUT_EXTENSION = ".jpg";
@@ -802,6 +805,43 @@ async function resolveInput(options2, workspacePath) {
802
805
  throw new Error(`Unsupported input mode: ${options2.mode}`);
803
806
  }
804
807
 
808
+ // src/utils/math.ts
809
+ function normalizeScores(items) {
810
+ if (items.length === 0) return [];
811
+ const safeScores = items.map(
812
+ (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
813
+ );
814
+ const positiveScores = safeScores.filter((s) => s > 0);
815
+ if (positiveScores.length === 0) return safeScores;
816
+ const sorted = [...positiveScores].sort((a, b) => a - b);
817
+ if (positiveScores.length <= NORMALIZATION_MIN_SAMPLE_SIZE) {
818
+ const min = sorted[0];
819
+ const max = sorted[sorted.length - 1];
820
+ if (max === min) return safeScores.map((s) => s > 0 ? 1 : 0);
821
+ return safeScores.map(
822
+ (s) => s <= 0 ? 0 : Math.max(0, Math.min((s - min) / (max - min), 1))
823
+ );
824
+ }
825
+ const median = sorted[Math.floor(sorted.length / 2)];
826
+ const absoluteDiffs = positiveScores.map((v) => Math.abs(v - median));
827
+ const mad = [...absoluteDiffs].sort((a, b) => a - b)[Math.floor(absoluteDiffs.length / 2)];
828
+ const scale = mad === 0 ? median : mad * NORMALIZATION_MAD_COEFFICIENT;
829
+ const logisticZ = safeScores.map((s) => {
830
+ if (s <= 0) return 0;
831
+ if (scale === 0) return 1;
832
+ const z = (s - median) / scale;
833
+ return 1 / (1 + Math.exp(-NORMALIZATION_LOGISTIC_K * z));
834
+ });
835
+ const cdf = safeScores.map((s) => {
836
+ if (s <= 0) return 0;
837
+ const rank = sorted.findIndex((v) => v >= s);
838
+ return rank / sorted.length;
839
+ });
840
+ return logisticZ.map(
841
+ (z, i) => z * (1 - NORMALIZATION_ALPHA) + cdf[i] * NORMALIZATION_ALPHA
842
+ );
843
+ }
844
+
805
845
  // src/utils/min-heap.ts
806
846
  var MinHeap = class {
807
847
  h = [];
@@ -896,20 +936,6 @@ function pruneTo(graph, frames, targetCount) {
896
936
  }
897
937
  return surviving;
898
938
  }
899
- function normalizeScores(graph) {
900
- if (graph.length === 0) return [];
901
- const safeScores = graph.map(
902
- (e) => Number.isFinite(e.score) && e.score > 0 ? e.score : 0
903
- );
904
- const sorted = [...safeScores].filter((s) => s > 0).sort((a, b) => a - b);
905
- if (sorted.length === 0) return safeScores;
906
- const pIdx = Math.min(
907
- Math.floor(sorted.length * NORMALIZATION_PERCENTILE),
908
- sorted.length - 1
909
- );
910
- const refScore = sorted[pIdx];
911
- return safeScores.map((s) => Math.min(s / refScore, 1));
912
- }
913
939
  function suppressConsecutiveRuns(graph, passingIndices, normalizedScores) {
914
940
  const result = /* @__PURE__ */ new Set();
915
941
  let runStart = 0;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Interface for objects that have a numeric score.
3
+ */
4
+ export interface ScoredItem {
5
+ score: number;
6
+ }
7
+ /**
8
+ * Normalize raw scores to [0, 1] range via Robust Hybrid Normalization.
9
+ *
10
+ * This model combines two mathematical approaches to provide a stable "relative" threshold:
11
+ *
12
+ * 1. Logistic-Robust-Z (Intensity):
13
+ * Calculates Z-scores using Median and Median Absolute Deviation (MAD).
14
+ * Maps these to a sigmoid (logistic) curve. This suppresses noise (scores near median)
15
+ * and highlights significant signals (outliers) without letting extreme outliers
16
+ * crush other meaningful transitions.
17
+ *
18
+ * 2. CDF / Percentile Rank (Relative Position):
19
+ * Maps each score to its percentile rank in the sequence. This ensures that 't'
20
+ * always has a consistent meaning as a "relative rank" regardless of absolute values.
21
+ *
22
+ * The final score is a weighted sum (NORMALIZATION_ALPHA) of both.
23
+ *
24
+ * @param items - Array of items with scores to normalize
25
+ * @returns normalized scores array (same length as input)
26
+ */
27
+ export declare function normalizeScores<T extends ScoredItem>(items: T[]): number[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumy-pack/scene-sieve",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "CLI tool for extracting key frames from video and GIF files",
5
5
  "keywords": [
6
6
  "cli",