1 package com.github.celldynamics.quimp.plugin.protanalysis; 2 3 import java.io.FileNotFoundException; 4 import java.io.PrintWriter; 5 import java.nio.file.Paths; 6 import java.util.ArrayList; 7 import java.util.HashSet; 8 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 12 import com.github.celldynamics.quimp.Outline; 13 import com.github.celldynamics.quimp.QParamsQconf; 14 import com.github.celldynamics.quimp.QuimpException; 15 import com.github.celldynamics.quimp.QuimpException.MessageSinkTypes; 16 import com.github.celldynamics.quimp.filesystem.FileExtensions; 17 import com.github.celldynamics.quimp.plugin.AbstractPluginQconf; 18 import com.github.celldynamics.quimp.plugin.QuimpPluginException; 19 import com.github.celldynamics.quimp.plugin.qanalysis.STmap; 20 21 import ij.ImagePlus; 22 23 // TODO Update UML below 24 /* 25 * !> 26 * @startuml doc-files/Prot_Analysis_1_UML.png 27 * salt 28 * { 29 * {^"Visual tracking" 30 * {+ 31 * Text field with help 32 * ... 33 * // Select points with // 34 * // CTRL key// 35 * } 36 * **Selected:** 4 37 * { 38 * [to ROI] | [from ROI] 39 * } 40 * [Clear all points] 41 * { (X) Static | () Dynamic} 42 * [X] Show tracked point 43 * [X] Smooth tracks 44 * ^Outline color ^ 45 * [ ] Open in new image 46 * [Track ] 47 * [Clear Overlay ] 48 * } 49 * {^"Maps" 50 * ^Select cell ^ 51 * { [Mot ] | [Convex] | [Fluo] } 52 * } 53 * {^"Tables and plots" 54 * [X] Plot selected 55 * ^Select cell ^ 56 * { (X) Ch1 | ( ) Ch2 | ( ) Ch3} 57 * ... 58 * { 59 * [X] X-Centr | [ ] Y-Centr 60 * [ ] Displ | [ ] Distance 61 * [ ] Direct | [ ] Speed 62 * [ ] Perim | [ ] Elong 63 * [ ] Circ | [ ] Area 64 * ==== | === 65 * [ ] Total fl | [ ] Mean fl 66 * [ ] Cortex wd | [ ] Cyto ar 67 * [ ] Total ctf | [ ] Mean ctf 68 * [ ] Cortex ar | [ ] Total ctf 69 * [ ] Mean ctf | 70 * } 71 * === 72 * [Generate ] 73 * } 74 * {^"Ploar plots" 75 * [Click point ] 76 * [Get from ROI ] 77 * Selected point: 127,45 78 * ^Select relative to^ 79 * [Show plots ] 80 * } 81 * } 82 * @enduml 83 * 84 * @startuml doc-files/Prot_Analysis_2_UML.png 85 * 86 * usecase UC0 as "**Load QCONF** 87 * -- 88 * Open QCONF file 89 * ..UC0.." 90 * 91 * usecase UC1 as "**Select points on contour** 92 * -- 93 * Use can click and select multiple 94 * points in cell contour. 95 * == 96 * This works within all frames 97 * ..UC1.. 98 * " 99 * 100 * usecase UC2 as "**Transfer points to ROI** 101 * -- 102 * Selected points can be 103 * copied to ROI manager 104 * ..UC2.. 105 * " 106 * 107 * usecase UC3 as "**Transfer points from ROI** 108 * -- 109 * Copy points from ROI 110 * manager and show them 111 * in contour. 112 * == 113 * * Delete old points 114 * * Deal with different 115 * frames 116 * ..UC3.. 117 * " 118 * 119 * usecase UC4 as "**Show selected points** 120 * -- 121 * Show points for each frame 122 * as user slide slider 123 * ..UC4.. 124 * " 125 * 126 * usecase UC5 as "**Clear points** 127 * -- 128 * Remove all points 129 * ..UC5.." 130 * 131 * usecase UC6 as "**Track points** 132 * -- 133 * Perform tracking for 134 * selected points 135 * == 136 * Regards static or dynamic 137 * ..UC6.. 138 * " 139 * 140 * usecase UC6b as "**Plot intensity** 141 * -- 142 * Plot intensity change 143 * over tracking point 144 * == 145 * This hsould be default 146 * for each tracking 147 * ..UC6b.. 148 * " 149 * 150 * usecase UC6a as "**Save tracks** 151 * -- 152 * Save tracks after 153 * tracking to csv 154 * file 155 * == 156 * ..UC6a.. 157 * " 158 * 159 * usecase UC7 as "**Display tracking** 160 * -- 161 * Show results on screen 162 * == 163 * * Depending on settings show 164 * in original window or separate 165 * * Show dynamic or static 166 * * Color outline 167 * * Smooth if option selected 168 * ..UC7.. 169 * " 170 * 171 * usecase UC8 as "**Color outline** 172 * -- 173 * Show outline in selected 174 * color 175 * == 176 * Colorscale scaled to range 177 * ..UC8.. 178 * " 179 * 180 * usecase UC9 as "**Clear overlay** 181 * -- 182 * Clear tracking 183 * == 184 * * Clear original window 185 * * Remove points 186 * ..UC9.." 187 * 188 * usecase UC10 as "**Plot maps** 189 * -- 190 * Show selected maps 191 * == 192 * Together with **UC10a** 193 * ROI allows to select 194 * maxim on the map and 195 * track them 196 * ..UC10.." 197 * 198 * usecase UC10a as "**Plot raw maps** 199 * -- 200 * Show selected maps as unscaled 201 * == 202 * Together with transferring 203 * ROI allows to select 204 * maxim on the map and 205 * track them 206 * ..UC10a.." 207 * 208 * usecase UC11 as "**Plot 2d** 209 * -- 210 * Plot selected metrics as 211 * 2D plot in function of 212 * frames 213 * == 214 * * Can open many plots at 215 * once 216 * * Should allow to select 217 * cell and channel 218 * ..UC11.." 219 * 220 * usecase UC12 as "**Copy to table** 221 * -- 222 * Copy selected metrics to 223 * IJ table. 224 * == 225 * * Should allow to select 226 * cell and channel 227 * ..UC12.. 228 * " 229 * 230 * usecase UC13 as "**Polar plots** 231 * -- 232 * Generate polar plots 233 * == 234 * * Save or show (depending on IJ 235 * features in showing vector files) 236 * * Show in log if saved 237 * ..UC13.. 238 * " 239 * 240 * usecase UC14 as "**Select origin point** 241 * -- 242 * Allow to select origin 243 * point for polar plots 244 * == 245 * * click on screen 246 * * Relative to screen 247 * * Relative to cell 248 * ..UC14.. 249 * " 250 * 251 * usecase UC15 as "**Predefined trackings** 252 * -- 253 * Allow to track points 254 * from predefined settings 255 * == 256 * Like: 257 * * Max from motility map 258 * ..UC15.. 259 * " 260 * 261 * usecase UC16 as "**Smooth tracks** 262 * -- 263 * Apply smoothing to tracks 264 * == 265 * * If option selected 266 * ..UC16.. 267 * " 268 * 269 * note bottom of (UC12) : Decide how to deal\nwith many tables 270 * 271 * note right of (UC8) 272 * Decide if standalone 273 * Now dependend from UC7 274 * Tracking must be done first and 275 * tracking map shown but this can 276 * be a standalone option as well 277 * end note 278 * 279 * User -> (UC0) 280 * User -> (UC1) 281 * (UC1) ..> (UC4) : <<include>> 282 * User -> (UC12) 283 * User -> (UC5) 284 * User -> (UC3) 285 * (UC3) ..> (UC5) : <<extend>> 286 * User -> (UC2) 287 * User -> (UC6) 288 * (UC6) ..> (UC7) : <<include>> 289 * (UC6) ..> (UC6a) : <<include>> 290 * (UC6) ..> (UC6b) : <<include>> 291 * (UC6) ..> (UC15) : <<include>> 292 * (UC7) ..> (UC8) : <<include>> 293 * (UC7) ..> (UC16) : <<include>> 294 * User -> (UC9) 295 * (UC9) ..> (UC5) : <<extend>> 296 * User --> (UC10) 297 * (UC10) ..> (UC10a) : <<include>> 298 * User --> (UC11) 299 * User --> (UC13) 300 * (UC13) ..> (UC14) : <<include>> 301 * @enduml 302 * !< 303 */ 304 /** 305 * Main class for Protrusion Analysis module. 306 * 307 * <p>Contain business logic for protrusion analysis. The UI is built by 308 * {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisUi}. The communication 309 * between 310 * these modules is through 311 * {@link com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions} 312 * 313 * <br> 314 * <img src="doc-files/Prot_Analysis_1_UML.png"/><br> 315 * 316 * <br> 317 * <img src="doc-files/Prot_Analysis_2_UML.png"/><br> 318 * 319 * @author p.baniukiewicz 320 */ 321 public class Prot_Analysis extends AbstractPluginQconf { 322 323 static final Logger LOGGER = LoggerFactory.getLogger(Prot_Analysis.class.getName()); 324 325 private static String thisPluginName = "Protrusion Analysis"; 326 327 ImagePlus image = null; 328 // points selected by user for current frame, cleared on each slice shift. In image coordinates 329 PointHashSet selected = new PointHashSet(); 330 // updated on each slice, outlines for current frame 331 ArrayList<Outline> outlines = new ArrayList<>(); 332 /** 333 * Instance of module UI. 334 * 335 * <p>Initialised by this constructor. 336 */ 337 ProtAnalysisUi frameGui; 338 339 /** 340 * Current frame, 0-based. 341 */ 342 int currentFrame = 0; 343 344 /** 345 * Default constructor. 346 * 347 */ 348 public Prot_Analysis() { 349 // here we do not load file! so can not create ui 350 super(new ProtAnalysisOptions(), thisPluginName); 351 selected = new PointHashSet(); 352 outlines = new ArrayList<>(); 353 } 354 355 /** 356 * Constructor that allows to provide own configuration parameters. 357 * 358 * <p>Immediately executes all computations. 359 * 360 * @param paramString parameter string. 361 * @throws QuimpPluginException on error 362 */ 363 public Prot_Analysis(String paramString) throws QuimpPluginException { 364 // 1. Load File 365 // 2. Run runFormQconf (but we overwritten it here to be empty so nothing happens yet) 366 super(paramString, new ProtAnalysisOptions(), thisPluginName); 367 selected = new PointHashSet(); 368 outlines = new ArrayList<>(); 369 createUIInstance(); 370 } 371 372 /** 373 * Create UI instance. 374 * 375 * <p>Require loaded file. 376 */ 377 private void createUIInstance() { 378 ImagePlus image = getImage(); // obain image from loaded file 379 LOGGER.trace("Attached image " + image.toString()); 380 frameGui = new ProtAnalysisUi(this, image); // build UI (we need image) 381 } 382 383 /** 384 * get current sink. 385 * 386 * @return sink type 387 */ 388 MessageSinkTypes getSink() { 389 return errorSink; 390 } 391 392 /* 393 * (non-Javadoc) 394 * 395 * @see com.github.celldynamics.quimp.plugin.PluginTemplate#validate() 396 */ 397 @Override 398 protected void validate() throws QuimpException { 399 super.validate(); 400 qconfLoader.getEcmm(); 401 qconfLoader.getQ(); 402 qconfLoader.getStats(); 403 } 404 405 /** 406 * Write cell statistic and protrusion statistics to files. 407 * 408 * <p>Currently not used might be useful. Example of use: 409 * 410 * <pre> 411 * <code> 412 * // write stats, and add to table 413 * writeStats(h, mapCell, mf, trackCollection).cellStatistics.addCellToCellTable(rt); 414 * </code> 415 * </pre> 416 * 417 * @param h Cell number 418 * @param mapCell cell map 419 * @param mf maxima finder object 420 * @param trackCollection track collection object 421 * @return ProtStat instance of object that keeps cell statistics. Can be used to form e.g. 422 * table with results. 423 * 424 * @throws FileNotFoundException if stats can not be written 425 */ 426 private ProtStat writeStats(int h, STmap mapCell, MaximaFinder mf, 427 TrackCollection trackCollection) throws FileNotFoundException { 428 QParamsQconf/../../../../../com/github/celldynamics/quimp/QParamsQconf.html#QParamsQconf">QParamsQconf qp = (QParamsQconf) qconfLoader.getQp(); 429 // Maps are correlated in order with Outlines in DataContainer. 430 // write data 431 PrintWriter cellStatFile = new PrintWriter( 432 Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.cellStatSuffix) 433 .toFile()); 434 PrintWriter protStatFile = new PrintWriter( 435 Paths.get(qp.getPath(), qp.getFileName() + "_" + h + FileExtensions.protStatSuffix) 436 .toFile()); 437 new ProtStat(mf, trackCollection, qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell) 438 .writeProtrusion(protStatFile, h); 439 440 ProtStatimp/plugin/protanalysis/ProtStat.html#ProtStat">ProtStat cellStat = new ProtStat(mf, trackCollection, 441 qp.getLoadedDataContainer().getStats().sHs.get(h), mapCell); 442 443 cellStat.writeCell(cellStatFile, h); 444 protStatFile.close(); 445 cellStatFile.close(); 446 return cellStat; 447 } 448 449 /* 450 * (non-Javadoc) 451 * 452 * @see com.github.celldynamics.quimp.plugin.AbstractPluginBase#showUi(boolean) 453 */ 454 @Override 455 public void showUi(boolean val) throws Exception { 456 // we need to load file here because UI require image from QCONF 457 // execute this only if run with default constructor and empty run("") method (plugin from menu) 458 // if (options.paramFile == null || options.paramFile.isEmpty()) { 459 // loadFile(options.paramFile); 460 // if (qconfLoader != null && qconfLoader.getQp() != null) { 461 // options.paramFile = qconfLoader.getQp().getParamFile().getAbsolutePath(); 462 // } 463 // createUIInstance(); 464 // } 465 if (frameGui != null) { 466 frameGui.showUI(true); 467 // gui.setVisible(true); 468 } else { 469 LOGGER.error("You need image (and QCONF) to see UI"); 470 } 471 } 472 473 /* 474 * (non-Javadoc) 475 * 476 * @see com.github.celldynamics.quimp.plugin.IQuimpPlugin#about() 477 */ 478 @Override 479 public String about() { 480 return "Protrusion Analysis Plugin.\n" + "Author: Piotr Baniukiewicz\n" 481 + "mail: p.baniukiewicz@warwick.ac.uk"; 482 } 483 484 /* 485 * (non-Javadoc) 486 * 487 * @see ij.plugin.PlugIn#run(java.lang.String) 488 */ 489 @Override 490 public void run(String arg) { 491 super.run(arg); 492 } 493 494 /* 495 * (non-Javadoc) 496 * 497 * @see com.github.celldynamics.quimp.plugin.AbstractOptionsParser#parseArgumentString(java.lang. 498 * String) 499 */ 500 @Override 501 protected boolean parseArgumentString(String arg) throws QuimpPluginException { 502 // override only to get always true at output, hack that will cause execute executer in run() 503 // method, and then LoadFile and runFromQconf. We need this because we do not support parameters 504 // and want to see UI every time but we need loaded file to build UI. 505 // With this approach we have the same path for IJ plugin run and IJ macro run (only file): 506 507 // run like IJ macro - scenario 1 508 // Prot_Analysis pa = new Prot_Analysis(); 509 // pa.run(""); 510 511 // IJ from script - scenario 2 512 // Prot_Analysis pa = new Prot_Analysis(); 513 // pa.run("{paramFile:src/test/Resources-static/ProtAnalysisTest/fluoreszenz-test.QCONF}"); 514 // Without this hack scenario 1 will try to open UI (showUI) without loaded file. 515 super.parseArgumentString(arg); 516 return true; 517 } 518 519 @Override 520 protected void runFromPaqp() throws QuimpException { 521 throw new QuimpException("This plugin does not support paQP files."); 522 523 } 524 525 /* 526 * (non-Javadoc) 527 * 528 * @see com.github.celldynamics.quimp.plugin.AbstractPluginQconf#runFromQconf() 529 */ 530 @Override 531 protected void runFromQconf() throws QuimpException { 532 // we do not support run from macro so just show UI here 533 createUIInstance(); 534 try { 535 showUi(true); 536 } catch (Exception e) { 537 throw new QuimpException(e); 538 } 539 } 540 541 /** 542 * Get image associated with loaded QCONF. 543 * 544 * @return image or null if image could not be loaded 545 */ 546 ImagePlus getImage() { 547 if (image == null) { 548 if (getQconfLoader() != null) { 549 image = getQconfLoader().getImage(); 550 } else { 551 throw new RuntimeException("Can not obtain image"); 552 } 553 } 554 return image; 555 } 556 557 /** 558 * Get gui. 559 * 560 * @return Main window class. 561 */ 562 ProtAnalysisUi getGui() { 563 return frameGui; 564 } 565 566 /** 567 * Keep list of selected points. 568 * 569 * <p>Reason of this class is that {@link ProtAnalysisUi} and {@link CustomCanvas} operate on 570 * 2D 571 * images without knowledge about frame, which is needed. They also use java.awt.Point as main 572 * class. Therefore the point selected in the image by user in {@link CustomCanvas} contains only 573 * x,y and cell number (all stored in {@link PointCoords}). Frame number is appended with 574 * {@link PointHashSet#add(PointCoords)}, called in this context (frame is stored in 575 * Prot_Analysis.currentFrame) 576 * 577 * <p>Field {@link Prot_Analysis#currentFrame} is updated by {@link ProtAnalysisUi} whereas 578 * point operations happen in {@link CustomCanvas}. {@link Prot_Analysis} integrates all 579 * informations. 580 * 581 * @author p.baniukiewicz 582 * 583 */ 584 @SuppressWarnings("serial") 585 class PointHashSet extends HashSet<PointCoords> { 586 587 /** 588 * Add information about current frame to point. 589 * 590 * @param e 2D point from current frame. Field {@link PointCoords#frame} will be overwritten by 591 * current frame. 592 * @return true if point exists in set. 593 */ 594 @Override 595 public boolean add(PointCoords e) { 596 e.frame = currentFrame; 597 LOGGER.debug("Added point: " + e); 598 return super.add(e); 599 } 600 601 /** 602 * Add {@link PointCoords} with frame number. 603 * 604 * <p>In contrary to {@link #add(PointCoords)} this method does not override frame number in 605 * specified {@link PointCoords}. 606 * 607 * @param e 2D point from current frame. 608 * @return true if point exists in set. 609 */ 610 boolean addRaw(PointCoords e) { 611 LOGGER.debug("Added raw point: " + e); 612 return super.add(e); 613 } 614 615 /** 616 * Remove point from set. 617 * 618 * @param e point to remove 619 * @return true if exist 620 */ 621 public boolean remove(PointCoords e) { 622 e.frame = currentFrame; 623 return super.remove(e); 624 } 625 626 } 627 628 }