anofit 0.1.0.dev0__cp313-cp313-win_amd64.whl

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.
Files changed (189) hide show
  1. anofit/__init__.py +32 -0
  2. anofit/__main__.py +6 -0
  3. anofit/_ed25519.cp313-win_amd64.pyd +0 -0
  4. anofit/_vendor/__init__.py +0 -0
  5. anofit/_vendor/csad_onnx.py +251 -0
  6. anofit/_vendor/logical.py +115 -0
  7. anofit/_vendor/model.py +191 -0
  8. anofit/_vendor/models/__init__.py +1 -0
  9. anofit/_vendor/models/segmentation/__init__.py +1 -0
  10. anofit/_vendor/models/segmentation/model.py +209 -0
  11. anofit/_vendor/seg_model.py +209 -0
  12. anofit/_vendor/structural.py +51 -0
  13. anofit/bundle.py +140 -0
  14. anofit/bundleio.py +233 -0
  15. anofit/cli.py +413 -0
  16. anofit/config.py +248 -0
  17. anofit/draw.py +59 -0
  18. anofit/engine/__init__.py +21 -0
  19. anofit/engine/compat.py +203 -0
  20. anofit/engine/determinism.py +134 -0
  21. anofit/fetch.py +362 -0
  22. anofit/fetch_manifest.json +82 -0
  23. anofit/jobs.py +252 -0
  24. anofit/licensing.cp313-win_amd64.pyd +0 -0
  25. anofit/modes.py +159 -0
  26. anofit/paths.py +94 -0
  27. anofit/preprocess.py +42 -0
  28. anofit/profile.py +226 -0
  29. anofit/profiles/_default.yaml +72 -0
  30. anofit/profiles/breakfast_box.yaml +70 -0
  31. anofit/profiles/juice_bottle.yaml +76 -0
  32. anofit/profiles/pushpins.yaml +86 -0
  33. anofit/profiles/screw_bag.yaml +101 -0
  34. anofit/profiles/splicing_connectors.yaml +70 -0
  35. anofit/quality.py +256 -0
  36. anofit/scoring.py +84 -0
  37. anofit/server.py +816 -0
  38. anofit/state.py +77 -0
  39. anofit/static/index.html +972 -0
  40. anofit/train/__init__.py +7 -0
  41. anofit/train/_boot.py +65 -0
  42. anofit/train/entry.py +669 -0
  43. anofit/train/ledger.py +84 -0
  44. anofit/train/logic/CSAD.py +590 -0
  45. anofit/train/logic/_ram/__init__.py +2 -0
  46. anofit/train/logic/_ram/configs/med_config.json +21 -0
  47. anofit/train/logic/_ram/configs/q2l_config.json +22 -0
  48. anofit/train/logic/_ram/configs/swin/config_swinB_224.json +9 -0
  49. anofit/train/logic/_ram/configs/swin/config_swinB_384.json +9 -0
  50. anofit/train/logic/_ram/configs/swin/config_swinL_224.json +9 -0
  51. anofit/train/logic/_ram/configs/swin/config_swinL_384.json +9 -0
  52. anofit/train/logic/_ram/data/__init__.py +71 -0
  53. anofit/train/logic/_ram/data/dataset.py +112 -0
  54. anofit/train/logic/_ram/data/ram_tag_list.txt +4585 -0
  55. anofit/train/logic/_ram/data/ram_tag_list_chinese.txt +4585 -0
  56. anofit/train/logic/_ram/data/ram_tag_list_threshold.txt +4585 -0
  57. anofit/train/logic/_ram/data/randaugment.py +340 -0
  58. anofit/train/logic/_ram/data/tag2text_ori_tag_list.txt +3429 -0
  59. anofit/train/logic/_ram/data/tag_list.txt +3429 -0
  60. anofit/train/logic/_ram/data/utils.py +112 -0
  61. anofit/train/logic/_ram/inference.py +46 -0
  62. anofit/train/logic/_ram/models/__init__.py +3 -0
  63. anofit/train/logic/_ram/models/bert.py +1088 -0
  64. anofit/train/logic/_ram/models/ram.py +409 -0
  65. anofit/train/logic/_ram/models/ram_plus.py +413 -0
  66. anofit/train/logic/_ram/models/swin_transformer.py +654 -0
  67. anofit/train/logic/_ram/models/tag2text.py +385 -0
  68. anofit/train/logic/_ram/models/utils.py +332 -0
  69. anofit/train/logic/_ram/models/vit.py +319 -0
  70. anofit/train/logic/_ram/transform.py +13 -0
  71. anofit/train/logic/_ram/utils/__init__.py +2 -0
  72. anofit/train/logic/_ram/utils/metrics.py +102 -0
  73. anofit/train/logic/_ram/utils/openset_utils.py +372 -0
  74. anofit/train/logic/_transformers_compat.py +171 -0
  75. anofit/train/logic/configs/class_histogram/breakfast_box.yaml +35 -0
  76. anofit/train/logic/configs/class_histogram/juice_bottle.yaml +34 -0
  77. anofit/train/logic/configs/class_histogram/pushpins.yaml +34 -0
  78. anofit/train/logic/configs/class_histogram/screw_bag.yaml +33 -0
  79. anofit/train/logic/configs/class_histogram/splicing_connectors.yaml +33 -0
  80. anofit/train/logic/configs/mvtecloco_train.yaml +27 -0
  81. anofit/train/logic/configs/segmentor/breakfast_box.yaml +29 -0
  82. anofit/train/logic/configs/segmentor/juice_bottle.yaml +29 -0
  83. anofit/train/logic/configs/segmentor/pushpins.yaml +29 -0
  84. anofit/train/logic/configs/segmentor/screw_bag.yaml +29 -0
  85. anofit/train/logic/configs/segmentor/splicing_connectors.yaml +29 -0
  86. anofit/train/logic/data_loader.py +191 -0
  87. anofit/train/logic/export_model.py +608 -0
  88. anofit/train/logic/extract_prompt.py +163 -0
  89. anofit/train/logic/grounded_sam.py +236 -0
  90. anofit/train/logic/groundingdino/__init__.py +0 -0
  91. anofit/train/logic/groundingdino/config/GroundingDINO_SwinB.py +43 -0
  92. anofit/train/logic/groundingdino/config/GroundingDINO_SwinT_OGC.py +43 -0
  93. anofit/train/logic/groundingdino/models/GroundingDINO/__init__.py +15 -0
  94. anofit/train/logic/groundingdino/models/GroundingDINO/backbone/__init__.py +1 -0
  95. anofit/train/logic/groundingdino/models/GroundingDINO/backbone/backbone.py +221 -0
  96. anofit/train/logic/groundingdino/models/GroundingDINO/backbone/position_encoding.py +186 -0
  97. anofit/train/logic/groundingdino/models/GroundingDINO/backbone/swin_transformer.py +802 -0
  98. anofit/train/logic/groundingdino/models/GroundingDINO/bertwarper.py +296 -0
  99. anofit/train/logic/groundingdino/models/GroundingDINO/fuse_modules.py +297 -0
  100. anofit/train/logic/groundingdino/models/GroundingDINO/groundingdino.py +395 -0
  101. anofit/train/logic/groundingdino/models/GroundingDINO/ms_deform_attn.py +413 -0
  102. anofit/train/logic/groundingdino/models/GroundingDINO/transformer.py +959 -0
  103. anofit/train/logic/groundingdino/models/GroundingDINO/transformer_vanilla.py +123 -0
  104. anofit/train/logic/groundingdino/models/GroundingDINO/utils.py +268 -0
  105. anofit/train/logic/groundingdino/models/__init__.py +18 -0
  106. anofit/train/logic/groundingdino/models/registry.py +66 -0
  107. anofit/train/logic/groundingdino/util/__init__.py +1 -0
  108. anofit/train/logic/groundingdino/util/box_ops.py +140 -0
  109. anofit/train/logic/groundingdino/util/get_tokenlizer.py +26 -0
  110. anofit/train/logic/groundingdino/util/inference.py +257 -0
  111. anofit/train/logic/groundingdino/util/logger.py +93 -0
  112. anofit/train/logic/groundingdino/util/misc.py +717 -0
  113. anofit/train/logic/groundingdino/util/slconfig.py +427 -0
  114. anofit/train/logic/groundingdino/util/slio.py +177 -0
  115. anofit/train/logic/groundingdino/util/time_counter.py +62 -0
  116. anofit/train/logic/groundingdino/util/utils.py +608 -0
  117. anofit/train/logic/groundingdino/util/visualizer.py +318 -0
  118. anofit/train/logic/groundingdino/util/vl_utils.py +100 -0
  119. anofit/train/logic/groundingdino/version.py +1 -0
  120. anofit/train/logic/models/component_feature_extractor.py +227 -0
  121. anofit/train/logic/models/model.py +190 -0
  122. anofit/train/logic/models/onnx_model.py +220 -0
  123. anofit/train/logic/models/segmentation/dataloader.py +295 -0
  124. anofit/train/logic/models/segmentation/loss.py +248 -0
  125. anofit/train/logic/models/segmentation/lsa.py +273 -0
  126. anofit/train/logic/models/segmentation/model.py +208 -0
  127. anofit/train/logic/models/segmentation/patch_histogram.py +177 -0
  128. anofit/train/logic/models/segmentation/segmentor.py +416 -0
  129. anofit/train/logic/models/tflite_model.py +289 -0
  130. anofit/train/logic/preview_segmentation.py +200 -0
  131. anofit/train/logic/pseudo_label.py +1348 -0
  132. anofit/train/logic/ram_prompt.py +136 -0
  133. anofit/train/logic/segment_anything/__init__.py +22 -0
  134. anofit/train/logic/segment_anything/automatic_mask_generator.py +372 -0
  135. anofit/train/logic/segment_anything/build_sam.py +107 -0
  136. anofit/train/logic/segment_anything/build_sam_hq.py +114 -0
  137. anofit/train/logic/segment_anything/modeling/__init__.py +12 -0
  138. anofit/train/logic/segment_anything/modeling/common.py +43 -0
  139. anofit/train/logic/segment_anything/modeling/image_encoder.py +398 -0
  140. anofit/train/logic/segment_anything/modeling/mask_decoder.py +178 -0
  141. anofit/train/logic/segment_anything/modeling/mask_decoder_hq.py +232 -0
  142. anofit/train/logic/segment_anything/modeling/prompt_encoder.py +214 -0
  143. anofit/train/logic/segment_anything/modeling/sam.py +174 -0
  144. anofit/train/logic/segment_anything/modeling/transformer.py +240 -0
  145. anofit/train/logic/segment_anything/predictor.py +276 -0
  146. anofit/train/logic/segment_anything/utils/__init__.py +5 -0
  147. anofit/train/logic/segment_anything/utils/amg.py +346 -0
  148. anofit/train/logic/segment_anything/utils/onnx.py +144 -0
  149. anofit/train/logic/segment_anything/utils/transforms.py +102 -0
  150. anofit/train/logic/train_full.py +226 -0
  151. anofit/train/logic/train_light.py +606 -0
  152. anofit/train/logic/train_segmentor.py +151 -0
  153. anofit/train/preflight.py +188 -0
  154. anofit/train/select.py +212 -0
  155. anofit/train/split.py +118 -0
  156. anofit/train/struct/_numpy_compat.py +48 -0
  157. anofit/train/struct/ae.py +215 -0
  158. anofit/train/struct/argparser.py +18 -0
  159. anofit/train/struct/composition_map_creation/dataset.py +283 -0
  160. anofit/train/struct/composition_map_creation/dino/utils.py +693 -0
  161. anofit/train/struct/composition_map_creation/dino/vision_transformer.py +432 -0
  162. anofit/train/struct/composition_map_creation/modules.py +55 -0
  163. anofit/train/struct/composition_map_creation/pseudo_label_dataset.py +51 -0
  164. anofit/train/struct/composition_map_creation/unet.py +293 -0
  165. anofit/train/struct/create_fg_masks.py +102 -0
  166. anofit/train/struct/create_pseudo_labels.py +299 -0
  167. anofit/train/struct/dice_loss.py +58 -0
  168. anofit/train/struct/effad_networks.py +102 -0
  169. anofit/train/struct/eval_salad_plus.py +103 -0
  170. anofit/train/struct/logger.py +15 -0
  171. anofit/train/struct/salad_dataset.py +339 -0
  172. anofit/train/struct/salad_infer.py +291 -0
  173. anofit/train/struct/test_count.py +155 -0
  174. anofit/train/struct/test_salad.py +119 -0
  175. anofit/train/struct/train_composition_segmentation_model.py +185 -0
  176. anofit/train/struct/train_entry.py +335 -0
  177. anofit/train/struct/train_salad.py +781 -0
  178. anofit/train/struct/unet.py +348 -0
  179. anofit/train/struct/visualization_utils.py +35 -0
  180. anofit/train/tune.py +310 -0
  181. anofit-0.1.0.dev0.dist-info/METADATA +170 -0
  182. anofit-0.1.0.dev0.dist-info/RECORD +189 -0
  183. anofit-0.1.0.dev0.dist-info/WHEEL +5 -0
  184. anofit-0.1.0.dev0.dist-info/entry_points.txt +2 -0
  185. anofit-0.1.0.dev0.dist-info/licenses/LICENSE +53 -0
  186. anofit-0.1.0.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +185 -0
  187. anofit-0.1.0.dev0.dist-info/licenses/licenses/Apache-2.0.txt +201 -0
  188. anofit-0.1.0.dev0.dist-info/licenses/licenses/MIT-SALAD.txt +21 -0
  189. anofit-0.1.0.dev0.dist-info/top_level.txt +1 -0
anofit/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """anofit — SCHAD 이상탐지 엔진 (구조부 + 논리부, 모드 선택).
2
+
3
+ geofit(찾기) · gaugefit(재기) 형제. 이쪽은 **가려내기**다.
4
+
5
+ import anofit
6
+ b = anofit.load("juice", mode="struct") # 학습된 분기의 부분집합만 허용
7
+ s, amap, parts = anofit.score(b, image_bgr)
8
+
9
+ 알고리즘 이름은 계속 SCHAD 다 — `anofit` 은 그것을 담아 파는 제품의 이름이다.
10
+ """
11
+ # ⚠ **torch/transformers 임포트 전에** 캐시 경로를 심는다. 라이브러리가 임포트 시점에
12
+ # 경로를 굳히므로 나중에 바꿔도 늦다. 아래 임포트들은 torch 를 지연 임포트하지만,
13
+ # 순서를 지키는 것이 규약이다.
14
+ from . import config as _config
15
+
16
+ _config.apply_cache_env()
17
+
18
+ from .bundle import BundleError, describe, evict, load
19
+ from .modes import BRANCHES, MODES, Mode, ModeError, available, resolve
20
+ from .scoring import fuse_from_raw, score, score_many, score_raw
21
+ from .quality import drift, mode_table, operating, roi_mismatch
22
+ from .state import ModelState, threshold_report
23
+
24
+ __version__ = "0.1.0.dev0"
25
+
26
+ __all__ = [
27
+ "load", "describe", "evict", "BundleError",
28
+ "score", "score_raw", "score_many", "fuse_from_raw",
29
+ "MODES", "Mode", "ModeError", "BRANCHES", "resolve", "available",
30
+ "ModelState", "threshold_report", "__version__",
31
+ "mode_table", "operating", "drift", "roi_mismatch",
32
+ ]
anofit/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
6
+ raise SystemExit(main())
Binary file
File without changes
@@ -0,0 +1,251 @@
1
+ """
2
+ This file contains the ONNX/Pytorch model for CSAD.
3
+ """
4
+ import timm
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ import torch.onnx
9
+
10
+ from .model import AutoEncoder, LocalStudent
11
+ from .seg_model import Segmentor
12
+
13
+
14
+ class FixedPatchClassDetector(nn.Module):
15
+ """
16
+ Optimized version of patch histogram detector for fast speed
17
+ and support batched input.
18
+ (Predefined patch sizes and overlap ratios)
19
+ (not using EMPatches to extract patches)
20
+ """
21
+ def __init__(self,num_classes=5,segmap_size=256,use_nahalanobis=True):
22
+ super().__init__()
23
+ self.num_classes = num_classes
24
+ self.segmap_size = segmap_size
25
+
26
+ self.use_nahalanobis = use_nahalanobis
27
+
28
+ self.hist_mean = torch.nn.Parameter(data=torch.randn((self.num_classes,1)),requires_grad=False)
29
+ self.hist_invcov = torch.nn.Parameter(data=torch.randn((self.num_classes,self.num_classes)),requires_grad=False)
30
+ self.patch_hist_mean = torch.nn.Parameter(data=torch.randn((self.num_classes*4,1)),requires_grad=False)
31
+ self.patch_hist_invcov = torch.nn.Parameter(data=torch.randn((self.num_classes*4,self.num_classes*4)),requires_grad=False)
32
+ self.hist_val_mean = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
33
+ self.hist_val_std = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
34
+ self.patch_hist_val_mean = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
35
+ self.patch_hist_val_std = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
36
+
37
+
38
+ def set_params(self,params):
39
+ self.hist_mean = torch.nn.Parameter(data=params['hist_mean'].unsqueeze(1),requires_grad=False)
40
+ self.hist_invcov = torch.nn.Parameter(data=params['hist_invcov'],requires_grad=False)
41
+ self.patch_hist_mean = torch.nn.Parameter(data=params['patch_hist_mean'].unsqueeze(1),requires_grad=False)
42
+ self.patch_hist_invcov = torch.nn.Parameter(data=params['patch_hist_invcov'],requires_grad=False)
43
+ self.hist_val_mean = torch.nn.Parameter(data=params['hist_val_mean'],requires_grad=False)
44
+ self.hist_val_std = torch.nn.Parameter(data=params['hist_val_std'],requires_grad=False)
45
+ self.patch_hist_val_mean = torch.nn.Parameter(data=params['patch_hist_val_mean'],requires_grad=False)
46
+ self.patch_hist_val_std = torch.nn.Parameter(data=params['patch_hist_val_std'],requires_grad=False)
47
+
48
+ @staticmethod
49
+ def mahalanobis(u, v, invcov):
50
+ delta = u - v
51
+ m = torch.matmul(delta.T, torch.matmul(invcov, delta)).squeeze()
52
+ return torch.sqrt(m)
53
+
54
+ @staticmethod
55
+ def histogram(seg_map):
56
+ _, max_indices = torch.max(seg_map, dim=1)
57
+ # Create a tensor of zeros with the same shape as the input tensor
58
+ out = torch.zeros_like(seg_map)
59
+ # Set the max values to 1 by using advanced indexing
60
+ out.scatter_(1, max_indices.unsqueeze(1), 1)
61
+ hist = torch.mean(out,dim=[2,3])
62
+ hist = hist.unsqueeze(2)
63
+ return hist
64
+
65
+ @staticmethod
66
+ def patch_histogram(seg_map):
67
+ # patch_size = 128
68
+ a = seg_map[:,:,:128,:128]
69
+ b = seg_map[:,:,128:,:128]
70
+ c = seg_map[:,:,:128,128:]
71
+ d = seg_map[:,:,128:,128:]
72
+ return torch.concat([
73
+ FixedPatchClassDetector.histogram(a),
74
+ FixedPatchClassDetector.histogram(b),
75
+ FixedPatchClassDetector.histogram(c),
76
+ FixedPatchClassDetector.histogram(d)]
77
+ ,dim=1)
78
+
79
+
80
+
81
+ def forward(self,segmap):
82
+ hist = FixedPatchClassDetector.histogram(segmap)
83
+ patch_hist = FixedPatchClassDetector.patch_histogram(segmap)
84
+
85
+ diff_hists = []
86
+ diff_patchhists = []
87
+ for i in range(segmap.shape[0]):
88
+ if self.use_nahalanobis:
89
+ diff_hist = FixedPatchClassDetector.mahalanobis(hist[i],self.hist_mean,self.hist_invcov)
90
+ diff_patchhist = FixedPatchClassDetector.mahalanobis(patch_hist[i],self.patch_hist_mean,self.patch_hist_invcov)
91
+ else:
92
+ diff_hist = ((hist[i]-self.hist_mean)**2).sum()**0.5
93
+ diff_patchhist = ((patch_hist[i]-self.patch_hist_mean)**2).sum()**0.5
94
+ diff_hists.append(diff_hist)
95
+ diff_patchhists.append(diff_patchhist)
96
+ diff_hist = torch.stack(diff_hists)
97
+ diff_patchhist = torch.stack(diff_patchhists)
98
+
99
+ diff_hist = (diff_hist-self.hist_val_mean)/self.hist_val_std
100
+ diff_patchhist = (diff_patchhist-self.patch_hist_val_mean)/self.patch_hist_val_std
101
+
102
+ out_score = diff_hist + diff_patchhist
103
+ return out_score
104
+
105
+ # without encoder
106
+ class ResNetTeacher(nn.Module):
107
+ def __init__(self,out_dim=512,feat_size=64):
108
+ super().__init__()
109
+ self.out_dim = out_dim
110
+ self.feat_size = feat_size
111
+ self.proj = nn.Conv2d(1024+512, self.out_dim, kernel_size=1, stride=1, padding=0)
112
+ self.proj.requires_grad_(False)
113
+
114
+ def forward(self, x):
115
+ x = [x[1],x[2]]
116
+ concat_feat = []
117
+ for i in range(len(x)):
118
+ feat = x[i]
119
+ feat = F.interpolate(feat, size=self.feat_size, mode='bilinear',align_corners=False)
120
+ concat_feat.append(feat)
121
+ concat_feat = torch.cat(concat_feat,dim=1)
122
+ concat_feat = nn.AvgPool2d(kernel_size=3, stride=1, padding=1)(concat_feat)
123
+ proj_feat = self.proj(concat_feat)
124
+ return proj_feat
125
+
126
+ class CSAD_ONNX(nn.Module):
127
+ def __init__(self,dim=512,num_classes=5):
128
+ super().__init__()
129
+ self.teacher_mean = torch.nn.Parameter(data=torch.randn((1,dim,1,1)),requires_grad=False)
130
+ self.teacher_std = torch.nn.Parameter(data=torch.randn((1,dim,1,1)),requires_grad=False)
131
+
132
+ self.q_st_start = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
133
+ self.q_st_end = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
134
+ self.q_ae_start = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
135
+ self.q_ae_end = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
136
+
137
+ self.lgst_mean = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
138
+ self.lgst_std = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
139
+
140
+ self.patch_hist_mean = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
141
+ self.patch_hist_std = torch.nn.Parameter(data=torch.randn(1)[0],requires_grad=False)
142
+
143
+ self.encoder = timm.create_model('hf_hub:timm/wide_resnet50_2.tv2_in1k'
144
+ ,pretrained=True,
145
+ features_only=True,
146
+ out_indices=[1,2,3])
147
+ self.teacher = ResNetTeacher(out_dim=512,feat_size=64)
148
+ self.local_st = LocalStudent(out_dim=512,feat_size=64,padding=True)
149
+ self.autoencoder = AutoEncoder(out_dim=512,out_size=64)
150
+ self.segmentor = Segmentor(num_classes=num_classes)
151
+ self.patch_hist_detector = FixedPatchClassDetector(
152
+ num_classes=num_classes
153
+ ,segmap_size=256,
154
+ use_nahalanobis=True
155
+ )
156
+
157
+ def load_model_params(self,models,params):
158
+ self.teacher_mean = torch.nn.Parameter(data=params['teacher_mean'],requires_grad=False)
159
+ self.teacher_std = torch.nn.Parameter(data=params['teacher_std'],requires_grad=False)
160
+
161
+ self.q_st_start = torch.nn.Parameter(data=params['q_st_start'],requires_grad=False)
162
+ self.q_st_end = torch.nn.Parameter(data=params['q_st_end'],requires_grad=False)
163
+ self.q_ae_start = torch.nn.Parameter(data=params['q_ae_start'],requires_grad=False)
164
+ self.q_ae_end = torch.nn.Parameter(data=params['q_ae_end'],requires_grad=False)
165
+
166
+ self.lgst_mean = torch.nn.Parameter(data=params['lgst_mean'],requires_grad=False)
167
+ self.lgst_std = torch.nn.Parameter(data=params['lgst_std'],requires_grad=False)
168
+ if self.lgst_std < 1e-3:
169
+ self.lgst_std = torch.nn.Parameter(data=torch.tensor([1])[0],requires_grad=False)
170
+
171
+ self.patch_hist_mean = torch.nn.Parameter(data=params['patchhist_mean'],requires_grad=False)
172
+ self.patch_hist_std = torch.nn.Parameter(data=params['patchhist_std'],requires_grad=False)
173
+ if self.patch_hist_std < 1e-3:
174
+ self.patch_hist_std = torch.nn.Parameter(data=torch.tensor([1])[0],requires_grad=False)
175
+
176
+ self.encoder = models['encoder']
177
+ self.teacher = models['teacher']
178
+ self.local_st = models['local_st']
179
+ self.autoencoder = models['autoencoder']
180
+ self.segmentor = models['segmentor']
181
+ self.patch_hist_detector = models['patch_hist_detector']
182
+ return self
183
+
184
+
185
+ @classmethod
186
+ def from_pretrained(cls,models,params):
187
+ num_classes = models['segmentor'].fc2.conv3.out_channels
188
+ return cls(num_classes=num_classes).load_model_params(models,params)
189
+
190
+
191
+ def forward(self, image):
192
+ # shape: (B,3,256,256)
193
+ feat = self.encoder(image)
194
+
195
+ # Patch Histogram branch
196
+ segmap = self.segmentor(feat)
197
+ # segmap = torch.argmax(segmap,dim=1)
198
+ patch_hist_score = self.patch_hist_detector(segmap)
199
+
200
+ # LGST branch
201
+ teacher_feat = self.teacher(feat)
202
+ teacher_feat = (teacher_feat-self.teacher_mean)/self.teacher_std
203
+ local_feat,global_feat = self.local_st(image)
204
+ ae_global_feat = self.autoencoder(image)
205
+
206
+ diff_local = torch.pow(teacher_feat-local_feat,2).mean(1)
207
+ diff_global = torch.pow(global_feat-ae_global_feat,2).mean(1)
208
+
209
+ # map normalization
210
+ diff_local = 0.1 * (diff_local - self.q_st_start) / (self.q_st_end - self.q_st_start)
211
+ diff_global = 0.1 * (diff_global - self.q_ae_start) / (self.q_ae_end - self.q_ae_start)
212
+
213
+ LGST_score = torch.amax(diff_local+diff_global,dim=(1,2))
214
+
215
+ # score fusion
216
+ LGST_score = (LGST_score-self.lgst_mean)/self.lgst_std
217
+ patch_hist_score = (patch_hist_score-self.patch_hist_mean)/self.patch_hist_std
218
+
219
+
220
+
221
+ return LGST_score+patch_hist_score
222
+
223
+ def forward_maps(self, image):
224
+ """forward 와 동일 점수 + 오버레이용 2D 이상맵(LGST 공간맵, (B,64,64)) 반환.
225
+
226
+ patch-histogram(논리) 브랜치는 히스토그램 기반이라 자연스러운 공간맵이 없어,
227
+ 오버레이는 LGST(구조) 공간맵으로 위치를 표시하고, 최종 점수에는 둘 다 반영된다.
228
+ """
229
+ feat = self.encoder(image)
230
+
231
+ segmap = self.segmentor(feat)
232
+ patch_hist_score = self.patch_hist_detector(segmap)
233
+
234
+ teacher_feat = self.teacher(feat)
235
+ teacher_feat = (teacher_feat - self.teacher_mean) / self.teacher_std
236
+ local_feat, global_feat = self.local_st(image)
237
+ ae_global_feat = self.autoencoder(image)
238
+
239
+ diff_local = torch.pow(teacher_feat - local_feat, 2).mean(1)
240
+ diff_global = torch.pow(global_feat - ae_global_feat, 2).mean(1)
241
+ diff_local = 0.1 * (diff_local - self.q_st_start) / (self.q_st_end - self.q_st_start)
242
+ diff_global = 0.1 * (diff_global - self.q_ae_start) / (self.q_ae_end - self.q_ae_start)
243
+
244
+ lgst_map = diff_local + diff_global # (B,64,64)
245
+ LGST_score = torch.amax(lgst_map, dim=(1, 2))
246
+
247
+ LGST_score = (LGST_score - self.lgst_mean) / self.lgst_std
248
+ patch_hist_score = (patch_hist_score - self.patch_hist_mean) / self.patch_hist_std
249
+
250
+ score = LGST_score + patch_hist_score
251
+ return score, lgst_map
@@ -0,0 +1,115 @@
1
+ """SCHAD 논리부(patch-hist) — 벤더링된 CSAD LightCSAD 추론 코드.
2
+
3
+ 원본: modules/csad/runner.py 의 LightCSAD/_light_segmentor/_preprocess/_get_transform.
4
+ schad 자립(standalone)을 위해 csad 모듈 의존 없이 _vendor 안으로 흡수한다.
5
+ encoder(timm wide_resnet50_2.tv2_in1k)·전처리(PIL BILINEAR)는 원본과 동일해야 점수가 재현된다.
6
+
7
+ 오프라인 배포(CLAUDE.md §13-6): 인코더 가중치는 **프로젝트 내** weights/ 에 둔다.
8
+ 과거엔 timm 이 `hf_hub:` 로 HuggingFace 를 직접 쳤고, 그러면 가중치가 프로젝트가 아니라
9
+ 사용자 캐시(~/.cache/huggingface)에 떨어진다 — 프로젝트를 통째로 복사해도 인터넷 없는
10
+ 현장 PC 에서 첫 로드가 실패한다(다른 모듈은 이미 weights/ 로 오프라인화돼 있었는데 여기만 누락).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+
16
+ import numpy as np
17
+
18
+ logger = logging.getLogger("anofit.logical")
19
+
20
+ # 학습 때 쓴 인코더와 동일해야 한다 — 바꾸면 segmentor 가 학습한 특징과 어긋나 점수가 재현되지 않는다.
21
+ _ENCODER_MODEL = "wide_resnet50_2.tv2_in1k"
22
+ _ENCODER_KW = dict(features_only=True, out_indices=[1, 2, 3])
23
+
24
+
25
+ def _encoder_weight_path():
26
+ """인코더 가중치 로컬 보관 경로(weights/timm__{모델}/encoder.pth)."""
27
+ from ..config import find_weight
28
+
29
+ return find_weight(f"timm__{_ENCODER_MODEL}/encoder.pth")
30
+
31
+
32
+ def _build_encoder():
33
+ """논리부 인코더 로드 — 오프라인 우선.
34
+
35
+ weights/ 에 받아둔 게 있으면 인터넷 없이 그것으로, 없으면 이때만 내려받아 weights/ 에
36
+ 영속화한다(다음부터 오프라인 가능). patchcore/backbone.py 와 같은 방식.
37
+ """
38
+ import timm
39
+ import torch
40
+
41
+ local = _encoder_weight_path()
42
+ if local.exists():
43
+ enc = timm.create_model(_ENCODER_MODEL, pretrained=False, **_ENCODER_KW)
44
+ enc.load_state_dict(torch.load(str(local), map_location="cpu"))
45
+ logger.info("논리부 인코더 로컬 로드: %s", local)
46
+ return enc
47
+
48
+ logger.info("논리부 인코더 다운로드: %s → %s", _ENCODER_MODEL, local)
49
+ try:
50
+ enc = timm.create_model(f"hf_hub:timm/{_ENCODER_MODEL}", pretrained=True, **_ENCODER_KW)
51
+ except Exception as exc:
52
+ from ..config import weights_root
53
+
54
+ raise RuntimeError(
55
+ f"논리부 인코더 로드 실패({_ENCODER_MODEL}): {exc}. "
56
+ f"오프라인 현장이면 인터넷 PC 에서 한 번 받아 '{weights_root()}' 폴더를 "
57
+ "현장 PC 로 복사하세요."
58
+ ) from exc
59
+ try:
60
+ local.parent.mkdir(parents=True, exist_ok=True)
61
+ torch.save(enc.state_dict(), str(local))
62
+ logger.info("논리부 인코더 영속화 완료: %s", local)
63
+ except OSError as exc:
64
+ logger.warning("인코더 로컬 저장 실패(%s): %s — 이번 세션은 그대로 동작", local, exc)
65
+ return enc
66
+
67
+
68
+ def _light_segmentor(num_classes, in_dim, state_dict):
69
+ """벤더링된 Segmentor 재구성(추론 전용)."""
70
+ from .models.segmentation.model import Segmentor # 벤더 상대 import
71
+
72
+ seg = Segmentor(num_classes=num_classes, in_dim=in_dim, in_size=256)
73
+ seg.load_state_dict(state_dict)
74
+ return seg.eval()
75
+
76
+
77
+ def LightCSAD(bundle):
78
+ import torch
79
+ import torch.nn as nn
80
+
81
+ from .csad_onnx import FixedPatchClassDetector
82
+
83
+ nc = int(bundle["num_classes"])
84
+ p = bundle["patchhist_params"]
85
+
86
+ class _LightCSAD(nn.Module):
87
+ def __init__(self):
88
+ super().__init__()
89
+ # 학습 때와 동일 encoder (segmentor 가 이 특징으로 학습됨) — weights/ 오프라인 우선
90
+ self.encoder = _build_encoder()
91
+ for q in self.encoder.parameters():
92
+ q.requires_grad = False
93
+ self.segmentor = _light_segmentor(nc, bundle.get("in_dim", [256, 1024]),
94
+ bundle["segmentor_state"])
95
+ self.patch_hist_detector = FixedPatchClassDetector(num_classes=nc, segmap_size=256, use_nahalanobis=True)
96
+ t = lambda x: torch.tensor(np.asarray(x), dtype=torch.float32)
97
+ self.patch_hist_detector.set_params({
98
+ "hist_mean": t(p["hist_mean"]), "hist_invcov": t(p["hist_invcov"]),
99
+ "patch_hist_mean": t(p["patch_hist_mean"]), "patch_hist_invcov": t(p["patch_hist_invcov"]),
100
+ "hist_val_mean": t(p["hist_val_mean"]), "hist_val_std": t(p["hist_val_std"]),
101
+ "patch_hist_val_mean": t(p["patch_hist_val_mean"]), "patch_hist_val_std": t(p["patch_hist_val_std"]),
102
+ })
103
+
104
+ def forward(self, image):
105
+ return self.patch_hist_detector(self.segmentor(self.encoder(image)))
106
+
107
+ def forward_maps(self, image):
108
+ # 경량(patch-hist)은 히스토그램 기반이라 공간 이상맵이 없음 → 오버레이 None
109
+ return self.forward(image), None
110
+
111
+ return _LightCSAD()
112
+
113
+
114
+ # 전처리(_get_transform/_preprocess)는 anofit/preprocess.py 로 옮겼다 (기획서 §3.4-2).
115
+ # 구조부 전용 경로가 논리부 모듈을 import 하지 않게 하려는 것이 목적이다.
@@ -0,0 +1,191 @@
1
+
2
+ import timm
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+
8
+ class PDN_M(nn.Module):
9
+ def __init__(self, out_dim=512,feat_size=64,padding=False):
10
+ super().__init__()
11
+ self.out_dim = out_dim
12
+ self.st_size = feat_size
13
+ self.ae_size = feat_size
14
+ self.padding = 1 if padding else 0
15
+ self.conv1 = nn.Conv2d(3, 256, kernel_size=4, stride=1, padding=3*self.padding)
16
+ self.conv2 = nn.Conv2d(256, 512, kernel_size=4, stride=1, padding=3*self.padding)
17
+ self.conv3 = nn.Conv2d(512, 512, kernel_size=1, stride=1, padding=0)
18
+ self.conv4 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1*self.padding)
19
+ self.conv5 = nn.Conv2d(512, self.out_dim*2, kernel_size=4, stride=1, padding=0)
20
+ self.conv6 = nn.Conv2d(self.out_dim*2, self.out_dim*2, kernel_size=1, stride=1, padding=0)
21
+ self.avgpool1 = nn.AvgPool2d(kernel_size=2, stride=2, padding=1*self.padding)
22
+ self.avgpool2 = nn.AvgPool2d(kernel_size=2, stride=2, padding=1*self.padding)
23
+
24
+ def forward(self, x):
25
+ x = self.conv1(x)
26
+ x = F.relu(x)
27
+ x = self.avgpool1(x)
28
+ x = self.conv2(x)
29
+ x = F.relu(x)
30
+ x = self.avgpool2(x)
31
+ x = self.conv3(x)
32
+ x = F.relu(x)
33
+ x = self.conv4(x)
34
+ x = F.relu(x)
35
+ x = self.conv5(x)
36
+ x = F.relu(x)
37
+ x = self.conv6(x)
38
+ # x = F.relu(x)
39
+ x = F.interpolate(x, size=self.st_size, mode='bilinear', align_corners=False)
40
+ st = x[:,:self.out_dim]
41
+ ae = x[:,self.out_dim:]
42
+ return st,ae
43
+
44
+
45
+ class LocalStudent(nn.Module):
46
+
47
+ def __init__(self, out_dim=512,feat_size=64,padding=False):
48
+ super().__init__()
49
+ self.out_dim = out_dim
50
+ self.feat_size = feat_size
51
+ self.padding = 1 if padding else 0
52
+ self.conv1 = nn.Conv2d(3, 256, kernel_size=4, stride=1, padding=3*self.padding)
53
+ self.conv2 = nn.Conv2d(256, 512, kernel_size=4, stride=1, padding=3*self.padding)
54
+ self.conv3 = nn.Conv2d(512, 512, kernel_size=1, stride=1, padding=0)
55
+ self.conv4 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1*self.padding)
56
+ self.conv5 = nn.Conv2d(512, self.out_dim, kernel_size=4, stride=1, padding=0)
57
+ # self.conv6 = nn.Conv2d(self.out_dim, self.out_dim, kernel_size=1, stride=1, padding=0)
58
+
59
+ self.avgpool1 = nn.AvgPool2d(kernel_size=2, stride=2, padding=1*self.padding)
60
+ self.avgpool2 = nn.AvgPool2d(kernel_size=2, stride=2, padding=1*self.padding)
61
+
62
+ self.conv_st = nn.Conv2d(self.out_dim, self.out_dim, kernel_size=1, stride=1, padding=0)
63
+ self.conv_ae = nn.Conv2d(self.out_dim, self.out_dim, kernel_size=1, stride=1, padding=0)
64
+
65
+ def forward(self, x):
66
+ x = self.conv1(x)
67
+ x = F.relu(x)
68
+ x = self.avgpool1(x)
69
+ x = self.conv2(x)
70
+ x = F.relu(x)
71
+ x = self.avgpool2(x)
72
+ x = self.conv3(x)
73
+ x = F.relu(x)
74
+ x = self.conv4(x)
75
+ x = F.relu(x)
76
+ x = self.conv5(x)
77
+ x = F.relu(x)
78
+ x = F.interpolate(x, size=self.feat_size, mode='bilinear', align_corners=False)
79
+ st = self.conv_st(x)
80
+ ae = self.conv_ae(x)
81
+ return st,ae
82
+
83
+
84
+
85
+ class AutoEncoder(nn.Module):
86
+ def __init__(self, out_size=64,out_dim=512,base_dim=64):
87
+ super().__init__()
88
+ self.out_dim = out_dim
89
+ self.base_dim = base_dim
90
+ self.out_size = out_size
91
+
92
+ self.enconv1 = nn.Conv2d(3, self.base_dim//2, kernel_size=4, stride=2, padding=1)
93
+ self.enconv2 = nn.Conv2d(self.base_dim//2, self.base_dim//2, kernel_size=4, stride=2, padding=1)
94
+ self.enconv3 = nn.Conv2d(self.base_dim//2, self.base_dim, kernel_size=4, stride=2, padding=1)
95
+ self.enconv4 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=2, padding=1)
96
+ self.enconv5 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=2, padding=1)
97
+ self.enconv6 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=8, stride=1, padding=0)
98
+
99
+ self.deconv1 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
100
+ self.deconv2 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
101
+ self.deconv3 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
102
+ self.deconv4 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
103
+ self.deconv5 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
104
+ self.deconv6 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=4, stride=1, padding=2)
105
+ self.deconv7 = nn.Conv2d(self.base_dim, self.base_dim, kernel_size=3, stride=1, padding=1)
106
+ self.deconv8 = nn.Conv2d(self.base_dim, self.out_dim, kernel_size=3, stride=1, padding=1)
107
+ self.dropout1 = nn.Dropout(p=0.2)
108
+ self.dropout2 = nn.Dropout(p=0.2)
109
+ self.dropout3 = nn.Dropout(p=0.2)
110
+ self.dropout4 = nn.Dropout(p=0.2)
111
+ self.dropout5 = nn.Dropout(p=0.2)
112
+ self.dropout6 = nn.Dropout(p=0.2)
113
+
114
+
115
+ def forward(self, x):
116
+ x1 = F.relu(self.enconv1(x))
117
+ x2 = F.relu(self.enconv2(x1))
118
+ x3 = F.relu(self.enconv3(x2))
119
+ x4 = F.relu(self.enconv4(x3))
120
+ x5 = F.relu(self.enconv5(x4))
121
+ x6 = self.enconv6(x5)
122
+
123
+ x = F.interpolate(x6, size=3, mode='bilinear')
124
+ x = F.relu(self.deconv1(x))
125
+ x = self.dropout1(x)
126
+ x = F.interpolate(x, size=8, mode='bilinear')
127
+ x = F.relu(self.deconv2(x))
128
+ x = self.dropout2(x)
129
+ x = F.interpolate(x, size=15, mode='bilinear')
130
+ x = F.relu(self.deconv3(x))
131
+ x = self.dropout3(x)
132
+ x = F.interpolate(x, size=32, mode='bilinear')
133
+ x = F.relu(self.deconv4(x))
134
+ x = self.dropout4(x)
135
+ x = F.interpolate(x, size=63, mode='bilinear')
136
+ x = F.relu(self.deconv5(x))
137
+ x = self.dropout5(x)
138
+ x = F.interpolate(x, size=127, mode='bilinear')
139
+ x = F.relu(self.deconv6(x))
140
+ x = self.dropout6(x)
141
+ x = F.interpolate(x, size=self.out_size, mode='bilinear')
142
+ x = F.relu(self.deconv7(x))
143
+ x = self.deconv8(x)
144
+ return x
145
+
146
+ class ResNetTeacher(nn.Module):
147
+ def __init__(self,out_dim=512,feat_size=64):
148
+ super().__init__()
149
+ # hf_hub:timm/wide_resnet50_2.tv2_in1k
150
+ # hf_hub:timm/wide_resnet50_2.racm_in1k
151
+ self.encoder = timm.create_model('hf_hub:timm/wide_resnet50_2.tv2_in1k'
152
+ ,pretrained=True,
153
+ features_only=True,
154
+ out_indices=[2,3])
155
+ for param in self.encoder.parameters():
156
+ param.requires_grad = False
157
+ self.encoder.eval()
158
+ self.out_dim = out_dim
159
+ self.feat_size = feat_size
160
+ self.proj = nn.Conv2d(1024+512, self.out_dim, kernel_size=1, stride=1, padding=0)
161
+ self.proj.requires_grad_(False)
162
+
163
+ def forward(self, x):
164
+ x = self.encoder(x)
165
+ concat_feat = []
166
+ for i in range(len(x)):
167
+ feat = x[i]
168
+ feat = F.interpolate(feat, size=self.feat_size, mode='bilinear',align_corners=False)
169
+ concat_feat.append(feat)
170
+ concat_feat = torch.cat(concat_feat,dim=1)
171
+ concat_feat = nn.AvgPool2d(kernel_size=3, stride=1, padding=1)(concat_feat)
172
+ proj_feat = self.proj(concat_feat)
173
+ return proj_feat
174
+
175
+
176
+
177
+
178
+ if __name__ == '__main__':
179
+
180
+ import torch
181
+ import torchsummary
182
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
183
+
184
+ model = AutoEncoder(
185
+ out_size=64,
186
+ out_dim=512,
187
+ base_dim=64,
188
+ input_size=256
189
+ ).cuda()
190
+ torchsummary.summary(model, (3, 256, 256))
191
+
@@ -0,0 +1 @@
1
+ """벤더링된 CSAD 논리부(patch-hist) — schad 자립용. 원본: modules/csad."""
@@ -0,0 +1 @@
1
+ """벤더링된 CSAD segmentation 모델(Segmentor) — schad 논리부용."""