Uses 1405K of libraries. Click here for Pure Java version (21004L/117K).
1 | !7 |
2 | |
3 | // Note: Don't use db_mainConcepts(), only the module's concepts field |
4 | |
5 | !include early #1033884 // Compact Module Include Gazelle V [dev version] |
6 | |
7 | module GazelleScreenCam { |
8 | !include early #1025212 // +Enabled without visualization |
9 | |
10 | int pixelRows = 128, colors = 8; |
11 | S script, testScreenScript; |
12 | bool animate = true; |
13 | bool horizontalLayout; // flat layout |
14 | |
15 | WatchTarget watchTarget; |
16 | |
17 | transient ImageSurface isPosterized, isRegions, isTestScreen; |
18 | transient new ScreenCamStream imageStream; |
19 | transient new BWIntegralImageStream integralImages; |
20 | transient new SourceTriggeredStream<BWImage> scaledAndPosterizedImages; |
21 | transient new DoubleFPSCounter fpsCounter; |
22 | transient int fps; |
23 | //transient ScreenSelectorRadioButtons screenSelector; |
24 | transient WatchTargetSelector watchTargetSelector; |
25 | transient new RollingAverage remainingMSPerFrame; |
26 | transient int remainingMS; |
27 | |
28 | transient new FunctionTimings<S> functionTimings; |
29 | |
30 | transient ReliableSingleThread rstRunScript = dm_rst(me(), r _runScript); |
31 | |
32 | transient JGazelleVScriptRunner scriptRunner; |
33 | transient JGazelleVScriptRunner testScreenScriptRunner; |
34 | |
35 | transient Animation animation; |
36 | |
37 | transient BWImage_FastRegions mainScreenRegions; |
38 | |
39 | transient UIURLSystem uiURLs; |
40 | |
41 | // set by host |
42 | transient Concepts concepts; |
43 | |
44 | S uiURL; |
45 | |
46 | transient FileWatchService fileWatcher; |
47 | |
48 | transient SimpleCRUD_v2<Label> labelCRUD; |
49 | transient SimpleCRUD_v2<GalleryImage> imageCRUD; |
50 | |
51 | transient JGallery gallery; |
52 | |
53 | start { |
54 | dm_onFieldChange horizontalLayout( |
55 | //r dm_revisualize // deh buggy |
56 | r dm_reload |
57 | ); |
58 | |
59 | // non-standalone mode (doesn't work yet) |
60 | if (concepts == null) { |
61 | db(); |
62 | concepts = db_mainConcepts(); |
63 | } |
64 | |
65 | // update count in tab when tab isn't selected |
66 | onConceptChanges(concepts, -> { |
67 | labelCRUD?.updateEnclosingTabTitle(); |
68 | imageCRUD?.updateEnclosingTabTitle(); |
69 | }); |
70 | |
71 | uiURLs = new UIURLSystem(me(), dm_fieldLiveValue uiURL()); |
72 | |
73 | scriptRunner = new JGazelleVScriptRunner(dm_fieldLiveValue script(me())); |
74 | testScreenScriptRunner = new JGazelleVScriptRunner(dm_fieldLiveValue testScreenScript(me())); |
75 | |
76 | imageStream.directlyFeedInto(integralImages); |
77 | |
78 | integralImages.onNewElement(ii -> |
79 | scaledAndPosterizedImages.newElement( |
80 | scaleAndPosterize(ii, new SnPSettings(pixelRows, colors)))); |
81 | |
82 | integralImages.onNewElement(r { |
83 | if (shouldRunScript()) rstRunScript.go(); |
84 | }); |
85 | |
86 | scaledAndPosterizedImages.onNewElement(img -> { |
87 | fpsCounter.inc(); |
88 | setField(fps := iround(fpsCounter!)); |
89 | |
90 | // display before analysis (old) |
91 | // isPosterized?.setImage_thisThread(img); |
92 | |
93 | // find regions |
94 | floodFill(img); |
95 | |
96 | // display after analysis so we can highlight a region |
97 | if (isPosterized != null) { |
98 | var img2 = highlightRegion(img, isPosterized, mainScreenRegions); |
99 | isPosterized.setImage_thisThread(img2); |
100 | } |
101 | }); |
102 | |
103 | // 20 Hz is enough for everyone |
104 | dm_doEvery(1000/20, r { |
105 | if (enabled) { |
106 | watchTargetSelector?.updateScreenCount(); |
107 | Timestamp deadline = tsNowPlusMS(1000/20); |
108 | if (watchTarget cast WatchScreen) |
109 | imageStream.useScreen(watchTarget.screenNr-1); |
110 | else if (watchTarget cast WatchMouse) |
111 | imageStream.area(mouseArea(watchTarget.width, watchTarget.height)); |
112 | imageStream.step(); |
113 | long remaining = deadline.minus(tsNow()); |
114 | remainingMSPerFrame.add(remaining); |
115 | setField(remainingMS := iround(remainingMSPerFrame!)); |
116 | } |
117 | }); |
118 | |
119 | startDirWatcher(); |
120 | for (f : allImageFiles(galleryDir())) |
121 | addToGallery(f); |
122 | } |
123 | |
124 | // convert to color & highlight region |
125 | BufferedImage highlightRegion(BWImage image, ImageSurface is, BWImage_FastRegions regions) { |
126 | var pixels = image.getRGBPixels(); |
127 | |
128 | var mouse = is.mousePosition; |
129 | if (mouse != null && regions != null) { |
130 | int iHighlightedRegion = regions.regionAt(mouse); |
131 | //int marked = 0; |
132 | if (iHighlightedRegion > 0) |
133 | for i over pixels: |
134 | if (regions.regionAt(i) == iHighlightedRegion) { |
135 | pixels[i] = 0xFF008000; |
136 | //++marked; |
137 | } |
138 | //printVars(+mouse, +iHighlightedRegion, +marked); |
139 | } |
140 | |
141 | ret bufferedImage(pixels, image.getWidth(), image.getHeight()); |
142 | } |
143 | |
144 | void startDirWatcher { |
145 | fileWatcher = new FileWatchService; |
146 | fileWatcher.addRecursiveListener(picturesDir(), file -> { |
147 | if (!isImageFile(file)) ret; |
148 | addToGallery(file); |
149 | }); |
150 | } |
151 | |
152 | ImageSurface stdImageSurface() { |
153 | ret pixelatedImageSurface().setAutoZoomToDisplay(true).repaintInThread(false); |
154 | } |
155 | |
156 | visualize { |
157 | gallery = new JGallery; |
158 | var galleryComponent = gallery.visualize(); |
159 | |
160 | new AWTOnConceptChanges(concepts, galleryComponent, -> { |
161 | gallery.setImageFiles(map(list(concepts, GalleryImage), i -> i.path)); |
162 | }).install(); |
163 | |
164 | /*screenSelector = new ScreenSelectorRadioButtons(dm_fieldLiveValue screenNr()); |
165 | screenSelector.compactLayout(true); |
166 | screenSelector.hideIfOnlyOne(true); |
167 | screenSelector.screenLabel("");*/ |
168 | isPosterized = stdImageSurface(); |
169 | |
170 | isRegions = stdImageSurface(); |
171 | isTestScreen = stdImageSurface(); |
172 | |
173 | // when test screen is visible, do the animation |
174 | awtEvery(isTestScreen, 1000/20, r stepAnimation); |
175 | |
176 | JTextArea taTimings = jTextArea_noUndo(); |
177 | awtEveryAndNow(taTimings, .5, r { |
178 | setText(taTimings, renderFunctionTimings()) |
179 | }); |
180 | |
181 | var jSpeedInfo = dm_transientCalculatedToolTip speedInfo_long(rightAlignLabel(dm_transientCalculatedLabel speedInfo())); |
182 | |
183 | //print("Labels: " + list(concepts, Label)); |
184 | labelCRUD = new SimpleCRUD_v2(concepts, Label); |
185 | labelCRUD.hideFields("globalID"); |
186 | labelCRUD.addCountToEnclosingTab(true); |
187 | |
188 | imageCRUD = new SimpleCRUD_v2(concepts, GalleryImage); |
189 | imageCRUD.addCountToEnclosingTab(true); |
190 | imageCRUD.itemToMap_inner2 = img |
191 | -> litorderedmap( |
192 | "File" := fileName(img.path), |
193 | "Folder" := dirPath(img.path)); |
194 | |
195 | // main visual |
196 | |
197 | var studyPanel = new StudyPanel; |
198 | |
199 | var tabs = scrollingTabs(jTopOrLeftTabs(horizontalLayout, |
200 | "Screen Cam" := withTools(isPosterized), |
201 | WithToolTip("Regions", "Visualize regions detected on screen") |
202 | := jscroll_centered_borderless(isRegions), |
203 | WithToolTip("Study", "Here you can analyze gallery images") |
204 | := withTopMargin(studyPanel.visualize()), |
205 | WithToolTip("Pretty Gallery", "Gallery view with preview images") |
206 | := galleryComponent, |
207 | WithToolTip("Powerful Gallery", "Gallery view with more functions (delete etc)") |
208 | := wrapCRUD(imageCRUD), |
209 | WithToolTip("Labels", "Manage labels (image markers)") |
210 | := wrapCRUD(labelCRUD), |
211 | WithToolTip("Script", "Run a Gazelle V script") |
212 | := scriptRunner.scriptAndResultPanel(), |
213 | "Test Screen" := testScreenPanel(), |
214 | Timings := withRightAlignedButtons(taTimings, |
215 | Reset := r resetTimings) |
216 | )); |
217 | |
218 | var cbEnabled = toolTip("Switch screen cam on or off", dm_checkBox enabled("")); |
219 | var lblScreenCam = setToolTip("Show scaled down and color-reduced screen image", |
220 | jlabel("Screen Cam")); |
221 | tabComponentClickFixer(lblScreenCam); |
222 | var screenCamTab = hstackWithSpacing(cbEnabled, lblScreenCam); |
223 | |
224 | replaceTabTitleComponent(tabs, "Screen Cam", screenCamTab); |
225 | |
226 | // for tab titles |
227 | labelCRUD.update(); |
228 | imageCRUD.update(); |
229 | |
230 | watchTargetSelector = new WatchTargetSelector; |
231 | |
232 | var urlBar = uiURLs.urlBar(); |
233 | setToolTip(uiURLs.comboBox, "UI navigation system (currently unused)"); |
234 | |
235 | var vis = northAndCenter( |
236 | withSideAndTopMargin(urlBar), |
237 | centerAndSouthOrEast(horizontalLayout, |
238 | horizontalLayout ? withMargin(tabs) : withSideMargin(tabs), |
239 | /*withMargin*/(borderlessScrollPane(jHigherScrollPane( |
240 | jfullcenter(vstack( |
241 | withLeftAndRightMargin(hstack( |
242 | dm_rcheckBox enabled("Watch"), |
243 | watchTargetSelector.visualize(), |
244 | jlabel(" in "), |
245 | withLabelToTheRight("colors @ ", dm_spinner colors(2, 256)), |
246 | withLabelToTheRight("p", dm_powersOfTwoSpinner pixelRows(512)), |
247 | , /* speed info can also go here */ |
248 | )), |
249 | verticalStrut(2), |
250 | withRightMargin(jSpeedInfo) |
251 | ))))), |
252 | )); |
253 | setHorizontalMarginForAllButtons(vis, 4); |
254 | ret vis; |
255 | } |
256 | |
257 | S speedInfo() { |
258 | ret "FPS " + fps + " idle " + remainingMS + " ms"; |
259 | } |
260 | |
261 | S speedInfo_long() { |
262 | ret "Screen cam running at " + nFrames(fps) + "/second. " + n2(remainingMS) + " ms remaining per frame in first core."; |
263 | } |
264 | |
265 | void floodFill(BWImage img) { |
266 | BWImage_FastRegions ff = new(img); |
267 | //ff.tolerance(0); |
268 | ff.collectBounds(); |
269 | functionTimings.time("Regions", ff); |
270 | mainScreenRegions = ff; |
271 | if (isRegions != null && isRegions.isShowing_quick()) { |
272 | //print("Showing regions image"); |
273 | isRegions.setImage_thisThread(ff.regionsImage()); |
274 | } |
275 | setEnclosingTabTitle(isRegions, nRegions(ff.regionCount())); |
276 | } |
277 | |
278 | bool useErrorHandling() { false; } |
279 | |
280 | S renderFunctionTimings() { |
281 | ret lines(ciSorted(map(functionTimings!, (f, avg) -> |
282 | firstToUpper(f) + ": " + n2(iround(nsToMicroseconds(avg!))) + " " + microSymbol() + "s (" + n2(iround(avg.n())) + ")"))); |
283 | } |
284 | |
285 | transient long _runScript_idx; |
286 | |
287 | void _runScript() { |
288 | long n = integralImages.elementCount(); |
289 | if (n > _runScript_idx) { |
290 | _runScript_idx = n; |
291 | |
292 | scriptRunner.parseAndRunOn(integralImages!); |
293 | } |
294 | } |
295 | |
296 | bool shouldRunScript() { |
297 | ret isShowing(scriptRunner.scpScriptResult); |
298 | } |
299 | |
300 | void stepAnimation { |
301 | if (!animate) ret; |
302 | if (animation == null) { |
303 | animation = new AnimatedLine; |
304 | animation.start(); |
305 | } |
306 | animation.nextFrame(); |
307 | var img = whiteImage(animation.w, animation.h); |
308 | animation.setGraphics(createGraphics(img)); |
309 | animation.paint(); |
310 | isTestScreen?.setImage(img); |
311 | |
312 | var ii = bwIntegralImage_withMeta(img); |
313 | testScreenScriptRunner.parseAndRunOn(ii); |
314 | } |
315 | |
316 | JComponent testScreenPanel() { |
317 | ret centerAndSouthWithMargin( |
318 | hsplit( |
319 | northAndCenterWithMargin(centerAndEastWithMargin( |
320 | jlabel("Input"), dm_fieldCheckBox animate()), |
321 | jscroll_centered_borderless(isTestScreen)), |
322 | |
323 | northAndCenterWithMargin(centerAndEastWithMargin( |
324 | jlabel("Output"), testScreenScriptRunner.lblScore), |
325 | testScreenScriptRunner.scpScriptResult) |
326 | ), |
327 | |
328 | testScreenScriptRunner.scriptInputField() |
329 | ); |
330 | } |
331 | |
332 | L popDownItems() { |
333 | ret ll(jCheckBoxMenuItem_dyn("Horizontal Layout", |
334 | -> horizontalLayout, |
335 | b -> setField(horizontalLayout := b))); |
336 | } |
337 | |
338 | void unvisualize {} // don't zero transient component fields |
339 | |
340 | void resetTimings { functionTimings.reset(); } |
341 | |
342 | // add tool side bar to image surface |
343 | JComponent withTools(ImageSurface is) { |
344 | new ImageSurface_PositionToolTip(is); |
345 | ret centerAndEastWithMargin(jscroll_centered_borderless(is), vstackWithSpacing(3, |
346 | jimageButtonScaledToWidth(16, #1103054, "Save screenshot in gallery", rThread saveScreenshotToGallery), |
347 | )); |
348 | } |
349 | |
350 | class WatchTargetSelector { |
351 | JComboBox<WatchTarget> cb = jComboBox(); |
352 | int screenCount; |
353 | |
354 | visualize { |
355 | updateList(); |
356 | print("Selecting watchTarget: " + watchTarget); |
357 | selectItem(cb, watchTarget); |
358 | main onChange(cb, watchTarget -> { |
359 | setField(+watchTarget); |
360 | print("Chose watchTarget: " + GazelleScreenCam.this.watchTarget); |
361 | }); |
362 | ret cb; |
363 | } |
364 | |
365 | void updateScreenCount() { |
366 | if (screenCount != screenCount()) |
367 | updateList(); |
368 | } |
369 | |
370 | void updateList() swing { |
371 | setComboBoxItems(cb, makeWatchTargets()); |
372 | } |
373 | |
374 | L<WatchTarget> makeWatchTargets() { |
375 | ret flattenToList( |
376 | countIteratorAsList_incl(1, screenCount = screenCount(), i -> WatchScreen(i)), |
377 | new WatchMouse |
378 | ); |
379 | } |
380 | } |
381 | |
382 | class SnPSelector { |
383 | settable new SnPSettings settings; |
384 | |
385 | event change; |
386 | |
387 | visualize { |
388 | var colors = jspinner(settings.colors, 2, 256); |
389 | main onChange(colors, -> { |
390 | settings.colors = intFromSpinner(colors); |
391 | change(); |
392 | }); |
393 | |
394 | var pixelRows = jPowersOfTwoSpinner(512, settings.pixelRows); |
395 | main onChange(pixelRows, -> { |
396 | settings.pixelRows = intFromSpinner(pixelRows); |
397 | change(); |
398 | }); |
399 | |
400 | ret hstack( |
401 | colors, |
402 | jlabel(" colors @ "), |
403 | pixelRows, |
404 | jlabel(" p")); |
405 | } |
406 | } |
407 | |
408 | JComponent wrapCRUD(SimpleCRUD_v2 crud) { |
409 | ret crud == null ?: withTopMargin(jRaisedSection(withMargin(crud.make_dontStartBots()))); |
410 | } |
411 | |
412 | File galleryDir() { |
413 | ret picturesDir(gazelle22_imagesSubDirName()); |
414 | } |
415 | |
416 | void saveScreenshotToGallery enter { |
417 | var img = imageStream!; |
418 | saveImageWithCounter(galleryDir(), "Screenshot", img); |
419 | } |
420 | |
421 | void addToGallery(File imgFile) { |
422 | if (!isImageFile(imgFile)) ret; |
423 | var img = uniq(concepts, GalleryImage, path := imgFile); |
424 | //printVars("addToGallery", +imgFile, +img); |
425 | } |
426 | |
427 | BWImage scaleAndPosterize(IBWIntegralImage ii, SnPSettings settings) { |
428 | ret posterizeBWImage_withMeta(settings.colors, |
429 | scaledBWImageFromBWIntegralImage_withMeta_height(settings.pixelRows, ii)); |
430 | } |
431 | |
432 | class StudyPanel { |
433 | new SnPSelector snpSelector; |
434 | GalleryImage image; |
435 | ImageSurface is = stdImageSurface(); |
436 | SingleComponentPanel scp = singleComponentPanel(); |
437 | SingleComponentPanel analysisPanel = singleComponentPanel(); |
438 | ConceptsComboBox<GalleryImage> cbImage = new(concepts, GalleryImage); |
439 | ImageToRegions itr; |
440 | int iSelectedRegion; |
441 | |
442 | *() { |
443 | cbImage.sortTheList = l -> sortConceptsByIDDesc(l); |
444 | |
445 | main onChangeAndNow(cbImage, img -> dm_q(me(), r { |
446 | image = img; |
447 | scp.setComponent(studyImagePanel()); |
448 | })); |
449 | |
450 | is.removeAllTools(); |
451 | is.onMousePositionChanged(r_dm_q(me(), l0 regionUpdate)); |
452 | |
453 | imageSurfaceOnLeftMouseDown(is, pt -> dm_q(me(), r { chooseRegionAt(pt) })); |
454 | |
455 | snpSelector.onChange(r_dm_q(me(), l0 runSnP)); |
456 | } |
457 | |
458 | void chooseRegionAt(Pt p) { |
459 | if (itr == null) ret; |
460 | iSelectedRegion = itr.regions.regionAt(p); |
461 | updateAnalysis(); |
462 | } |
463 | |
464 | // load new image |
465 | JComponent studyImagePanel() { |
466 | if (image == null) null; |
467 | var i = loadImage2(image.path); |
468 | if (i == null) ret jcenteredlabel("Image not found"); |
469 | |
470 | iSelectedRegion = 0; |
471 | itr = new ImageToRegions(i, snpSelector.settings); |
472 | runSnP(); |
473 | ret hsplit(jscroll_centered_borderless(is), analysisPanel); |
474 | } |
475 | |
476 | void runSnP { |
477 | if (itr == null) ret; |
478 | itr.run(); |
479 | regionUpdate(); |
480 | } |
481 | |
482 | void regionUpdate { |
483 | if (itr == null) ret; |
484 | var highlighted = highlightRegion(itr.posterized, is, itr.regions); |
485 | is.setImage(highlighted); |
486 | is.performAutoZoom(); // seems to be necessary for some reason |
487 | |
488 | updateAnalysis(); |
489 | } |
490 | |
491 | void updateAnalysis { |
492 | new LS lines; |
493 | lines.add(nRegions(itr.regions.regionCount())); |
494 | lines.add("Selected region: " + iSelectedRegion); |
495 | S text = lines_rtrim(lines); |
496 | analysisPanel.setComponent(jscroll(jMultiLineLabel(text))); |
497 | } |
498 | |
499 | visual |
500 | jRaisedSection(northAndCenterWithMargins( |
501 | centerAndEast(withLabel("Study", cbImage), |
502 | hstack(jlabel(" in "), snpSelector.visualize())), |
503 | scp)); |
504 | } // end of StudyPanel |
505 | |
506 | class ImageToRegions { |
507 | BufferedImage inputImage; |
508 | BWIntegralImage ii; |
509 | SnPSettings snpSettings; |
510 | BWImage posterized; |
511 | BWImage_FastRegions regions; |
512 | |
513 | *(BufferedImage *inputImage, SnPSettings *snpSettings) {} |
514 | |
515 | run { |
516 | ii = bwIntegralImage_withMeta(inputImage); |
517 | posterized = scaleAndPosterize(ii, snpSettings); |
518 | regions = new BWImage_FastRegions(posterized); |
519 | regions.collectBounds(); |
520 | functionTimings.time("Regions", regions); |
521 | } |
522 | } |
523 | } // end of module |
524 | |
525 | concept Label > ConceptWithGlobalID { |
526 | S name; |
527 | //new RefL examples; |
528 | |
529 | toString { ret "Label " + name; } |
530 | } |
531 | |
532 | concept GalleryImage { |
533 | File path; |
534 | |
535 | toString { ret /*"[" + id + "] " +*/ fileName(path); } |
536 | } |
537 | |
538 | concept SavedRegion { |
539 | new Ref image; // e.g. a GalleryImage |
540 | SnPSettings snpSettings; |
541 | Rect bounds; |
542 | BitMatrix bitMatrix; |
543 | //new RefL<Label> labels; |
544 | } |
545 | |
546 | concept Example { |
547 | new Ref<Label> label; |
548 | new Ref item; // e.g. a SavedRegion |
549 | double confidence = 1; |
550 | } |
551 | |
552 | concept IfThenTheory { |
553 | new Ref if_; |
554 | new Ref then; |
555 | } |
556 | |
557 | sclass WatchTarget {} |
558 | |
559 | // screenNr: 1 = screen 1 etc |
560 | srecord WatchScreen(int screenNr) > WatchTarget { |
561 | toString { ret "Screen " + screenNr; } |
562 | } |
563 | |
564 | srecord WatchMouse(int width, int height) > WatchTarget { |
565 | WatchMouse() { |
566 | height = 256; |
567 | width = iround(height*16.0/9); |
568 | } |
569 | |
570 | toString { ret "Mouse"; } |
571 | } |
572 | |
573 | // SnP = Scale and Posterize |
574 | persistable sclass SnPSettings { |
575 | int pixelRows = 128; |
576 | int colors = 8; |
577 | |
578 | *(int *pixelRows, int *colors) {} |
579 | } |
Began life as a copy of #1033862
download show line numbers debug dex old transpilations
Travelled to 2 computer(s): bhatertpkbcr, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1033924 |
Snippet name: | Gazelle Screen Cam / Gazelle 22 Module [backup before FlexibleRateTimer] |
Eternal ID of this version: | #1033924/1 |
Text MD5: | 9db08d41b57d7d33177c3c184ab40fbd |
Transpilation MD5: | 588eea842591e146471f700b367242cc |
Author: | stefan |
Category: | javax / gazelle v |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-01-12 16:22:52 |
Source code size: | 17597 bytes / 579 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 114 / 165 |
Referenced in: | [show references] |