--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="lib" path="/home/lingutln/Downloads/ist-source/Interactive Segmentation App/lib/swt-gtk-64.jar"/>
+ <classpathentry kind="lib" path="/home/lingutln/Downloads/ist-source/Interactive Segmentation App/lib/jface.jar"/>
+ <classpathentry kind="lib" path="/home/lingutln/workspace/image_annotation/lib/json.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>Annotation</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+#Tue Dec 13 14:02:38 PST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
--- /dev/null
+Signature-Version: 1.0\r
+SHA1-Digest-Manifest-Main-Attributes: 5EWAvbAiR6Q4Q79WtdyMPP5did0=\r
+Created-By: 1.5.0 (IBM Corporation)\r
+SHA1-Digest-Manifest: 84TjmKKEeQbxGf6FHXw8xH9Wnt4=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$2.class\r
+SHA1-Digest: iGZzlMSQnikJlp+jw0oCR4mBOgE=\r
+\r
+Name: org/eclipse/jface/preference/PreferencePage$3.class\r
+SHA1-Digest: qF1zFYwLQhp4SagSACM13Hj3CbQ=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$4.class\r
+SHA1-Digest: jTe+5Kj0fw7HBrMorB9wnfwA+aQ=\r
+\r
+Name: org/eclipse/jface/preference/RadioGroupFieldEditor.class\r
+SHA1-Digest: GvdMdbOhtG0PFj1BCRskOj/mncc=\r
+\r
+Name: org/eclipse/jface/dialogs/Dialog.class\r
+SHA1-Digest: zG6Ozv8VWfsFI7UDzB8XlSeEUFM=\r
+\r
+Name: org/eclipse/jface/internal/JFaceActivator.class\r
+SHA1-Digest: uCLmH9z+xLKaGimc/A2D34kztrI=\r
+\r
+Name: org/eclipse/jface/bindings/CachedBindingSet.class\r
+SHA1-Digest: 7rqIEDyVwUfjsGZBymbuHjpcuZ0=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerRow.class\r
+SHA1-Digest: 8qoYH649iJt30GFqXWgvxGgPxVI=\r
+\r
+Name: org/eclipse/jface/fieldassist/IContentProposalListener.class\r
+SHA1-Digest: tmRtj73sYnqzleK2xxiOyzE1L1k=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$4.class\r
+SHA1-Digest: m2DvMWv0btAAjBlhol5Yyyi1Hjw=\r
+\r
+Name: org/eclipse/jface/viewers/ICellModifier.class\r
+SHA1-Digest: MLWSDSBJALcKVtHybMcxQvrkiuw=\r
+\r
+Name: org/eclipse/jface/dialogs/DialogPage.class\r
+SHA1-Digest: paRcOAFkjDH7SYEVfeqMKqVKJa0=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressMonitorDialog.class\r
+SHA1-Digest: +EdvdkPTMVBPM4ecBxhkEZrKqtk=\r
+\r
+Name: org/eclipse/jface/action/AbstractAction.class\r
+SHA1-Digest: w0kJoNREzOzn43LG2L5iaFwnaPc=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$ColorAndFontCollector\r
+ .class\r
+SHA1-Digest: K/6GSuix21oodCJuGwUG6ztcofw=\r
+\r
+Name: org/eclipse/jface/dialogs/IconAndMessageDialog.class\r
+SHA1-Digest: nYuOeO2gOZtFkL31QpAAvvBrjVI=\r
+\r
+Name: org/eclipse/jface/fieldassist/SimpleContentProposalProvider$1.cl\r
+ ass\r
+SHA1-Digest: nxcSSDXMNp3hYPYbERVhhXDSfwU=\r
+\r
+Name: org/eclipse/jface/dialogs/DialogMessageArea.class\r
+SHA1-Digest: 0pmQEGMPsY/H9zdgGsE/LdE0qKs=\r
+\r
+Name: org/eclipse/jface/operation/IRunnableContext.class\r
+SHA1-Digest: /EcDpCNiCk6CuDPOVJ9/VLxELxI=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$4.class\r
+SHA1-Digest: 0tpIkrdSqJGGtf6gLpvs5mixjoI=\r
+\r
+Name: org/eclipse/jface/dialogs/MessageDialogWithToggle.class\r
+SHA1-Digest: Pc562HrcnM8tamv1cFpR2t/4AK4=\r
+\r
+Name: org/eclipse/jface/viewers/Viewer$2.class\r
+SHA1-Digest: Cx2F7HkRfJfkgTCmv3fya0SJgD0=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$5.class\r
+SHA1-Digest: jMg3zDTLAxesrNizIY+xdwPv8Wg=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditorActivationStrategy.c\r
+ lass\r
+SHA1-Digest: 7jzZvzV76INEtLejjrRF0KpFMJI=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewerEditor.class\r
+SHA1-Digest: 7L/k0DP2bSjtrQIQo+KgChSJML8=\r
+\r
+Name: org/eclipse/jface/bindings/keys/IKeyLookup.class\r
+SHA1-Digest: 3RO+TXrY+GUrNbqfPdXdS/umgF8=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewerEditor.class\r
+SHA1-Digest: YOBbpDpgWafz8nz4AFar85A7xoI=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTableViewer$1.class\r
+SHA1-Digest: 90MbjrI3u2AXcS3vs2ztUNUlp9g=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$8.class\r
+SHA1-Digest: 36SQLLvrPr8LTXtO4/9+mFaI1vc=\r
+\r
+Name: org/eclipse/jface/fieldassist/ComboContentAdapter.class\r
+SHA1-Digest: QRz+sVte5Ms73iIHkFfgsWy1i3Y=\r
+\r
+Name: org/eclipse/jface/action/IStatusLineManager.class\r
+SHA1-Digest: a4GdvF+6pe+bbvRc6QJRyUaQoLk=\r
+\r
+Name: org/eclipse/jface/window/IShellProvider.class\r
+SHA1-Digest: YLuzbe/8n6jGYsAGro9+z7HrwGU=\r
+\r
+Name: org/eclipse/jface/viewers/ListViewer.class\r
+SHA1-Digest: DKR66oVPaOLKJJOAIrM6Z7z+Ajo=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$1.class\r
+SHA1-Digest: 0gy1qbGJIfonNbxdi/D6T+7RPEQ=\r
+\r
+Name: org/eclipse/jface/viewers/ILazyTreeContentProvider.class\r
+SHA1-Digest: qWN0XG4Qhia6KvxFLrIPK/eVfEE=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$4.class\r
+SHA1-Digest: QgEz4CI4aGL5ZykOCKFuE28CDu0=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$KeyTrapListener.\r
+ class\r
+SHA1-Digest: 0N7egGc+uYroh6YoN8QtX3UG1oA=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceLabelProvider.class\r
+SHA1-Digest: 89CKBoan2dnwFULZWROtmX+0Cpk=\r
+\r
+Name: org/eclipse/jface/action/ToolBarContributionItem$4.class\r
+SHA1-Digest: GBm74NJU+trWLBjMR5mvo386ymA=\r
+\r
+Name: org/eclipse/jface/bindings/SchemeEvent.class\r
+SHA1-Digest: hoZW87aPwR8U0YvqYaZI9HMu6C8=\r
+\r
+Name: org/eclipse/jface/commands/PersistentState.class\r
+SHA1-Digest: yL3lMIitYZ+ms6h0zAmyB6SZCFk=\r
+\r
+Name: org/eclipse/jface/viewers/IBasicPropertyConstants.class\r
+SHA1-Digest: lIP3Ni4M1fUH5CwarS0KMtioceg=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter.class\r
+SHA1-Digest: rA1DcSYjmxcedjABiaEKospSAto=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager.properties\r
+SHA1-Digest: UueIvQ/nF9uuYBwcs1azcYU19uc=\r
+\r
+Name: org/eclipse/jface/preference/BooleanFieldEditor$1.class\r
+SHA1-Digest: 5kEPR9EotiV4X1c6jMjI0oqK/Ns=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressMonitorDialog$3.class\r
+SHA1-Digest: PyFbLevAtCrXakbSmqpXhcWajzM=\r
+\r
+Name: org/eclipse/jface/resource/ImageDescriptor.class\r
+SHA1-Digest: WzgmcUKovAQ9VO4qE9zDbzxTpOk=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/DeferredContentProvider.class\r
+SHA1-Digest: 9Wk50rmWV3ImvRvXRG58iffbx+4=\r
+\r
+Name: org/eclipse/jface/window/Window$3.class\r
+SHA1-Digest: qtonjTRSx2Lt3CMTj6DbJbaLnbA=\r
+\r
+Name: org/eclipse/jface/viewers/CustomHashtable$HashMapEntry.class\r
+SHA1-Digest: tPgEFUQac0g46rfQBPdpf0vMVTU=\r
+\r
+Name: org/eclipse/jface/viewers/ContentViewer.class\r
+SHA1-Digest: XRPy4VxsQalW4K3gTLZ0C/AZvCs=\r
+\r
+Name: org/eclipse/jface/viewers/IFontProvider.class\r
+SHA1-Digest: g/dCUHHOCwC1gWJnqYVUuOkbLWE=\r
+\r
+Name: org/eclipse/jface/preference/IPersistentPreferenceStore.class\r
+SHA1-Digest: 0h/VCibXYY6f837VN8gZHYVC9AY=\r
+\r
+Name: org/eclipse/jface/viewers/ISelectionChangedListener.class\r
+SHA1-Digest: zsv0sq11yrTP//btCXjMitoyTUg=\r
+\r
+Name: org/eclipse/jface/viewers/CheckboxTreeViewer.class\r
+SHA1-Digest: 2IuKVo0W0eefa1r+aXz1h54zCAQ=\r
+\r
+Name: org/eclipse/jface/operation/IRunnableWithProgress.class\r
+SHA1-Digest: pU3FXK0vb68suXDN0vVqFRsVi0U=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/AbstractKeyFormatter.\r
+ properties\r
+SHA1-Digest: qQ7mPHSVsC3AQZDzY6RmQ1DfwiA=\r
+\r
+Name: org/eclipse/jface/viewers/DecoratingLabelProvider.class\r
+SHA1-Digest: vcnZjCwHH8uRYDf3nDQXm04+ZwI=\r
+\r
+Name: org/eclipse/jface/resource/FontRegistry$FontRecord.class\r
+SHA1-Digest: 1eBoKicV0hgzRjOxO3Rx/ebUozg=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$4.class\r
+SHA1-Digest: CGbQ81V61G+73ENinJAskhr2jlY=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/SetModel.class\r
+SHA1-Digest: h5lrw51Xq5crMZMvRi6MWgsl76M=\r
+\r
+Name: org/eclipse/jface/viewers/ITableFontProvider.class\r
+SHA1-Digest: PXD0wkY3N2bk3rpL6+CIMOu+dec=\r
+\r
+Name: org/eclipse/jface/preference/FieldEditor.class\r
+SHA1-Digest: 7K4g+fddHzmlVs8G2DpfW6RY5Xg=\r
+\r
+Name: org/eclipse/jface/preference/StringFieldEditor$4.class\r
+SHA1-Digest: 1n/UsXplUufL9Rt1kw8uJQOkvqE=\r
+\r
+Name: org/eclipse/jface/wizard/IWizard.class\r
+SHA1-Digest: OpJzFa5/5cY1FZmwhojIvRpb15Y=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$6.class\r
+SHA1-Digest: oYD52jDXsjqz7sYPpI+g19uH2ws=\r
+\r
+Name: org/eclipse/jface/fieldassist/AutoCompleteField.class\r
+SHA1-Digest: 3N38AL22/8ZgQEatQpXjm3ZsMps=\r
+\r
+Name: org/eclipse/jface/action/Separator.class\r
+SHA1-Digest: UvxUEudgExshA7J7wb5BoFESz0g=\r
+\r
+Name: org/eclipse/jface/resource/ColorRegistry$1.class\r
+SHA1-Digest: f71p6xN807Y6b/y2nqF5gmzazBY=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/AbstractKeyFormatter.\r
+ class\r
+SHA1-Digest: JijjWGplCRMUfslb2x2ncB9+6Pw=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor.class\r
+SHA1-Digest: mEDoY6sUprGzmhKjO1dniSDCa10=\r
+\r
+Name: org/eclipse/jface/window/ApplicationWindow.class\r
+SHA1-Digest: IZl1iLxQgeG+UAsoLbsEQM8RSxw=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$3.class\r
+SHA1-Digest: VQYLNVmwiILmm++iCDrzx+LWZEo=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$2.class\r
+SHA1-Digest: heTOuGjccnCCplk8AqdiPQYklD8=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$8.class\r
+SHA1-Digest: jI6Z36lYy2QgSdrlOR2sxyJNSNc=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$4.class\r
+SHA1-Digest: iADBpnr8VrqHDJKTsSHkacaBMC8=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$10.class\r
+SHA1-Digest: FAxocK04ZrFWJFeAMtCAs+9CRJo=\r
+\r
+Name: org/eclipse/jface/preference/RadioGroupFieldEditor$2.class\r
+SHA1-Digest: AJVgCJqIuJFv2xiltX4vRp4VoQY=\r
+\r
+Name: org/eclipse/jface/resource/AbstractResourceManager.class\r
+SHA1-Digest: KknrjuPVMJyKGhIyeALK1NhGbKo=\r
+\r
+Name: org/eclipse/jface/action/StatusLineManager$1.class\r
+SHA1-Digest: jE5hBsg9R+QrrP3IIr2ZZ10Ug9A=\r
+\r
+Name: org/eclipse/jface/preference/ColorSelector$1.class\r
+SHA1-Digest: bMvVHd/a7h/77azt3YgqdnCYsPU=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/BackgroundContentProvider.cla\r
+ ss\r
+SHA1-Digest: 5roAVac/frvjMj67eyWqhEIKEIU=\r
+\r
+Name: org/eclipse/jface/action/StatusLine$2.class\r
+SHA1-Digest: G+r8KY3LMsnDaqaKfhWAmSJ+9Qg=\r
+\r
+Name: org/eclipse/jface/dialogs/MessageDialog.class\r
+SHA1-Digest: N374r8GdoJv197GWY9mqNH5eJwA=\r
+\r
+Name: org/eclipse/jface/dialogs/IDialogBlockedHandler.class\r
+SHA1-Digest: tKloZ49QXCFTDoMfu1Vb2+MxJtQ=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$6.class\r
+SHA1-Digest: GqUX7gcsWOQaipxTsfDEuylDye8=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/AbstractVirtualTable.class\r
+SHA1-Digest: brnlUiB6TTYrfbSsYrcFQkdnv7Y=\r
+\r
+Name: org/eclipse/jface/viewers/IFontDecorator.class\r
+SHA1-Digest: 891zp1j09NBrzqwkKlmW6pm/1+U=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$15.class\r
+SHA1-Digest: wTJzQ/mvWex9xus+cCxH5esrbhs=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/AbstractConcurrentModel.class\r
+SHA1-Digest: Pl29R9jkzZNC/ahl5xDa1TrFOEE=\r
+\r
+Name: org/eclipse/jface/viewers/SelectionChangedEvent.class\r
+SHA1-Digest: D0XjDa8cFH1VCo6lbeTMx/FQkws=\r
+\r
+Name: org/eclipse/jface/viewers/CheckboxTreeViewer$1.class\r
+SHA1-Digest: TNNQiMqPUJud17cCPA2IEFcw+Og=\r
+\r
+Name: org/eclipse/jface/wizard/WizardSelectionPage.class\r
+SHA1-Digest: uE3H+08Tiu1/bqHwyx2gXgfWe3o=\r
+\r
+Name: org/eclipse/jface/viewers/SWTFocusCellManager$1.class\r
+SHA1-Digest: JwDhPhtCJhYEkzul0sQa0iCjwqg=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$5.class\r
+SHA1-Digest: IGv9QPtbXGBGrCFLlICFjvhF6uI=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration.class\r
+SHA1-Digest: Wymg79Vuh3R2r18c3reOJiOzNto=\r
+\r
+Name: org/eclipse/jface/viewers/ComboBoxCellEditor$4.class\r
+SHA1-Digest: 2Mv9grE6uGu+MLdLZaUPzTXHf8A=\r
+\r
+Name: org/eclipse/jface/viewers/OwnerDrawLabelProvider$2.class\r
+SHA1-Digest: SpYWw9tEhqwiWSOsnDNqB2mrb3E=\r
+\r
+Name: org/eclipse/jface/resource/FontDescriptor.class\r
+SHA1-Digest: eT5gH5T1mgfxlTl/DeD9o/+2xcQ=\r
+\r
+Name: org/eclipse/jface/preference/PreferencePage.class\r
+SHA1-Digest: fkGGQdxuSvXQhwO/6nTr4s2pFSs=\r
+\r
+Name: org/eclipse/jface/util/PropertyChangeEvent.class\r
+SHA1-Digest: EajgzipEUA6FJYIyyR8HlDUaJuU=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$Hover.class\r
+SHA1-Digest: CwXHp+oid5QN8CC/agHhYygRu/0=\r
+\r
+Name: org/eclipse/jface/dialogs/PageChangingEvent.class\r
+SHA1-Digest: EzH4BwyIhpqgE4NpiDdYb77D2RI=\r
+\r
+Name: org/eclipse/jface/viewers/IDecorationContext.class\r
+SHA1-Digest: uo0idfey67ODyrk3PuQe0JBR1Co=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/ConcurrentTableUpdator$1.clas\r
+ s\r
+SHA1-Digest: D4I8+a7OEFF+9scpYvL8fPNE35U=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog.class\r
+SHA1-Digest: j3QiBHFGoqNYM9lYRlKjJACLmxo=\r
+\r
+Name: org/eclipse/jface/viewers/IInputProvider.class\r
+SHA1-Digest: +4gWEOSXC6MMfxwKnaxLWuHxC+c=\r
+\r
+Name: org/eclipse/jface/fieldassist/IContentProposalProvider.class\r
+SHA1-Digest: Me8Rz5jWeHYl8X6CEASTGEOjXng=\r
+\r
+Name: org/eclipse/jface/window/SameShellProvider.class\r
+SHA1-Digest: D+ZZxaND3T79NvHygMhumBnTezU=\r
+\r
+Name: org/eclipse/jface/action/IContributionManagerOverrides.class\r
+SHA1-Digest: XT0XSk1MLNZgd1GM64ODnBmwKeM=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDragAdapter$1.class\r
+SHA1-Digest: 9fux831/DeidfEcSTMrOkGOicBU=\r
+\r
+Name: org/eclipse/jface/window/Window.class\r
+SHA1-Digest: f2LgB6JO8pAE3znhtDOhHtOsPbU=\r
+\r
+Name: org/eclipse/jface/fieldassist/IControlContentAdapter.class\r
+SHA1-Digest: X56J3Fu2l9LAmCgRSEygaTm/E2Q=\r
+\r
+Name: org/eclipse/jface/util/Policy$1.class\r
+SHA1-Digest: FdI6d9oWZD67J54iUPVuU07nlyE=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnPixelData.class\r
+SHA1-Digest: 3hM93QKuNRIeZy7kMUSzW3G2bjc=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/FormalKeyFormatter.cl\r
+ ass\r
+SHA1-Digest: NLAxKJ3zlljxjxs4Qtf1U1JEaEg=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_hp-ux.properties\r
+SHA1-Digest: DdgNeYu/6JwIhnA+ziuY1MfdoMY=\r
+\r
+Name: org/eclipse/jface/window/ApplicationWindow$1.class\r
+SHA1-Digest: pZkzltYH/pT7NJT6JoBaOLA2e/w=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$1.class\r
+SHA1-Digest: vDSjOG6fISzrQOq+81/A0NXaeak=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$1.class\r
+SHA1-Digest: T7wVfH1jsrxsAwFuDwOw2VujKuI=\r
+\r
+Name: org/eclipse/jface/fieldassist/FieldDecoration.class\r
+SHA1-Digest: yxaNFae07DcPFqt2cS9kkPgyQgE=\r
+\r
+Name: org/eclipse/jface/resource/URLImageDescriptor.class\r
+SHA1-Digest: xfpFtZYPkTeIkKwUPH7bVYuI0eE=\r
+\r
+Name: org/eclipse/jface/bindings/Binding.class\r
+SHA1-Digest: qUbUSRqzW5b60ay9Stc2r0XhA8E=\r
+\r
+Name: org/eclipse/jface/util/OpenStrategy$4.class\r
+SHA1-Digest: Q34pdgB010llEvChdiVxC7HRZmc=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$7.class\r
+SHA1-Digest: i7tNTnZWospqj0NsfD+Cb3O1nvM=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditor.class\r
+SHA1-Digest: qT62vM+iZ793659vi43YypCpRjI=\r
+\r
+Name: org/eclipse/jface/preference/ColorFieldEditor.class\r
+SHA1-Digest: mPMTPU8DZsIHQTfCRx8M2ghvs7s=\r
+\r
+Name: org/eclipse/jface/action/IToolBarManager.class\r
+SHA1-Digest: i+c+nu4vep+zBSrZuORi3Sc/b8o=\r
+\r
+Name: org/eclipse/jface/dialogs/DialogTray.class\r
+SHA1-Digest: 5jxPOwxsY0mRt7x6q1ADmpQ5tk4=\r
+\r
+Name: org/eclipse/jface/bindings/ISchemeListener.class\r
+SHA1-Digest: MMWaPQ7IW9t4niSnqpOqkk2J2ug=\r
+\r
+Name: org/eclipse/jface/resource/ColorRegistry.class\r
+SHA1-Digest: ghlErnokdMnUppnC5Rg64UspcYk=\r
+\r
+Name: org/eclipse/jface/layout/TreeColumnLayout$TreeLayoutListener.cla\r
+ ss\r
+SHA1-Digest: 70Vw7oLYGelbGNaXCxT7JoAp7ko=\r
+\r
+Name: org/eclipse/jface/viewers/BaseLabelProvider$1.class\r
+SHA1-Digest: VJ0cyhBjSbI1mJUv63hSalGJKYQ=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$3.class\r
+SHA1-Digest: Mm3k4+hXRB2D0n0mMc8FLM0MebE=\r
+\r
+Name: org/eclipse/jface/viewers/IColorProvider.class\r
+SHA1-Digest: 8DcdSk9zjqjd/JtQUfu3j6/h/gQ=\r
+\r
+Name: org/eclipse/jface/preference/PreferencePage$4.class\r
+SHA1-Digest: lknbKDfMDxyQskxfxKKxN1GCJ9Q=\r
+\r
+Name: org/eclipse/jface/resource/ImageRegistry.class\r
+SHA1-Digest: 8gXTaXNH5orPlV5rTOOg7MzXXR0=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerColumn.class\r
+SHA1-Digest: e2VIRAB1IO+iCFTJEchSoiz3XF8=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerComparator.class\r
+SHA1-Digest: EBmM3EbHDfVZThgHARGD4cT6Wyw=\r
+\r
+Name: org/eclipse/jface/action/ToolBarManager.class\r
+SHA1-Digest: C7ImvSN0ik3hKhB/r6tWPjEtdrg=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/IConcurrentModel.class\r
+SHA1-Digest: FNQSCi9PiBLCYeAzDohidY6sP5w=\r
+\r
+Name: org/eclipse/jface/action/IContributionItem.class\r
+SHA1-Digest: bXDHmVshoXP39HMPjpqJz0NuwK8=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$5.class\r
+SHA1-Digest: 4JsPFuoXAugqDDTWI5r5en2z3hY=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/ToolBarManager2.cl\r
+ ass\r
+SHA1-Digest: 5+YgtcblKrsWfrixChkGIYI9VUg=\r
+\r
+Name: org/eclipse/jface/viewers/BaseLabelProvider.class\r
+SHA1-Digest: /5bVcq79HIp/BwtP46v/pFMPIhk=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$1.class\r
+SHA1-Digest: IvzKz6Xl699zp2RS/tAJDVRgWgE=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$10.class\r
+SHA1-Digest: Bg2Kn2uLL08EWFb+4HAimWZkOzc=\r
+\r
+Name: org/eclipse/jface/viewers/ITreePathLabelProvider.class\r
+SHA1-Digest: 8Nb2jUmFJW0Jn+LeSAYeweeo2Ew=\r
+\r
+Name: org/eclipse/jface/viewers/LabelProviderChangedEvent.class\r
+SHA1-Digest: mbQZ/j6whMi4RcB8mjBveK7zcTE=\r
+\r
+Name: org/eclipse/jface/action/ToolBarContributionItem.class\r
+SHA1-Digest: C9BtCUPurPzQewoeYCwFDrdZGR8=\r
+\r
+Name: org/eclipse/jface/action/LegacyActionTools.class\r
+SHA1-Digest: P57JOvtufqnWZsMqVEdHjcNEwxo=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$5.class\r
+SHA1-Digest: GKMNPGNT2UZQRMo09apctBBvFpo=\r
+\r
+Name: org/eclipse/jface/viewers/SWTFocusCellManager.class\r
+SHA1-Digest: MP6fa7q73TKIJX8Uv7BSUp4qIqE=\r
+\r
+Name: org/eclipse/jface/viewers/OwnerDrawLabelProvider.class\r
+SHA1-Digest: kbzmkO9H7tDizrX1aAszpA/Wj3U=\r
+\r
+Name: org/eclipse/jface/action/SubToolBarManager.class\r
+SHA1-Digest: e15sVyMVIa81F9VydWgI+l7fPqs=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnLayoutData.class\r
+SHA1-Digest: wR/4YK+DCcWF+TqH+EPnfEnI6lI=\r
+\r
+Name: org/eclipse/jface/util/ISafeRunnableRunner.class\r
+SHA1-Digest: C4a7MwkgoLP1QXl3fkLZXbIjYqM=\r
+\r
+Name: org/eclipse/jface/util/TransferDropTargetListener.class\r
+SHA1-Digest: 6FZoSxMHKoPfstPic/1GMQlktc8=\r
+\r
+Name: org/eclipse/jface/fieldassist/FieldDecorationRegistry$Entry.clas\r
+ s\r
+SHA1-Digest: xruXtxS3oTQD7qVLiZjFbWq4Kzc=\r
+\r
+Name: org/eclipse/jface/viewers/DialogCellEditor$1.class\r
+SHA1-Digest: wiz/rzkV2RcqrTYLiYroFTHf5xo=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$5.class\r
+SHA1-Digest: zaDihpPtfDGrwSrbdPwpW4EPw/A=\r
+\r
+Name: org/eclipse/jface/viewers/DoubleClickEvent.class\r
+SHA1-Digest: aYLfn0r6mq8YUfky0Iom6C4mg5E=\r
+\r
+Name: org/eclipse/jface/fieldassist/IContentProposal.class\r
+SHA1-Digest: odEBYrDVhWgkCWdC1ABLvXBBl+o=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$6.class\r
+SHA1-Digest: UsE4FYilN88afT9IkOsK71pY8CY=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractListViewer$1.class\r
+SHA1-Digest: UEQ3dWFrwQJ56Q6bXMzvgGxMKI8=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeyLookupFactory.class\r
+SHA1-Digest: +XqDeX8VBAEJp53sdpg4E9sKmUA=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTableViewer$2.class\r
+SHA1-Digest: Vi0qeWuO/PIJKNe2PuAI7xH9wso=\r
+\r
+Name: org/eclipse/jface/action/IMenuCreator.class\r
+SHA1-Digest: kieVrrSWjOqN28wXYLHKDjzOg90=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer$1.class\r
+SHA1-Digest: f6aAKLuJn0bhKCMDJDTBNOIodrg=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$9.class\r
+SHA1-Digest: ClRZsrmpOwGpCvwAubTFNfpFmkI=\r
+\r
+Name: org/eclipse/jface/menus/AbstractTrimWidget.class\r
+SHA1-Digest: /fEqZ6WMBxIOHILszWWszkZtcmw=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerCell.class\r
+SHA1-Digest: XvPDw5i/CSGXMM3hV4j+yv9Qwpo=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractListViewer.class\r
+SHA1-Digest: VEzvbObT+6LuhCaQ6hBgF852BTc=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$2.class\r
+SHA1-Digest: +7+8Z9qHzaXfVjLdbWC0qrUpyBg=\r
+\r
+Name: org/eclipse/jface/viewers/ComboViewer.class\r
+SHA1-Digest: 9fR3KEGUr2OiW2h7zHb1DBj6Pco=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$5.class\r
+SHA1-Digest: eHLnVLxCl8i6msLemooRW7oJ4xM=\r
+\r
+Name: org/eclipse/jface/wizard/Wizard.class\r
+SHA1-Digest: LazrshzZld3IMGIrmlX/hH/uF40=\r
+\r
+Name: org/eclipse/jface/dialogs/InputDialog.class\r
+SHA1-Digest: Fp0WtGtRPp/9AJRVFAyCO81KR/M=\r
+\r
+Name: org/eclipse/jface/viewers/IInputSelectionProvider.class\r
+SHA1-Digest: MZI5TJ9X1919Jkk8lkgpbEX8dc0=\r
+\r
+Name: org/eclipse/jface/resource/FontRegistry.class\r
+SHA1-Digest: xB+IDR83HvKXJStlHG87e6rR16s=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewerColumn.class\r
+SHA1-Digest: Yc6eUSCm/cmfBsMAYumBRUv9j84=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager.class\r
+SHA1-Digest: cPFGv9W+qowqdUdaA4MmCLfmxU8=\r
+\r
+Name: org/eclipse/jface/action/ICoolBarManager.class\r
+SHA1-Digest: SsIS4rnUWyYkuY2nOs5l5rXr3EU=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewerColumn.class\r
+SHA1-Digest: 2qd3dEc1q04dyEowPw624ZvF97M=\r
+\r
+Name: org/eclipse/jface/dialogs/DialogSettings.class\r
+SHA1-Digest: 79eryhvFMtUJ5lgxebp1v4QHZZ0=\r
+\r
+Name: org/eclipse/jface/resource/AbstractResourceManager$RefCount.clas\r
+ s\r
+SHA1-Digest: LiZU6gMUXyxQDMWWqn2tny7XAcY=\r
+\r
+Name: org/eclipse/jface/preference/ColorSelector.class\r
+SHA1-Digest: vYzAt0XEUxYV4isQaBFxiD0C9V8=\r
+\r
+Name: org/eclipse/jface/action/StatusLineManager.class\r
+SHA1-Digest: yVxa+rR0vV8a2p76wuu5dkubmb8=\r
+\r
+Name: org/eclipse/jface/preference/BooleanFieldEditor$2.class\r
+SHA1-Digest: KGKiEM5qjLC/kMYRuS3RyNHefqQ=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerSorter.class\r
+SHA1-Digest: 8h50w1p2p1PFPmPdMc3iosjcJ0Y=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressIndicator.class\r
+SHA1-Digest: rGsMOj7x4nT9+UE/z04O4hKA/8k=\r
+\r
+Name: org/eclipse/jface/viewers/TreeColumnViewerLabelProvider$1.class\r
+SHA1-Digest: LayPvFXQFPFIOB72JEhVJ5/J/Oo=\r
+\r
+Name: org/eclipse/jface/window/ApplicationWindow$ApplicationWindowLayo\r
+ ut.class\r
+SHA1-Digest: DYR+N03EEHLYLAgKosledVrh1vI=\r
+\r
+Name: org/eclipse/jface/action/CoolBarManager.class\r
+SHA1-Digest: QEzBDIxP6BXcPEKKSr1Iy/gxtNo=\r
+\r
+Name: org/eclipse/jface/fieldassist/FieldDecorationRegistry.class\r
+SHA1-Digest: Gqr8/Y9YF7qdXEnEypAjd18XsLI=\r
+\r
+Name: org/eclipse/jface/viewers/IOpenListener.class\r
+SHA1-Digest: LkdnUxquMcFT9cE12C0Rr39PLwQ=\r
+\r
+Name: org/eclipse/jface/preference/FieldEditor$1.class\r
+SHA1-Digest: ncOEYR67CeB+YDPHOVDFLEGaDPc=\r
+\r
+Name: org/eclipse/jface/preference/IPreferencePage.class\r
+SHA1-Digest: rNfcBt1JT6gR3RHOc82WxhjhQNA=\r
+\r
+Name: org/eclipse/jface/preference/BooleanFieldEditor.class\r
+SHA1-Digest: tI75Elc7BcBxkahHXb0fmkfVqvw=\r
+\r
+Name: org/eclipse/jface/viewers/ITableLabelProvider.class\r
+SHA1-Digest: k/fkWmJL0Z/pbnEjoHXtK9xxSZU=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnableDialog$1.class\r
+SHA1-Digest: gRS/fSnSA8k1VgfqJ6p3s1u2kWY=\r
+\r
+Name: org/eclipse/jface/layout/AbstractColumnLayout.class\r
+SHA1-Digest: f/ByQWIeY0o9i77B9xmCP9XdG/o=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$ColorAndFontCollector\r
+ WithProviders.class\r
+SHA1-Digest: 0lcbRO1FqTxlPOYp4r5CQKwzubU=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/IToolBarManager2.c\r
+ lass\r
+SHA1-Digest: kXWlyfzHabQlbfj+HnZ/5hfXqK8=\r
+\r
+Name: org/eclipse/jface/preference/ColorFieldEditor$1.class\r
+SHA1-Digest: /dlrN7rz7cGYiKw+ymGS0JAMg/A=\r
+\r
+Name: org/eclipse/jface/action/SubCoolBarManager.class\r
+SHA1-Digest: 3S9WsZ1ty0oENw1hkGSi4C6M5qk=\r
+\r
+Name: plugin.properties\r
+SHA1-Digest: tWNH3hrisFHSrwmu2D5aiafbnjw=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$1.class\r
+SHA1-Digest: TFpLnlsSJIZhyyqHWFxV0icGVj8=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/NativeKeyFormatter.pr\r
+ operties\r
+SHA1-Digest: Hi7YkOmGtbLmnfhvmhwGOJy0mLQ=\r
+\r
+Name: org/eclipse/jface/window/ToolTip.class\r
+SHA1-Digest: TaTQGa/W1JCTtNn9/aHlYdMW++4=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressMonitorDialog$ProgressMonitor.\r
+ class\r
+SHA1-Digest: ADo2Enwrh9VAIYhx0eGEpP+N+/s=\r
+\r
+Name: org/eclipse/jface/bindings/IBindingManagerListener.class\r
+SHA1-Digest: dc6u6FbL/18eNjT71elM3xdOj9Q=\r
+\r
+Name: org/eclipse/jface/preference/ComboFieldEditor$1.class\r
+SHA1-Digest: qFgA2vKOcH6inGwrZhEhJnnY5XE=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewer$1.class\r
+SHA1-Digest: iz5SYN6ITjkuL0Qjthf37rF5Aho=\r
+\r
+Name: org/eclipse/jface/dialogs/ErrorDialog$1.class\r
+SHA1-Digest: 6D/zwylihOWzoZEZV+kOV27KUiE=\r
+\r
+Name: org/eclipse/jface/viewers/ICheckStateListener.class\r
+SHA1-Digest: OV9Nl3s2BO9SJ3FsqDJpAbTuhg8=\r
+\r
+Name: org/eclipse/jface/dialogs/TrayDialog.class\r
+SHA1-Digest: MOh+A0wmNDwUD+mJwSbG8JRS/QE=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$4.class\r
+SHA1-Digest: ip8lX75vIV+lmBMBALS6SObisow=\r
+\r
+Name: org/eclipse/jface/viewers/IStructuredSelection.class\r
+SHA1-Digest: 2tK+qLoIYEVAHeZsAknZWaP81Cg=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$3.class\r
+SHA1-Digest: 9gSxvKNaCXDrw8efwKEHX3pQvos=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$5.class\r
+SHA1-Digest: 0jLFM4I3R9GAX5crkraZ82ZQZmE=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer.class\r
+SHA1-Digest: GN/nRmwm7nY2okxXifzNLCSljTQ=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$11.class\r
+SHA1-Digest: CGcS4ZFi37GyVUoAJiQTSJ8lMGk=\r
+\r
+Name: org/eclipse/jface/action/AbstractGroupMarker.class\r
+SHA1-Digest: XQnug1YGBO0kQJO6MQRVDZpdFX0=\r
+\r
+Name: org/eclipse/jface/menus/IMenuStateIds.class\r
+SHA1-Digest: yOEupcQIPltveAoeYJ9O0mgXIDE=\r
+\r
+Name: org/eclipse/jface/preference/ColorSelector$2.class\r
+SHA1-Digest: bsH9bjZUChnZnzpn9c+PfH9+AwM=\r
+\r
+Name: org/eclipse/jface/viewers/ILazyContentProvider.class\r
+SHA1-Digest: lspzBOZYqKBFQe1uN2QkD0KwZXM=\r
+\r
+Name: org/eclipse/jface/action/StatusLine$3.class\r
+SHA1-Digest: je2hwk5H86vP06oYxuce5TqXpuw=\r
+\r
+Name: org/eclipse/jface/action/MenuManager.class\r
+SHA1-Digest: w/Bn0jkvfT25xHfhqwtFS4BQ0ug=\r
+\r
+Name: org/eclipse/jface/wizard/IWizardNode.class\r
+SHA1-Digest: X/fmztQYyvWDgMV0KsNv+ih23WQ=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/required_field_cue.gif\r
+SHA1-Digest: kFgJwSfgjrz6DeAnyTG8VwiYBro=\r
+\r
+Name: org/eclipse/jface/viewers/IDelayedLabelDecorator.class\r
+SHA1-Digest: v7JhOkUsV2uR7TQyAUSJ8ll2lII=\r
+\r
+Name: org/eclipse/jface/viewers/SWTFocusCellManager$2.class\r
+SHA1-Digest: mybS+mcAjjKwYStuiXB8Xw9gALc=\r
+\r
+Name: org/eclipse/jface/viewers/ILabelDecorator.class\r
+SHA1-Digest: fIvm/v7b86gWCRMVsBpm1/EpXGQ=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$6.class\r
+SHA1-Digest: JxolexrtUJ8/4r25uSSal7mn1Ho=\r
+\r
+Name: org/eclipse/jface/viewers/OwnerDrawLabelProvider$3.class\r
+SHA1-Digest: EuR5vMrQ+HR/wifGMLjmayZxhWs=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditorDeactivationEvent.cl\r
+ ass\r
+SHA1-Digest: 4xZJLS/ZALs2kxTN1rQt1VurDSY=\r
+\r
+Name: org/eclipse/jface/dialogs/ControlAnimator.class\r
+SHA1-Digest: cpu/ClpckOMkv+scejHIQGqJU5A=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_sunos.properties\r
+SHA1-Digest: HrsLzqtlY256rEtTO16bpSdax4c=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/contassist_ovr.gif\r
+SHA1-Digest: 57qSW9uQioUmE5UdvgvmFM8F+b0=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor$LayoutData.class\r
+SHA1-Digest: I6whnxT8y74ZaGRq+gHGe5bJ3j4=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_linux_gtk.properties\r
+SHA1-Digest: Oo2DBUcSZfvWdidnAFd6RKVFHrw=\r
+\r
+Name: org/eclipse/jface/dialogs/IconAndMessageDialog$1.class\r
+SHA1-Digest: +aiCa3azGCdIliM7kCO26GoeN50=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnable.class\r
+SHA1-Digest: 3YyIwI9dR006n4vFQ6zPzQJpv3Y=\r
+\r
+Name: META-INF/eclipse.inf\r
+SHA1-Digest: SAqY+5ITAL0mkdYeijlSRhyIaZk=\r
+\r
+Name: org/eclipse/jface/layout/AbstractColumnLayout$1.class\r
+SHA1-Digest: uftACdFIsdtuxsTknO5MIeCVIFw=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDragAdapter$2.class\r
+SHA1-Digest: A7/v7QBKBYyFeNiQO0gtzPMz6FQ=\r
+\r
+Name: org/eclipse/jface/dialogs/PageChangedEvent.class\r
+SHA1-Digest: WIEUtq749x6EYR6qUTFO6s9BXyE=\r
+\r
+Name: org/eclipse/jface/util/Policy$2.class\r
+SHA1-Digest: el4nezY2lboq2UA4cEQVvp+DFyk=\r
+\r
+Name: org/eclipse/jface/preference/ListEditor$1.class\r
+SHA1-Digest: hOS4vTAwITe63OK69gRtrgdGvfY=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$PersistBoundsAction.class\r
+SHA1-Digest: q3r/UgLWuZYH027UVQ1ijbRuywg=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/DeferredContentProvider$Table\r
+ ViewerAdapter.class\r
+SHA1-Digest: v4PTzyzSS/bNn2x31k8dS1VH4MM=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceStore.class\r
+SHA1-Digest: iHIiOHR1kcn10bvJJqhWqcoDFUA=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewerFocusCellManager.class\r
+SHA1-Digest: Rg33tiTXenal1c2q7KsWbIg4Vxg=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor$1.class\r
+SHA1-Digest: D95MQhuRclaek/BfU9j6G+4JIzU=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditor$1.class\r
+SHA1-Digest: lvHU+SbHKPlAYsrhA514+l8T8sI=\r
+\r
+Name: org/eclipse/jface/dialogs/images/message_info.gif\r
+SHA1-Digest: 4+5SUEYuS/dj4a895j9IOruyRMY=\r
+\r
+Name: org/eclipse/jface/dialogs/ControlEnableState$ItemState.class\r
+SHA1-Digest: MNcQJ60aixDqZgIyFFeOGA1fudU=\r
+\r
+Name: org/eclipse/jface/resource/FileImageDescriptor.class\r
+SHA1-Digest: mNk+JLnHPWQbdAFaCa+OZ3kX6kw=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$2.class\r
+SHA1-Digest: 0bHaCc7F8zeV/hDl/f1nwNvUL4g=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$2.class\r
+SHA1-Digest: RL67nF6sxbH1BT7xkNMaZ63pdas=\r
+\r
+Name: org/eclipse/jface/viewers/CheckboxTableViewer$1.class\r
+SHA1-Digest: sk0LD9EDw+X2tvFOgGxPlClIJqU=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$8.class\r
+SHA1-Digest: t0pjQVNqMRqxwJdjC9/BvOZSD5g=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$UpdateItemSafeRunnabl\r
+ e.class\r
+SHA1-Digest: uMBa4hk48TSMlVv6ufekAsZZwu8=\r
+\r
+Name: org/eclipse/jface/viewers/CheckboxCellEditor.class\r
+SHA1-Digest: fxy0K42NYAEraNzB0zmJ65Xc2DM=\r
+\r
+Name: org/eclipse/jface/viewers/TreeSelection$InitializeData.class\r
+SHA1-Digest: ZRPxfeO9CZpleerkNJnnTG9TJ14=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText.class\r
+SHA1-Digest: MTtYd60chLP3u/JyQhN2PIiQbBw=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$4.class\r
+SHA1-Digest: al2vN1FYLYpSMHMCAxokIPATtOk=\r
+\r
+Name: org/eclipse/jface/fieldassist/IContentProposalListener2.class\r
+SHA1-Digest: I5IQUeHrSrr0RNUuN2LA5fD/sfw=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$TooltipHideListener.class\r
+SHA1-Digest: AWowHswa0UE/EOOAhC6SYztBPJo=\r
+\r
+Name: org/eclipse/jface/viewers/IBaseLabelProvider.class\r
+SHA1-Digest: CcLat4gZgEPhu/B2W4WMQlnlC5I=\r
+\r
+Name: org/eclipse/jface/util/OpenStrategy.class\r
+SHA1-Digest: T0mxgRh4jSaKco8V4MCNtuVqfV4=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$6.class\r
+SHA1-Digest: 7PcTTaLAGPxbyetOasLBK/b6Wjo=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$2.class\r
+SHA1-Digest: 8uA3wtws8z7jD43kdW+r8vnw34A=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnable$1.class\r
+SHA1-Digest: 2umWrfjksQ0MS70+JG2x2RzbLAY=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$11.class\r
+SHA1-Digest: O02NzqZ0AbJKtR+2NCYptxcgSgM=\r
+\r
+Name: org/eclipse/jface/resource/LocalResourceManager$1.class\r
+SHA1-Digest: jpcFMI4RCk5T966O5yLGI/lP7rQ=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/IntHashMap.class\r
+SHA1-Digest: o6kUSud7+tn1YgigbdcAju/O8ug=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor$1.class\r
+SHA1-Digest: Mr9pauGbra0I693a/ptImT7QNTY=\r
+\r
+Name: org/eclipse/jface/dialogs/images/help.gif\r
+SHA1-Digest: 0yYw/cn9njN3clSzHx43MdH0L7k=\r
+\r
+Name: org/eclipse/jface/action/StatusLine.class\r
+SHA1-Digest: kzn2ZEdHZIUeJYd95zXPGZ9D07Q=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$1.class\r
+SHA1-Digest: VWwFVyqkzncebypKrPsWlYQ5S3o=\r
+\r
+Name: org/eclipse/jface/contexts/IContextIds.class\r
+SHA1-Digest: DDvZqTHQrgoS8gP7axmOLICx2S4=\r
+\r
+Name: org/eclipse/jface/viewers/TreeNodeContentProvider.class\r
+SHA1-Digest: jDb6XqU8SpxJb1jCjFzcCxwEHMA=\r
+\r
+Name: org/eclipse/jface/action/ContributionItem.class\r
+SHA1-Digest: cYRyTsQtAPnjToLzl6ZjMy/hfQ0=\r
+\r
+Name: org/eclipse/jface/action/Action$1.class\r
+SHA1-Digest: qD4GQNN/CLnK+K7WmoglJUHzTDQ=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog.class\r
+SHA1-Digest: 2D9l2J3eK+35A6dz+z6WxmcgVgY=\r
+\r
+Name: org/eclipse/jface/layout/LayoutConstants.class\r
+SHA1-Digest: yX2rsxqXNPU5sCfYsVUR9QxzTuA=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewerFocusCellManager$1.class\r
+SHA1-Digest: X81rPyD+S5fd7kuwoVVMrFpa2xY=\r
+\r
+Name: org/eclipse/jface/viewers/TableColumnViewerLabelProvider.class\r
+SHA1-Digest: 9HqbbhBAsNP2f5i3+7CyC0ALW8M=\r
+\r
+Name: org/eclipse/jface/viewers/IColorDecorator.class\r
+SHA1-Digest: BWmbSGHnKYvj2Dh/DteJ4bjUCNY=\r
+\r
+Name: org/eclipse/jface/viewers/ILightweightLabelDecorator.class\r
+SHA1-Digest: kbo1qzUrH3zJV+I4GJ4WYhJW37A=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$Collecto\r
+ r.class\r
+SHA1-Digest: D5ShG7fV9bGwCiCOH/HqHahMIMc=\r
+\r
+Name: org/eclipse/jface/viewers/DialogCellEditor$2.class\r
+SHA1-Digest: snWPTjmEFYjOXdgJykp5WD3nBgo=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$6.class\r
+SHA1-Digest: vg6+OjDls3q+Cj1LIAGgBeBf1JA=\r
+\r
+Name: org/eclipse/jface/resource/ImageDataImageDescriptor.class\r
+SHA1-Digest: DtD9W5VBTkzJWIvPamlI2whyxxM=\r
+\r
+Name: org/eclipse/jface/dialogs/TrayDialog$1.class\r
+SHA1-Digest: 3UC9Kt03AvXFuUEjAxkUK3AV7S0=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDragAdapter.class\r
+SHA1-Digest: HEm5T4B0tN97W71IQJHkuxkl/ME=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/ToolBarContributio\r
+ nItem2.class\r
+SHA1-Digest: 7NFzkt09eqSgxjsqTOZSN8/kd/0=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewer$1.class\r
+SHA1-Digest: LC6wzgZN/wfh3UraLeZFFE80SPs=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTableViewer$3.class\r
+SHA1-Digest: b6kXtpeNnCl0tx2H1/CSghMyh5Y=\r
+\r
+Name: org/eclipse/jface/util/ListenerList.class\r
+SHA1-Digest: JRJeqTnvP2boO9uai9RqVB94gOI=\r
+\r
+Name: org/eclipse/jface/resource/JFaceResources.class\r
+SHA1-Digest: aBrxtrcskDguKeazeyN6LeXTjiM=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer$2.class\r
+SHA1-Digest: JdoC3DlW5WKUwKNSeqyOaW6Eeno=\r
+\r
+Name: org/eclipse/jface/util/LocalSelectionTransfer.class\r
+SHA1-Digest: xzoMAPMzcDGE7EikpP41APaTenY=\r
+\r
+Name: org/eclipse/jface/preference/StringFieldEditor.class\r
+SHA1-Digest: hwIsBB9NjnOJkV9XuJ1DIUtwLm4=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$1.class\r
+SHA1-Digest: 1WRJ6ds7CVEcEbVII/YjjyBN1Qo=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$3.class\r
+SHA1-Digest: T5UsNG6YLa+//AKyPrXFLUG4/Ho=\r
+\r
+Name: org/eclipse/jface/viewers/WrappedViewerLabelProvider.class\r
+SHA1-Digest: cdGH5ez8CNa4MzvRw3PCrUt+hC4=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$6.class\r
+SHA1-Digest: Njh00JUQgN9MHQuTdI0SrWLBz5I=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTableViewer.class\r
+SHA1-Digest: EG7QRmsCmDmAon+tAwth25np7qo=\r
+\r
+Name: org/eclipse/jface/operation/ModalContext$1.class\r
+SHA1-Digest: zrfgG4topJmDQxmT/TGLPpaV/SI=\r
+\r
+Name: org/eclipse/jface/action/ControlContribution.class\r
+SHA1-Digest: 9IIz8uXykWOv7BrbG3FKwax8qVs=\r
+\r
+Name: org/eclipse/jface/bindings/Scheme.class\r
+SHA1-Digest: n35jC969b3ntgi+35SH+MVZO6vY=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$1.class\r
+SHA1-Digest: A0X4U+JozQ5bpnMZ5dnxKdX42jU=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceStore$1.class\r
+SHA1-Digest: 01ON1xif/rXBcumyCBomc8WUaEY=\r
+\r
+Name: org/eclipse/jface/resource/LocalResourceManager.class\r
+SHA1-Digest: bCV1WrTJ9PvyU+0N2MZoxgvTcrM=\r
+\r
+Name: org/eclipse/jface/resource/DeviceResourceDescriptor.class\r
+SHA1-Digest: nlVoMy25VQqSg3kq6dYnXH9ySUg=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$1.class\r
+SHA1-Digest: /AzVTwzR5tozuEwYPHJlzhhtIu0=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnableDialog$2.class\r
+SHA1-Digest: fvMnOCEMp1oh6O+jh025+Wn+P2A=\r
+\r
+Name: org/eclipse/jface/dialogs/IMessageProvider.class\r
+SHA1-Digest: jtqJpxCW3PevB3d0w8G1duZSQZc=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/warn_ovr.gif\r
+SHA1-Digest: QinaeVEnreHLqsEwedtOOmgyv9Q=\r
+\r
+Name: org/eclipse/jface/bindings/BindingManagerEvent.class\r
+SHA1-Digest: lMe5xyhkfCpuxXj88lUc0KtswF8=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceConverter.class\r
+SHA1-Digest: HMRPHyQ6nY8s5WlcVEnXessWmwM=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor.class\r
+SHA1-Digest: c/mTOmIPdA06EK03KIWbmFpE0jM=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$TraversalFilter.\r
+ class\r
+SHA1-Digest: u3c557X0JoNUTbacV6WwXeY8RMA=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$1.class\r
+SHA1-Digest: ZeRLuay83LgukkB/mePMfrXGfi8=\r
+\r
+Name: org/eclipse/jface/preference/IPreferencePageContainer.class\r
+SHA1-Digest: /KfDDJ25D9yh1qmw/PfvLCS+uqg=\r
+\r
+Name: org/eclipse/jface/viewers/CustomHashtable.class\r
+SHA1-Digest: oqESqdnwGRChPAgfopSQlu+EZqQ=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$2.class\r
+SHA1-Digest: 1zi9buG+O1MFNklL5LFFvcZzPgo=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewer$2.class\r
+SHA1-Digest: bLlDGWKfdwjMvgp3kiGGg53/gNg=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$5.class\r
+SHA1-Digest: 1/CO04abNoxxMr9jtI0fnton1AM=\r
+\r
+Name: org/eclipse/jface/action/ToolBarManager$1.class\r
+SHA1-Digest: aNYdmr+bnL9pl4sapwQXHlAYC5o=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$4.class\r
+SHA1-Digest: 8Hal+63xM4XsoHyOppkphaGyGDw=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer.class\r
+SHA1-Digest: wKz0Wo41oX43cnST0s4md/jBVVU=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$12.class\r
+SHA1-Digest: kh2CM6jdnIUNfyG+282GJzeIuTU=\r
+\r
+Name: org/eclipse/jface/viewers/CheckStateChangedEvent.class\r
+SHA1-Digest: Ksus+UUE8tLT+SYKEV39ZJmLu0c=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$ICallback.class\r
+SHA1-Digest: oJZaOdjbc14XnVnpSuFcGiaiHXE=\r
+\r
+Name: org/eclipse/jface/preference/ColorSelector$3.class\r
+SHA1-Digest: kJN187i+2VU+gpx09+nrkbzSWIA=\r
+\r
+Name: org/eclipse/jface/preference/IPreferenceStore.class\r
+SHA1-Digest: etOMu/+F26Hd4DQ4KkQWoXnSS0w=\r
+\r
+Name: org/eclipse/jface/commands/ActionHandler$1.class\r
+SHA1-Digest: kFsiZrJvnHz+KzTZcfXupcur3Cg=\r
+\r
+Name: org/eclipse/jface/action/StatusLine$4.class\r
+SHA1-Digest: njb6+cRh3pIXNlrC48vDj1TO7aM=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$1.class\r
+SHA1-Digest: JqelNAvQD3QOoG4eQ5lFCAJhbuQ=\r
+\r
+Name: org/eclipse/jface/preference/FileFieldEditor.class\r
+SHA1-Digest: 7ysHkCAetehqJ1fJ37FT/sS652c=\r
+\r
+Name: org/eclipse/jface/resource/JFaceColors.class\r
+SHA1-Digest: daJm7rWnOWWo+gNUVYTq7evSuhg=\r
+\r
+Name: org/eclipse/jface/viewers/ContentViewer$1.class\r
+SHA1-Digest: Tb2gpdo/UFpbU6tYXXetzGKSZkk=\r
+\r
+Name: org/eclipse/jface/action/ToolBarContributionItem$1.class\r
+SHA1-Digest: 4agIFkSYnP6ZG8eDb9AKy6TKwY8=\r
+\r
+Name: org/eclipse/jface/viewers/SWTFocusCellManager$3.class\r
+SHA1-Digest: NivKVpSWAl0azOv0HnW05x+iCb8=\r
+\r
+Name: org/eclipse/jface/viewers/ColorCellEditor.class\r
+SHA1-Digest: 1eYDNxHZ2akrgbxKPqk5KB6LgLk=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$7.class\r
+SHA1-Digest: +yf4gB57nfKVmo/u4tQNwptAa0w=\r
+\r
+Name: org/eclipse/jface/viewers/OpenEvent.class\r
+SHA1-Digest: 1XwKuWw+QBj+I3EwXmTt31jrado=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerDropAdapter.class\r
+SHA1-Digest: mlvfm7I8PgwWpTh/lDohNk+ax84=\r
+\r
+Name: org/eclipse/jface/resource/DeviceResourceException.class\r
+SHA1-Digest: uxAb9MGXDAiCL5RGXF2ZjhpimDU=\r
+\r
+Name: org/eclipse/jface/wizard/ProgressMonitorPart$1.class\r
+SHA1-Digest: NiC6gjQ5rX5449Yb8M5bnPGdj0s=\r
+\r
+Name: org/eclipse/jface/layout/TableColumnLayout.class\r
+SHA1-Digest: XMzM+DeJTdp22sAn/x/Hyt2VJFw=\r
+\r
+Name: org/eclipse/jface/util/TransferDragSourceListener.class\r
+SHA1-Digest: Lo+s2yClbDS/EOSaLAh0t8LknRU=\r
+\r
+Name: org/eclipse/jface/resource/ImageRegistry$Entry.class\r
+SHA1-Digest: QLTfXd/FfRauy2UDzd7vc4j4ODg=\r
+\r
+Name: org/eclipse/jface/dialogs/IconAndMessageDialog$2.class\r
+SHA1-Digest: QmnFoW7LvPgnpOzwddsZ7pXdEFM=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$ContentProp\r
+ osalPopup$InfoPopupDialog.class\r
+SHA1-Digest: 46FdyvKh6mxXeXggcOd+79FMORU=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$CommandCallback.c\r
+ lass\r
+SHA1-Digest: y1BMAs9flPxZTeHzWuOg7mt4uBA=\r
+\r
+Name: org/eclipse/jface/viewers/ILabelProviderListener.class\r
+SHA1-Digest: 7lvrA7c4Tu134DZ/RkkAq0+FkWI=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor$DefaultPreviewer.cl\r
+ ass\r
+SHA1-Digest: SGcmnjbSBLnFLzCe/+1jq9NxWGg=\r
+\r
+Name: org/eclipse/jface/menus/IWidget.class\r
+SHA1-Digest: aUOhQ5T6SKmfDMgKY+KkmtY4fHs=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDragAdapter$3.class\r
+SHA1-Digest: X9F2HmEVsPY7vDHwx/qZItNU4bc=\r
+\r
+Name: org/eclipse/jface/viewers/ColorCellEditor$ColorCellLayout.class\r
+SHA1-Digest: YBMAPPB9vChppwxBXfJwh39UWmk=\r
+\r
+Name: org/eclipse/jface/action/IMenuListener2.class\r
+SHA1-Digest: R/MdrqiSzQ37sbpZiCk1phECly8=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerFilter.class\r
+SHA1-Digest: t+nuhqWFFJGZxb6aSB9AjA/C3Q8=\r
+\r
+Name: org/eclipse/jface/viewers/CellNavigationStrategy.class\r
+SHA1-Digest: t7zgt9DFW69vZjfZyKkM54oaVds=\r
+\r
+Name: org/eclipse/jface/preference/ListEditor$2.class\r
+SHA1-Digest: uqBpVSQXSFff7+XV8whd07w0Am0=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor$2.class\r
+SHA1-Digest: 4n9mTboTrvaf5IqBpiAS/t77Ntc=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$1.class\r
+SHA1-Digest: Xr4jmabxBEobgAbGzhobygnHHUg=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditor$2.class\r
+SHA1-Digest: u5RtLUKtMTAOPY5cgnwK2WTxbYE=\r
+\r
+Name: org/eclipse/jface/preference/PathEditor.class\r
+SHA1-Digest: +S4cC3zj6w1IBwlZLsUv7oLm6dg=\r
+\r
+Name: org/eclipse/jface/viewers/IContentProvider.class\r
+SHA1-Digest: yO0/5uvnhqoVnY0aoU252XGQMqs=\r
+\r
+Name: org/eclipse/jface/preference/StringFieldEditor$1.class\r
+SHA1-Digest: +h7PqEUBPHe9dVs6Glb6p+1SW4s=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/ConcurrentTableUpdator$Range.\r
+ class\r
+SHA1-Digest: tSTzSK2AFHyShaKYKt55KlkEH8M=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$3.class\r
+SHA1-Digest: 6pGLDoOZ2l8SN7Emn70NXcesheA=\r
+\r
+Name: org/eclipse/jface/viewers/FocusCellOwnerDrawHighlighter$1.class\r
+SHA1-Digest: di6v+upJFKG9ucL8nqeAZoMDUak=\r
+\r
+Name: org/eclipse/jface/viewers/AcceptAllFilter.class\r
+SHA1-Digest: mcK5dWzxY79h0gqQQwmfRj2sJi0=\r
+\r
+Name: org/eclipse/jface/preference/BooleanPropertyAction$1.class\r
+SHA1-Digest: /H3QjxH6oIwXZ3MpjLK/xDAtzQ4=\r
+\r
+Name: org/eclipse/jface/resource/ImageRegistry$OriginalImageDescriptor\r
+ .class\r
+SHA1-Digest: kndwQlmwG27f7lud7fivGfkQtMo=\r
+\r
+Name: org/eclipse/jface/action/IMenuManager.class\r
+SHA1-Digest: VxAA0V4YTQA3UPbS5R6qAtIr00s=\r
+\r
+Name: org/eclipse/jface/fieldassist/SimpleContentProposalProvider.clas\r
+ s\r
+SHA1-Digest: Bi7F4UWh+bvk/arpKt4yxVD9izU=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$9.class\r
+SHA1-Digest: 2P94ZJ12gAxYXNlALN095JT5v5U=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerColumn$1.class\r
+SHA1-Digest: C1JLtvjyhOVTgNwEC3Cb5UmVGLI=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerComparator$1.class\r
+SHA1-Digest: rHUhe73OefEuXW6/ogyqxHRICOQ=\r
+\r
+Name: org/eclipse/jface/viewers/FocusCellOwnerDrawHighlighter.class\r
+SHA1-Digest: AHrjof87KQ32jSl9vIl7GXYzkso=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$FieldDecorationData\r
+ .class\r
+SHA1-Digest: gvli4Yn6/gdUOr9bkCdhROLyhPE=\r
+\r
+Name: org/eclipse/jface/dialogs/IPageChangingListener.class\r
+SHA1-Digest: 9l898ZRjO5QC9/xRt+uJEPTnD7w=\r
+\r
+Name: org/eclipse/jface/dialogs/StatusDialog.class\r
+SHA1-Digest: 1XZHKZ+Dxjqy4M8Sz7QEBD8nw8s=\r
+\r
+Name: org/eclipse/jface/preference/FieldEditorPreferencePage.class\r
+SHA1-Digest: RXV/bhU+SPhdEalBQRjfzQiIyEw=\r
+\r
+Name: org/eclipse/jface/viewers/IFilter.class\r
+SHA1-Digest: xYCE0vSQQClAf9RIryXODSxmwlA=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$1.class\r
+SHA1-Digest: imMe4RZwy4njTnzTVBTC55+O8pc=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$5.class\r
+SHA1-Digest: 7g1of9yrG55ajsiiXQCMi8HYKtg=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem.class\r
+SHA1-Digest: ZTy4o3pmBswP5IHjC53tkPfFIU0=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$ContentProp\r
+ osalPopup$TargetControlListener.class\r
+SHA1-Digest: fbI6CQYQYD5vXLoMUPiYCYWe1xY=\r
+\r
+Name: org/eclipse/jface/dialogs/images/popup_menu.gif\r
+SHA1-Digest: 22R9N09vLbsIdT7NXQIXEHLO9Lk=\r
+\r
+Name: org/eclipse/jface/action/IContributionManager.class\r
+SHA1-Digest: v105cSPG9w6fKeCpKB1384IAEoc=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredSelection.class\r
+SHA1-Digest: sBcdpWLl1DYHvRZTuUaGe252vLo=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_windows98.properties\r
+SHA1-Digest: 2yI7jAy587B/b3gaeKWlR0yberc=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$7.class\r
+SHA1-Digest: p+VppDoo7MPNXkgks/RjC0c+mA0=\r
+\r
+Name: org/eclipse/jface/bindings/BindingManager.class\r
+SHA1-Digest: HANyKX7b2pD/ZLFiNdmIUY7EcAM=\r
+\r
+Name: org/eclipse/jface/resource/ColorDescriptor.class\r
+SHA1-Digest: r5iwKsHsxYahtFRBj4NhOBHT4yo=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$3.class\r
+SHA1-Digest: B/mhds/Dw7TMMDiCXBuXgqEeTzA=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnable$2.class\r
+SHA1-Digest: wZfQcCu8Vt5cb6ZEbofrjvA3FdE=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$12.class\r
+SHA1-Digest: rvbB9G8QjqV2s4wk84T1p6hGmM0=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor$2.class\r
+SHA1-Digest: RBTKXY1Ob8f8082JrPE5+cD4qVU=\r
+\r
+Name: org/eclipse/jface/operation/IThreadListener.class\r
+SHA1-Digest: EJR/R98/auL8qd4/lZMVvd9SNg0=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$2.class\r
+SHA1-Digest: 9ubZ1tB2EbVm2bM61gESQ3CagnQ=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewerRow.class\r
+SHA1-Digest: DPZa2Mk4rUkY3Ge95wDQoBRovX8=\r
+\r
+Name: org/eclipse/jface/viewers/ComboBoxCellEditor$1.class\r
+SHA1-Digest: 8x7+fw7HQSwPwCGavBa1/BLX58A=\r
+\r
+Name: org/eclipse/jface/action/IAction.class\r
+SHA1-Digest: L5aIwRXcSCe6jRjtCxBqbun8lOk=\r
+\r
+Name: org/eclipse/jface/action/ContributionManager.class\r
+SHA1-Digest: UjLZPx2pIBQdiezA9eH9iQGWtlw=\r
+\r
+Name: org/eclipse/jface/viewers/ISelectionProvider.class\r
+SHA1-Digest: 2Xv8rrqYG9g9QQM0YkBJzs57iK4=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/CoolBarManager2.cl\r
+ ass\r
+SHA1-Digest: idvQDUsvxH0pNAOitAiCBTNpQAw=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor.class\r
+SHA1-Digest: eCKjKtb0DiHyo/whTjJypi6LcB0=\r
+\r
+Name: org/eclipse/jface/viewers/TreeColumnViewerLabelProvider.class\r
+SHA1-Digest: oDKx3wSOOD/S+AHKfPVJzs/f0Qk=\r
+\r
+Name: org/eclipse/jface/dialogs/IDialogSettings.class\r
+SHA1-Digest: U72D0RrHbbyIGs0bfpr9+jIulqw=\r
+\r
+Name: org/eclipse/jface/viewers/ILazyTreePathContentProvider.class\r
+SHA1-Digest: c+Ba//yLYhEJ/vJfueHLJe0HYPw=\r
+\r
+Name: org/eclipse/jface/viewers/IViewerLabelProvider.class\r
+SHA1-Digest: 9YjMuwc6VGf1I5QZk6nf32f7MrI=\r
+\r
+Name: org/eclipse/jface/resource/ArrayFontDescriptor.class\r
+SHA1-Digest: 0lHb3Pue2/Xeef5+H4MQXPfinbw=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/FastProgressReporter.class\r
+SHA1-Digest: n9yEl7X1i6XW66E/5aiqRgKUCK4=\r
+\r
+Name: org/eclipse/jface/dialogs/DialogSettings$XMLWriter.class\r
+SHA1-Digest: vTc8cA23Ik0PTcK59zdr2npDJ4c=\r
+\r
+Name: org/eclipse/jface/viewers/DialogCellEditor$3.class\r
+SHA1-Digest: Uc1DM1H3WpMM/Jmt9WVVQXV9inQ=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$7.class\r
+SHA1-Digest: k+CzVBX+X4dBv1Rwi90RKsFvSRk=\r
+\r
+Name: org/eclipse/jface/bindings/keys/SWTKeySupport.class\r
+SHA1-Digest: WxRlv19w5l+xtjFRy2WlGJFo0Dc=\r
+\r
+Name: org/eclipse/jface/dialogs/TrayDialog$2.class\r
+SHA1-Digest: zA+C3XqZnE0zQtB8lg9pBsdb2/U=\r
+\r
+Name: org/eclipse/jface/viewers/IDoubleClickListener.class\r
+SHA1-Digest: QJtV0uJ0RWPasHqjyTNwnx08Fjg=\r
+\r
+Name: org/eclipse/jface/resource/DataFormatException.class\r
+SHA1-Digest: 7CkkwzusO6V1BkM6lxgpMZJyUg0=\r
+\r
+Name: org/eclipse/jface/util/Policy.class\r
+SHA1-Digest: dcIT60UbzgaEyJv+nyepyQ+w8pY=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer$3.class\r
+SHA1-Digest: BWsEft/KoCOeMQJnP4gesv0VlCg=\r
+\r
+Name: org/eclipse/jface/preference/ScaleFieldEditor$1.class\r
+SHA1-Digest: 5DLE3inzefSlmXvCRKO4l0H1eUA=\r
+\r
+Name: org/eclipse/jface/fieldassist/TextControlCreator.class\r
+SHA1-Digest: OS0lxF4rUfjxim3NVeguh1GWtqk=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_aix.properties\r
+SHA1-Digest: mMrn0dBjkyVAqngF/nJgdZKuiCE=\r
+\r
+Name: org/eclipse/jface/resource/ImageRegistry$1.class\r
+SHA1-Digest: GPBar2cqFQ6LKsiaA3m9V3yVrO4=\r
+\r
+Name: org/eclipse/jface/bindings/keys/SWTKeyLookup.class\r
+SHA1-Digest: BpnGjNXP0fF/+JxzEe42zZHOfhg=\r
+\r
+Name: org/eclipse/jface/action/MenuManager$1.class\r
+SHA1-Digest: O6Y5HoPqUa1El/T25ZkF3IbNqNw=\r
+\r
+Name: org/eclipse/jface/dialogs/images/popup_menu_disabled.gif\r
+SHA1-Digest: srJeylJlVeUWnJbChgrmd/099NU=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$2.class\r
+SHA1-Digest: FapsCo5Kpq4h46GwzXnbwekOekM=\r
+\r
+Name: org/eclipse/jface/util/OpenStrategy$1.class\r
+SHA1-Digest: 0vIKHu+LBVxDc2VsRGtcU9z2rPc=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$4.class\r
+SHA1-Digest: wMcvrqnMlGj4k3Sf6OPQI62EMBQ=\r
+\r
+Name: org/eclipse/jface/preference/BooleanPropertyAction.class\r
+SHA1-Digest: xdpLLDpKyzwCZxOMJLWX9tt83cE=\r
+\r
+Name: org/eclipse/jface/dialogs/IPageChangeProvider.class\r
+SHA1-Digest: 33KEh4FFykggcUvH0cLKhOot+b4=\r
+\r
+Name: org/eclipse/jface/wizard/IWizardPage.class\r
+SHA1-Digest: a9+A7BQQkKb0C5X/NVEfv2xMzDU=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/NativeKeyFormatter.cl\r
+ ass\r
+SHA1-Digest: nNeirgwng3yQKzbAWBq0yXhfpX4=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceContentProvider.class\r
+SHA1-Digest: TXHe4L7LXxEdG0n4fxoYT4KjNR0=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer.class\r
+SHA1-Digest: vNWTlEJEdfV6dO0rkzwVOn6XWOU=\r
+\r
+Name: org/eclipse/jface/resource/ResourceRegistry.class\r
+SHA1-Digest: IOlJqFf+m30Kba/HN8/2Z0JH9X4=\r
+\r
+Name: org/eclipse/jface/preference/PreferencePage$1.class\r
+SHA1-Digest: jLLkIcssdCNQQ/9NkOQZIVjjS+E=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/error_ovr.gif\r
+SHA1-Digest: TK9Xs4Ma2nrknYgS1Uklk4YAfSM=\r
+\r
+Name: org/eclipse/jface/viewers/CustomHashtable$HashEnumerator.class\r
+SHA1-Digest: Uu37f/plY1D80mic7y8t9Kzinng=\r
+\r
+Name: org/eclipse/jface/menus/TextState.class\r
+SHA1-Digest: 7g5CAqZojYeU3Rm9RGK/vdBxflM=\r
+\r
+Name: org/eclipse/jface/operation/ModalContext$ModalContextThread.clas\r
+ s\r
+SHA1-Digest: /yE4JccpKtBesg2AJlbf6LY6ZZ8=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$2.class\r
+SHA1-Digest: eB0vqHEJNbMfR9Qfx3mawiKrNW8=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField.class\r
+SHA1-Digest: eWEI1/CKrCJEuR+Kyg9W8/6ZxM8=\r
+\r
+Name: org/eclipse/jface/dialogs/ImageAndMessageArea$1.class\r
+SHA1-Digest: PXMLexLRLhj6zPb5S+097PgFJeo=\r
+\r
+Name: org/eclipse/jface/viewers/ITreeViewerListener.class\r
+SHA1-Digest: J+vdR9Oz9XXy6s3YPB/8CxDT86Y=\r
+\r
+Name: about.html\r
+SHA1-Digest: M+fykt9heyWoEv1LNiIEeBhi/2Q=\r
+\r
+Name: org/eclipse/jface/commands/RadioState.class\r
+SHA1-Digest: dnjLILvfgNta2HYMH4lar7XhbzM=\r
+\r
+Name: org/eclipse/jface/viewers/ArrayContentProvider.class\r
+SHA1-Digest: aYmv1zcRQFD5ATDaEEmL06W56qs=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceManager.class\r
+SHA1-Digest: uarelCwb7g6CbiNeRL8CMoNtK4s=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$2.class\r
+SHA1-Digest: blI9FoBo6jfXM6qPyNC2P66BQR8=\r
+\r
+Name: org/eclipse/jface/bindings/TriggerSequence.class\r
+SHA1-Digest: EUa2iHMv4Q2XmImCqWq/w7Vtwvw=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnableDialog$3.class\r
+SHA1-Digest: nVRwnWunMDAXQh7QVuhuJr1CUe0=\r
+\r
+Name: org/eclipse/jface/viewers/LabelDecorator.class\r
+SHA1-Digest: gvHUWKGx7wppMggBC0ks0kzUnts=\r
+\r
+Name: org/eclipse/jface/action/SubMenuManager.class\r
+SHA1-Digest: 0Md+/otvaw/ER4iqGu7zwECmWIM=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer$TableTreeEditorImpl.cl\r
+ ass\r
+SHA1-Digest: fdZ61E/DAYVHbuLVK3Zu1Oi9zUA=\r
+\r
+Name: org/eclipse/jface/resource/StringConverter.class\r
+SHA1-Digest: gUPkCjqtAhSx0BjOyJN1AfkkOUY=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$2.class\r
+SHA1-Digest: 9DhGiE90W8u/nM6zTaiEmMMv+4c=\r
+\r
+Name: org/eclipse/jface/wizard/images/page.gif\r
+SHA1-Digest: cQM0Fq+6Z8epnE5peRkHHEm2ayQ=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$3.class\r
+SHA1-Digest: 3IVcA8A2blqOrSEM/CJJf7mgT6U=\r
+\r
+Name: org/eclipse/jface/action/SubContributionItem.class\r
+SHA1-Digest: oaR20nnISgYYwkwsVh0x/CdjxGg=\r
+\r
+Name: org/eclipse/jface/viewers/ICellEditorListener.class\r
+SHA1-Digest: bIgZl5hilWT8ONJglpXG2+lD9lI=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/LazySortedCollection$1.class\r
+SHA1-Digest: i2+jtJseLuyEDTrpn7fFQq1W0Yk=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewer$3.class\r
+SHA1-Digest: f2CbE/zhXzc5QxbYzKV8QRYL4+c=\r
+\r
+Name: org/eclipse/jface/resource/FontRegistry$1.class\r
+SHA1-Digest: BNdXtUNnzdXAtonMtO+inUTaSYM=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$6.class\r
+SHA1-Digest: Zuvuc3FgTGtFoCiMEhM3b7Ke2qs=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$5.class\r
+SHA1-Digest: STQH4pMPS95e3IvOVMx5F7jXAv4=\r
+\r
+Name: org/eclipse/jface/viewers/LabelProvider.class\r
+SHA1-Digest: rWbjVohCUCmM0No8WPHujncSy4E=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$13.class\r
+SHA1-Digest: Pr5/0MDJVbOWl2Y3EH1/zZnZhqU=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/ICoolBarManager2.c\r
+ lass\r
+SHA1-Digest: u0tSlPBuPJYOS+zX4M8L0SLHCEk=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewer.class\r
+SHA1-Digest: NCdDifsh8iVwz3Ktfpj4D2Q6RFc=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$PageContainerFillLayout.cl\r
+ ass\r
+SHA1-Digest: SblVjeNMPGVvykTHHg5raLfjahA=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts.properties\r
+SHA1-Digest: pLlTRGmJKGTnjwooLyohaqVVP1Q=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$2.class\r
+SHA1-Digest: RBpQmD8f3qL40iSphQ6W1R7xc5Y=\r
+\r
+Name: org/eclipse/jface/action/StatusLine$StatusLineLayout.class\r
+SHA1-Digest: yF+wk875Ggz/Op8tTDT/hOJ2+GM=\r
+\r
+Name: org/eclipse/jface/viewers/ContentViewer$2.class\r
+SHA1-Digest: PEjUSpFy6gXQr0rHKuuN1VAel+Y=\r
+\r
+Name: org/eclipse/jface/action/ToolBarContributionItem$2.class\r
+SHA1-Digest: mZgkllYMKqQiHrWwA5vYcgAdWeU=\r
+\r
+Name: org/eclipse/jface/dialogs/TitleAreaDialog.class\r
+SHA1-Digest: 44UXOjW+XFMC5/yaCcKQs6HFGBk=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer.class\r
+SHA1-Digest: SWxwhUBQk+6h+npZolDNTFPlknc=\r
+\r
+Name: org/eclipse/jface/commands/ActionHandler.class\r
+SHA1-Digest: ym9jCNObCU4o8zZqlZolaulvZQs=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressMonitorDialog$1.class\r
+SHA1-Digest: Ki3luz87q8F/TQAgkktuJXA3AQo=\r
+\r
+Name: org/eclipse/jface/window/Window$1.class\r
+SHA1-Digest: qJxw+zNqd2wl4GZODZwyW04CEbA=\r
+\r
+Name: org/eclipse/jface/action/SubMenuManager$1.class\r
+SHA1-Digest: 7LQtCfIC1p6IV0A6zoNgPwolGcQ=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$ResizeAction.class\r
+SHA1-Digest: voMjeDoBoqW/6LT3m1bXW6WHSpo=\r
+\r
+Name: org/eclipse/jface/dialogs/images/title_banner.gif\r
+SHA1-Digest: wBwKg54T7FuojhG33rUCNeYEsBk=\r
+\r
+Name: org/eclipse/jface/layout/TreeColumnLayout$1.class\r
+SHA1-Digest: zKwlYf3OSat9W4KdWfFVAzgW324=\r
+\r
+Name: org/eclipse/jface/window/Window$IExceptionHandler.class\r
+SHA1-Digest: Cg90thTLd8sZLgnp0HvEP9nBFug=\r
+\r
+Name: org/eclipse/jface/preference/ListEditor$3.class\r
+SHA1-Digest: oy8Km+UVzYMhZvVRpq/bN+IPT+U=\r
+\r
+Name: org/eclipse/jface/commands/RadioState$RadioStateManager$RadioGro\r
+ up.class\r
+SHA1-Digest: YiUCaSO3gZRUo07ojpZ1quUjR0s=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor$3.class\r
+SHA1-Digest: geSmCsbAPUpU30hx3tzhR+v0JTE=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditor$3.class\r
+SHA1-Digest: ZNeAwT+JTNTYIU+elZMGHHZ/i+I=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$2.class\r
+SHA1-Digest: ZEWNxaczvH/GE/lN+h7d6RWgYek=\r
+\r
+Name: org/eclipse/jface/preference/StringFieldEditor$2.class\r
+SHA1-Digest: GtkSrN6aJFLvjK25EyHKAXHBrvo=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/LazySortedCollection$Edge.cla\r
+ ss\r
+SHA1-Digest: FjMZiTyah3/2WP0BEOC8IFOj6SU=\r
+\r
+Name: org/eclipse/jface/viewers/TableViewerFocusCellManager.class\r
+SHA1-Digest: n1u3zMuOVbmHcFn94RjpJOBqXUw=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$4.class\r
+SHA1-Digest: udYOLAS/2ufp/M3DERlUmmAXm30=\r
+\r
+Name: org/eclipse/jface/util/ILogger.class\r
+SHA1-Digest: eNX1IaWuQk87dxk6lcEJffTsmu0=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditorActivationEvent.clas\r
+ s\r
+SHA1-Digest: +Lr0yRFP9kj7amR2ED+uBDZZcF8=\r
+\r
+Name: org/eclipse/jface/viewers/ITableColorProvider.class\r
+SHA1-Digest: wmgsAB35SwZM8BwNOtLCmreFyr0=\r
+\r
+Name: org/eclipse/jface/viewers/ILabelProvider.class\r
+SHA1-Digest: r6gLAjAFliFIrTFVPUmWB/D1X8M=\r
+\r
+Name: org/eclipse/jface/viewers/TreeSelection.class\r
+SHA1-Digest: f4joIQKmTe+Juw2OCgznJc+aO6M=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnableDialog.class\r
+SHA1-Digest: 7AuaaK/kLjP1+nLq2EQbBP9mf7A=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/IConcurrentModelListener.clas\r
+ s\r
+SHA1-Digest: AlSsuiWv/1RO84RuUb4FViH02ic=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/BackgroundContentProvider$1.c\r
+ lass\r
+SHA1-Digest: 8LTW5qNI97wLj9WPEBN4CJSz93s=\r
+\r
+Name: org/eclipse/jface/dialogs/InputDialog$1.class\r
+SHA1-Digest: wP0WAu37Lj+h6aZq76bcwWLAKJg=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/EmacsKeyFormatter.cla\r
+ ss\r
+SHA1-Digest: 0hUeaT8c6mHWJd/+oBvfBCnIYs0=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerColumn$2.class\r
+SHA1-Digest: C7s9vbHbmf0W4BATxnGysnm4Ixs=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$IActiveChecker.cl\r
+ ass\r
+SHA1-Digest: G5Nb6wPZpJCKZmEqLsAwTPrB7Xc=\r
+\r
+Name: org/eclipse/jface/wizard/IWizardContainer2.class\r
+SHA1-Digest: Ty0SM2BT4G17b4ZBiKNB1tA9rvg=\r
+\r
+Name: org/eclipse/jface/viewers/CellLabelProvider.class\r
+SHA1-Digest: R8a5UDueiUxjXGj92sBDgnnRAXE=\r
+\r
+Name: org/eclipse/jface/viewers/IStructuredContentProvider.class\r
+SHA1-Digest: 2FTYYk0MjKz3+PCqKTNXx+21qTI=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$1.class\r
+SHA1-Digest: acFOO0tUKeliKUoU9S7XzRzqbVw=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$2.class\r
+SHA1-Digest: TFqMD6a3zkzCguklz3gG9O3wKtk=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$6.class\r
+SHA1-Digest: Z9QZQi0F443FrjhIUiK15LTFgjY=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/BackgroundContentProvider$Sor\r
+ tThread.class\r
+SHA1-Digest: nz9OWBBdBQ8XzIdhiSeo3r8F2/c=\r
+\r
+Name: org/eclipse/jface/dialogs/Dialog$1.class\r
+SHA1-Digest: XQZG+5/hwLwaftLYOl42OzxNOOs=\r
+\r
+Name: org/eclipse/jface/preference/DirectoryFieldEditor.class\r
+SHA1-Digest: Wo0QhBmnem9HZjtBUvWwQAMm99k=\r
+\r
+Name: org/eclipse/jface/dialogs/IInputValidator.class\r
+SHA1-Digest: FTjnZG1JqzG8fWtZLBBxby8w1jM=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$8.class\r
+SHA1-Digest: SEHUNkxUvBnTpRjoHBascUrqHgI=\r
+\r
+Name: org/eclipse/jface/viewers/IDecoration.class\r
+SHA1-Digest: hFANTTYtdCm/M61HZVRyWhHSYzw=\r
+\r
+Name: org/eclipse/jface/window/Window$DefaultExceptionHandler.class\r
+SHA1-Digest: AnRDjJ7LRNfS8zveCdyEeDpU5gI=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$4.class\r
+SHA1-Digest: WFzvw1ShLrPg37mAP17gpRPa9fY=\r
+\r
+Name: org/eclipse/jface/preference/images/pref_dialog_title.gif\r
+SHA1-Digest: +f9inxjFleZdqF8QasPKYtOtQts=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnable$3.class\r
+SHA1-Digest: Y84Q2/VKWE8ULQQX1pGZ+W/9AB4=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$13.class\r
+SHA1-Digest: cKrV8PIN52KBnGjXaFtPChvT2F0=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor$3.class\r
+SHA1-Digest: n+8Id78pAM4vutmX36qgE8hDwoc=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$3.class\r
+SHA1-Digest: DIAGv99xi3dYhcVunLEWX5YZrFQ=\r
+\r
+Name: org/eclipse/jface/viewers/ComboBoxCellEditor$2.class\r
+SHA1-Digest: RngLKnOZIAY+9TwE7QN165zaGU8=\r
+\r
+Name: org/eclipse/jface/window/Window$FontChangeListener.class\r
+SHA1-Digest: HhKZE8ZyvdaraLNI1xoZC+oVMig=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnWeightData.class\r
+SHA1-Digest: U++p8SuVDhCRur15RA8iYcsNJCU=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewerRow.class\r
+SHA1-Digest: BauA9NOalZJDGBQEqeU1AgOopwo=\r
+\r
+Name: org/eclipse/jface/layout/LayoutGenerator.class\r
+SHA1-Digest: KYt3khyT2KxKIcOV53paU9UnPmc=\r
+\r
+Name: org/eclipse/jface/viewers/IPostSelectionProvider.class\r
+SHA1-Digest: xjw5YPx5EAiAny5uJcz7sMAvhzE=\r
+\r
+Name: org/eclipse/jface/util/Assert$AssertionFailedException.class\r
+SHA1-Digest: sY+Xvd2nNLy+6HJhRvFQ2wC6/vM=\r
+\r
+Name: org/eclipse/jface/dialogs/ImageAndMessageArea.class\r
+SHA1-Digest: eQgMElW7NoVRCXQNviWJTTwrAxI=\r
+\r
+Name: org/eclipse/jface/images/dots_button.gif\r
+SHA1-Digest: aMO48kHOSAa1q3kG1INeOUinOx0=\r
+\r
+Name: org/eclipse/jface/wizard/ProgressMonitorPart.class\r
+SHA1-Digest: y+3AL8O7qaXnGcFKSnIQwJjcDW4=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_macosx.properties\r
+SHA1-Digest: vDukkesScCaSuS1N/vKDLy5vLJY=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog.class\r
+SHA1-Digest: P7lNg0bQpNddBiVe4CY0CmYC9bY=\r
+\r
+Name: org/eclipse/jface/util/Util.class\r
+SHA1-Digest: 4Zi4P2yN8kj/0RmvRKHECH19kA4=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditorActivationStrategy$1\r
+ .class\r
+SHA1-Digest: 29yx3w38rYqnPHqsbLQHcJPvsa0=\r
+\r
+Name: org/eclipse/jface/bindings/keys/ParseException.class\r
+SHA1-Digest: ERt/VxrP+2MkZpZdJxJ4DnjBGfk=\r
+\r
+Name: org/eclipse/jface/commands/ToggleState.class\r
+SHA1-Digest: zDUpF6DqpLgvtGMl5zDwiVGXPcI=\r
+\r
+Name: org/eclipse/jface/dialogs/StatusDialog$MessageLine.class\r
+SHA1-Digest: M5vGIUZ3D67x8/NwNv2ULanAmt0=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$8.class\r
+SHA1-Digest: HnClWObvdBQLk7rjxkQ8nLUR+y4=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$UpdateItemSafeRunna\r
+ ble.class\r
+SHA1-Digest: zw7XcXQrg0C02MfPJUD8ktsMHrQ=\r
+\r
+Name: org/eclipse/jface/viewers/ITreeSelection.class\r
+SHA1-Digest: +F8z26Op0upYoPrfA3pm1e01ebc=\r
+\r
+Name: org/eclipse/jface/dialogs/TrayDialog$3.class\r
+SHA1-Digest: vq0hDaycb0Z3ppgTYHwLJvRQo0g=\r
+\r
+Name: org/eclipse/jface/viewers/ITreeContentProvider.class\r
+SHA1-Digest: 7gb/bVtY4Rzl3UVgCmgg/mPQCDQ=\r
+\r
+Name: org/eclipse/jface/fieldassist/TextContentAdapter.class\r
+SHA1-Digest: Ny050dfIJlel4CzOFQvfGV8UeGw=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$UpdateSequenceLi\r
+ stener.class\r
+SHA1-Digest: ddcHLbRqDLzzy0WREs8zSu+v+sk=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnLabelProvider.class\r
+SHA1-Digest: VHaJNHpmiZuYwYnCJ6LvSkMCAqQ=\r
+\r
+Name: org/eclipse/jface/viewers/TableTreeViewer$4.class\r
+SHA1-Digest: N9pFGoIvcHk1o+VtAr6K60swRQk=\r
+\r
+Name: org/eclipse/jface/preference/ScaleFieldEditor$2.class\r
+SHA1-Digest: DbEydSGp0pewvPadWAB79W2IpOQ=\r
+\r
+Name: org/eclipse/jface/preference/ListEditor.class\r
+SHA1-Digest: C5wusKhxLf/zzXV7mQIB2qDlDAY=\r
+\r
+Name: org/eclipse/jface/dialogs/images/message_error.gif\r
+SHA1-Digest: 1cph4z9iIwiy2S9c0S/cYT+I3iI=\r
+\r
+Name: org/eclipse/jface/resource/ImageRegistry$2.class\r
+SHA1-Digest: Q29P91VkibEPlLYuajQVG1HPFw8=\r
+\r
+Name: org/eclipse/jface/viewers/EditingSupport.class\r
+SHA1-Digest: yfMP6u+HhNi6W0BSDVxlNDzANsk=\r
+\r
+Name: org/eclipse/jface/action/MenuManager$2.class\r
+SHA1-Digest: ZeZPLHJZJnXEs5Z9+KTNmntNk5o=\r
+\r
+Name: org/eclipse/jface/util/OpenStrategy$2.class\r
+SHA1-Digest: xM2dHa4s9ZDKYlJ2bhSqOqxLRkE=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$5.class\r
+SHA1-Digest: EAq3sdtsWSXV3WIPP0TLtlGI48U=\r
+\r
+Name: org/eclipse/jface/action/GroupMarker.class\r
+SHA1-Digest: RECukQoU4kH9MozQtUeoN4YzYX4=\r
+\r
+Name: org/eclipse/jface/preference/StringButtonFieldEditor$1.class\r
+SHA1-Digest: mBHRuZaMvalRFCGXwP+FW5LPhn8=\r
+\r
+Name: org/eclipse/jface/viewers/TreePathViewerSorter$1.class\r
+SHA1-Digest: 6efAh49omiCnSe5LJzZPyX8v6j8=\r
+\r
+Name: org/eclipse/jface/viewers/CheckboxTableViewer.class\r
+SHA1-Digest: SBjSD0xcqjI2XLx9KytVOMoUh4k=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$1.class\r
+SHA1-Digest: fm5zmAwRHAKwrSpxPwIbZ1Es0Cc=\r
+\r
+Name: org/eclipse/jface/preference/PreferencePage$2.class\r
+SHA1-Digest: BLi85lNcmPGhMUcAG4FyFmtNZRg=\r
+\r
+Name: org/eclipse/jface/layout/GridLayoutFactory.class\r
+SHA1-Digest: 1ixPBQcO24d88JEznt9fOagQk9g=\r
+\r
+Name: org/eclipse/jface/viewers/NamedHandleObjectLabelProvider.class\r
+SHA1-Digest: RpQ1vvqUlcYDYO7kKgG6Wp5czVs=\r
+\r
+Name: org/eclipse/jface/action/ContributionManager$1.class\r
+SHA1-Digest: SX6xIgBnN7AdowIhGKbnCfpB1xY=\r
+\r
+Name: org/eclipse/jface/preference/StringButtonFieldEditor.class\r
+SHA1-Digest: Gc3hsSfRxQINvVoPs7qMgzRNNWg=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$3.class\r
+SHA1-Digest: UI2Pr1xWjiMDmCUwKVgZeU4+MT4=\r
+\r
+Name: org/eclipse/jface/resource/RGBColorDescriptor.class\r
+SHA1-Digest: Sha6puqMEpI+/CTi1WCXX0Ey9xc=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/KeyFormatterFactory.c\r
+ lass\r
+SHA1-Digest: aduzk4wOIWaXbZe7TXENxrkkamI=\r
+\r
+Name: org/eclipse/jface/resource/MissingImageDescriptor.class\r
+SHA1-Digest: GVzwDdS4Hcf5QQRdYcZs+1ZTdzg=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_windowsnt.properties\r
+SHA1-Digest: O8sXG9Pxd+YKWlI4WGbGETZerC0=\r
+\r
+Name: org/eclipse/jface/resource/CompositeImageDescriptor.class\r
+SHA1-Digest: 0Q1r4JDo1ywexXXHLEFyc+mUpQg=\r
+\r
+Name: org/eclipse/jface/dialogs/ImageAndMessageArea$2.class\r
+SHA1-Digest: nQjeywsE58mq4F+cWud6DJtrfSY=\r
+\r
+Name: org/eclipse/jface/viewers/ITreePathContentProvider.class\r
+SHA1-Digest: c3U1VZfwTMXr77Imuv7z1KQSgY4=\r
+\r
+Name: org/eclipse/jface/operation/ModalContext.class\r
+SHA1-Digest: MIyvgca76IoeDGrcKUOTf1+zVAs=\r
+\r
+Name: org/eclipse/jface/viewers/ICheckable.class\r
+SHA1-Digest: rK9QVEZwEdCcusxW3jPnhyKP69s=\r
+\r
+Name: org/eclipse/jface/action/IMenuListener.class\r
+SHA1-Digest: XGLegWw7uJyNCBFMeqhJqeFQq80=\r
+\r
+Name: org/eclipse/jface/dialogs/IDialogConstants.class\r
+SHA1-Digest: F6Q5E9oHkyGl+kdTgOQwIut4YI0=\r
+\r
+Name: org/eclipse/jface/operation/AccumulatingProgressMonitor$3.class\r
+SHA1-Digest: /W3tELlqeYoDMeqi5OpIcwy25oE=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$ContentProp\r
+ osalPopup.class\r
+SHA1-Digest: lR99G8CxSJNu7IU+AQfOo/qzgMs=\r
+\r
+Name: org/eclipse/jface/util/Assert.class\r
+SHA1-Digest: XdV7j5TiXZEMFmkgPoa/X0RuwVQ=\r
+\r
+Name: org/eclipse/jface/viewers/DialogCellEditor$DialogCellLayout.clas\r
+ s\r
+SHA1-Digest: bk25CDJJL7PDwHtRjI35Ui6amOQ=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceNode.class\r
+SHA1-Digest: 6yml3aZl2p9SAdAPAoFHCNWTkQI=\r
+\r
+Name: org/eclipse/jface/action/Action.class\r
+SHA1-Digest: YY+4PWn4Z1MGifXiRcvq3baYrFg=\r
+\r
+Name: org/eclipse/jface/util/SafeRunnableDialog$4.class\r
+SHA1-Digest: UryJn0w04ssNX6KLS8Wz70PXRnY=\r
+\r
+Name: org/eclipse/jface/messages.properties\r
+SHA1-Digest: 1AE74/GTrLZcH6PUlF4unDyg/r0=\r
+\r
+Name: org/eclipse/jface/dialogs/ControlEnableState.class\r
+SHA1-Digest: bNJx0mD0lQAhKw4/eaWoSTXLFl0=\r
+\r
+Name: org/eclipse/jface/viewers/FocusCellHighlighter.class\r
+SHA1-Digest: d6PtIB22jvo+o6to25JX8jSh1zY=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTreeViewer$3.class\r
+SHA1-Digest: UeNqzpJXcRURKWgx+MpCSZxB03Q=\r
+\r
+Name: org/eclipse/jface/viewers/Viewer$1.class\r
+SHA1-Digest: XZTuWaebhl1SRc3T3suK0pRBeYU=\r
+\r
+Name: org/eclipse/jface/action/ActionContributionItem$4.class\r
+SHA1-Digest: vkPZOQLw+WeldJa9FdYnS3mIKWY=\r
+\r
+Name: org/eclipse/jface/internal/InternalPolicy.class\r
+SHA1-Digest: 7/DN/MbxcrL2BY1KCJQSCHz9GZU=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/errorqf_ovr.gif\r
+SHA1-Digest: Um1Gp9X1ZLQQXPbSn55JPp4zdQE=\r
+\r
+Name: org/eclipse/jface/action/SubStatusLineManager.class\r
+SHA1-Digest: HMQeBJgEcTGjW3WjkHEFGFY54BM=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequenceText$TraversalFilterM\r
+ anager.class\r
+SHA1-Digest: rG7+caEfvMHcnLpB3P+bZcG+JG4=\r
+\r
+Name: org/eclipse/jface/preference/IntegerFieldEditor.class\r
+SHA1-Digest: ZknSkGrFM+5x9rutNtf8J+J18tk=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$7.class\r
+SHA1-Digest: DfyzSlUVRhku5oAHcaimtTUsMmo=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$6.class\r
+SHA1-Digest: A6O4p4KCOLRlh+aOjcs6HpTY3Ks=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$14.class\r
+SHA1-Digest: PYrRZRMj0hwbSK/zF3Uw/D70shI=\r
+\r
+Name: org/eclipse/jface/dialogs/TitleAreaDialog$1.class\r
+SHA1-Digest: FNeyU+GHMHkrQAwXOVd+5P9nrQg=\r
+\r
+Name: org/eclipse/jface/viewers/Viewer.class\r
+SHA1-Digest: gBkcQUZMqjjBaDHcl1WXyw4RCrY=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/ChangeQueue$Change.class\r
+SHA1-Digest: 9f/YGJeC/TD7LY8woROG6M1fDQE=\r
+\r
+Name: org/eclipse/jface/resource/ResourceManager.class\r
+SHA1-Digest: Dyhj/QDC0ZdNmq0Qtt5rAU/6gw8=\r
+\r
+Name: org/eclipse/jface/action/StatusLineLayoutData.class\r
+SHA1-Digest: T8HPa7xIJIIGPNT46SdWVSXArUE=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$MoveAction.class\r
+SHA1-Digest: thlMX+3p2Eez56ZbgmddcMXqX6s=\r
+\r
+Name: org/eclipse/jface/wizard/IWizardContainer.class\r
+SHA1-Digest: t6UIOrVi+LgTyr6Ror/O1myGmc8=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor$3.class\r
+SHA1-Digest: A6fAlBwC5yzeFeyV8+yhfbATxIM=\r
+\r
+Name: org/eclipse/jface/viewers/CustomHashtable$EmptyEnumerator.class\r
+SHA1-Digest: iRTEprgJrq6KopjhMrYJO5yq0Nc=\r
+\r
+Name: org/eclipse/jface/preference/ComboFieldEditor.class\r
+SHA1-Digest: VBXzKuE1En4dC8OU8EST0pAg3HU=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_windows2000.properties\r
+SHA1-Digest: fNufPvbgB1v0tuy/96aaa7Ox+Ds=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$ContentProp\r
+ osalPopup$PopupCloserListener.class\r
+SHA1-Digest: 5rFpZ7aGwkim8B0mrZyrf41UxRc=\r
+\r
+Name: org/eclipse/jface/util/IPropertyChangeListener.class\r
+SHA1-Digest: NsV5pgvLNCDRe6D77HTE5mbo9Zw=\r
+\r
+Name: org/eclipse/jface/action/ToolBarContributionItem$3.class\r
+SHA1-Digest: u29QnAR7W4XJckBTZigBhlhe9Jo=\r
+\r
+Name: org/eclipse/jface/util/IOpenEventListener.class\r
+SHA1-Digest: O1KuuU6LNLaw4ohBnG8tkPtOVx8=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter.class\r
+SHA1-Digest: slrNm/O5OWcqB4nFYluqPlnknp8=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeySequence.class\r
+SHA1-Digest: T/jhzNZ6gjUuFW17XnTggVHjnW4=\r
+\r
+Name: org/eclipse/jface/preference/JFacePreferences.class\r
+SHA1-Digest: j1Yen4upKix72kc4gM+8V+IT+mQ=\r
+\r
+Name: org/eclipse/jface/viewers/TreeNode.class\r
+SHA1-Digest: pWqn3M1KVaIrkF19WJZJaYd9cx8=\r
+\r
+Name: org/eclipse/jface/internal/provisional/action/IToolBarContributi\r
+ onItem.class\r
+SHA1-Digest: T28GsgkyXThg7Sqi9/guUy3WaCA=\r
+\r
+Name: org/eclipse/jface/viewers/TextCellEditor.class\r
+SHA1-Digest: I/qrH7ONV70oN00F/mD9gLuwUM8=\r
+\r
+Name: org/eclipse/jface/dialogs/ProgressMonitorDialog$2.class\r
+SHA1-Digest: A/1jXUaO26kKR0gcJ676cEmeDWc=\r
+\r
+Name: org/eclipse/jface/wizard/WizardPage.class\r
+SHA1-Digest: 8vTb/Mu1p3w48VRMvf7bS9uVtd0=\r
+\r
+Name: org/eclipse/jface/window/Window$2.class\r
+SHA1-Digest: gn5QsegN1dmrC9AfR+XyR7WryA0=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_linux.properties\r
+SHA1-Digest: MPIe46ddE+PVndulixs9MVOt9QM=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$Hover.class\r
+SHA1-Digest: BvM9VBU+lBCxadpvtyYFvpodkqs=\r
+\r
+Name: org/eclipse/jface/dialogs/images/message_warning.gif\r
+SHA1-Digest: NTU6oN3RVw9xXNgd8iqw6yXxmws=\r
+\r
+Name: org/eclipse/jface/commands/RadioState$RadioStateManager.class\r
+SHA1-Digest: 4pf9kmlMExF4NuTbbxwYasNwjxI=\r
+\r
+Name: org/eclipse/jface/resource/DerivedImageDescriptor.class\r
+SHA1-Digest: Z3WfSlMbycShUorbqWYWrcKnAhE=\r
+\r
+Name: org/eclipse/jface/fieldassist/IControlCreator.class\r
+SHA1-Digest: bBaZd6FmLuWnd3IBehnFDoQS4So=\r
+\r
+Name: org/eclipse/jface/viewers/DecorationOverlayIcon.class\r
+SHA1-Digest: Y7UN+ZBMslqOwChKuTP+JVbvOCY=\r
+\r
+Name: org/eclipse/jface/preference/FontFieldEditor$4.class\r
+SHA1-Digest: HOPmvZNPfrdPFIdmb2koHIfQVA0=\r
+\r
+Name: org/eclipse/jface/viewers/TableLayout.class\r
+SHA1-Digest: zPCs8XBBMr0ffICbyPASqKq7Uyw=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/ConcurrentTableUpdator.class\r
+SHA1-Digest: OwzjHLLd/DTQ7zD5MYsfBSZYtLs=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditor$4.class\r
+SHA1-Digest: 46oxUsLbLTUc8QXl4t6S8Sd+/20=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$3.class\r
+SHA1-Digest: Or+4UVtfOW2WSAYqLhNwF34+jlE=\r
+\r
+Name: org/eclipse/jface/preference/StringFieldEditor$3.class\r
+SHA1-Digest: Z2Tzj8/yEyrrbi1uk/RnOOGx5Os=\r
+\r
+Name: org/eclipse/jface/fieldassist/FieldAssistColors.class\r
+SHA1-Digest: 6O3TNVYciymrfLYWCrbX1TopUUY=\r
+\r
+Name: org/eclipse/jface/viewers/ISelection.class\r
+SHA1-Digest: v6qu+ZFiiJ/B7ag5Te3pBK19BHQ=\r
+\r
+Name: org/eclipse/jface/viewers/TreePath.class\r
+SHA1-Digest: qM2oISuX8yH+BaKH7B4KZNWfUVU=\r
+\r
+Name: org/eclipse/jface/viewers/TreeViewer$5.class\r
+SHA1-Digest: Isq93suZbPTe+8u6BYsK6J9rZ5E=\r
+\r
+Name: org/eclipse/jface/viewers/IElementComparer.class\r
+SHA1-Digest: sOaZtvS4AB/hqgZ5D1RIsBJ6lyI=\r
+\r
+Name: org/eclipse/jface/layout/TreeColumnLayout.class\r
+SHA1-Digest: x+0oz+CX28DxveiHNLcH8z2PSp8=\r
+\r
+Name: org/eclipse/jface/action/SubContributionManager$1.class\r
+SHA1-Digest: y2osOkOKwIUm8er1pBR708XDBHQ=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerEditorActivationListener.c\r
+ lass\r
+SHA1-Digest: bZzW8WEagCO4g5LGt7gAdKv4fok=\r
+\r
+Name: org/eclipse/jface/window/DefaultToolTip.class\r
+SHA1-Digest: vWYCKbbRk7vky9sv4+XyuNzoBsM=\r
+\r
+Name: org/eclipse/jface/util/Geometry.class\r
+SHA1-Digest: NOSckiiUmNQhIY4uV5Wdzgt7Pl8=\r
+\r
+Name: org/eclipse/jface/resource/DeviceResourceManager.class\r
+SHA1-Digest: K0fl0ke6tO8VbENziK6aQ3L60Ok=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$PageLayout.class\r
+SHA1-Digest: hgPfD3IdU0KAPdn/+v+qs+JNL7k=\r
+\r
+Name: org/eclipse/jface/action/ExternalActionManager$IBindingManagerCa\r
+ llback.class\r
+SHA1-Digest: ltqW2wL1ugLf4e22Vauu7bLzGNI=\r
+\r
+Name: org/eclipse/jface/viewers/DialogCellEditor.class\r
+SHA1-Digest: H6RNBVJwdHT0CD1AvM1h49Pgjpk=\r
+\r
+Name: org/eclipse/jface/window/ToolTip$ToolTipOwnerControlListener.cla\r
+ ss\r
+SHA1-Digest: raTdFjdhKwG5kUAmMvx7frBvQ/c=\r
+\r
+Name: org/eclipse/jface/action/SubContributionManager.class\r
+SHA1-Digest: U5W16vpvNvo4s2TZxiaOxJEnRLY=\r
+\r
+Name: org/eclipse/jface/viewers/StructuredViewer$2.class\r
+SHA1-Digest: ULFmm/2x5DVLM76TmWuN/KHgJeM=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/ChangeQueue.class\r
+SHA1-Digest: q7v2iwDl52cXjFX5aGC50Su/qtY=\r
+\r
+Name: org/eclipse/jface/fieldassist/DecoratedField$3.class\r
+SHA1-Digest: HopONxRvINGg5HdPKEvjSYTJQsk=\r
+\r
+Name: org/eclipse/jface/fieldassist/ControlDecoration$7.class\r
+SHA1-Digest: vw4W7AHZA8HlQmkK1QZH2kzNR9Q=\r
+\r
+Name: org/eclipse/jface/util/DelegatingDropAdapter$1.class\r
+SHA1-Digest: YTc8uaLSnGz/1kS97jxUqoLU5Z8=\r
+\r
+Name: org/eclipse/jface/viewers/ViewerLabel.class\r
+SHA1-Digest: RmWs07S4iNrcxmk/9vOwwJT0u1A=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/EmacsKeyFormatter.pro\r
+ perties\r
+SHA1-Digest: f2Wrkiulg1IixjoSP3f/We7gLlk=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_windowsxp.properties\r
+SHA1-Digest: AOq+tOEd4D7kDs1EriU8ejSUBw0=\r
+\r
+Name: org/eclipse/jface/preference/RadioGroupFieldEditor$1.class\r
+SHA1-Digest: Ht+NUxZq1ltSesaqW4c5lPo97FM=\r
+\r
+Name: org/eclipse/jface/preference/ScaleFieldEditor.class\r
+SHA1-Digest: ebMXK9FL5rhpK0uhuPPsxf8V0PA=\r
+\r
+Name: org/eclipse/jface/fieldassist/images/info_ovr.gif\r
+SHA1-Digest: QOIr94zJdt1aM96T1ySsGB2diq0=\r
+\r
+Name: org/eclipse/jface/dialogs/Dialog$2.class\r
+SHA1-Digest: t1PEV5jkUDjpT+vZEIyNQ+D/t6k=\r
+\r
+Name: org/eclipse/jface/bindings/Trigger.class\r
+SHA1-Digest: ScMIxH6pA8H2s/qpnZ4RStKqm4A=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$9.class\r
+SHA1-Digest: 9CysviQ86AJCcrzyR3RsHVNtdLo=\r
+\r
+Name: org/eclipse/jface/action/StatusLine$1.class\r
+SHA1-Digest: 1eNlUkuGlgioHpas3+yLSCNIKwY=\r
+\r
+Name: org/eclipse/jface/dialogs/ErrorDialog.class\r
+SHA1-Digest: enTlYXi3qDaY12njhet+QCoewqc=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewer.class\r
+SHA1-Digest: 1lrwGiVxUTZvXpYovewE8JppR50=\r
+\r
+Name: org/eclipse/jface/layout/GridDataFactory.class\r
+SHA1-Digest: ZNJ6USDgac1ey4OzFI3Nq1YEmW0=\r
+\r
+Name: org/eclipse/jface/dialogs/IPageChangedListener.class\r
+SHA1-Digest: 8B3Te8gqEOgUJWN6ZTxIpy+Vfu8=\r
+\r
+Name: org/eclipse/jface/wizard/WizardDialog$5.class\r
+SHA1-Digest: bD3ABgsmJGNBiIQoJm7/hkm834I=\r
+\r
+Name: org/eclipse/jface/preference/PreferenceDialog$14.class\r
+SHA1-Digest: 32dZRx1+iJRASSKriotmgOQdURc=\r
+\r
+Name: org/eclipse/jface/viewers/TreePathViewerSorter.class\r
+SHA1-Digest: T+cz5sTsuNx8icLNrEIwlt2QOj4=\r
+\r
+Name: org/eclipse/jface/viewers/CellEditor$4.class\r
+SHA1-Digest: dFe4RhMEFxg4EY5y9W3Z+/loc8g=\r
+\r
+Name: org/eclipse/jface/dialogs/PopupDialog$4.class\r
+SHA1-Digest: +xnp7OGFdUnmUUM/AWMp2p8ly68=\r
+\r
+Name: org/eclipse/jface/dialogs/AnimatorFactory.class\r
+SHA1-Digest: YCT0EGjeUbg8jYcm8shnbDGsW8Q=\r
+\r
+Name: org/eclipse/jface/viewers/ComboBoxCellEditor$3.class\r
+SHA1-Digest: vkM9PvZ4rF9F/wo2GzgYzOYydH4=\r
+\r
+Name: org/eclipse/jface/viewers/OwnerDrawLabelProvider$1.class\r
+SHA1-Digest: rXBeIfEAbZt/agUC5qCogBQT1As=\r
+\r
+Name: org/eclipse/jface/viewers/ICellEditorValidator.class\r
+SHA1-Digest: ghG+8bfz08dZdRU29KWTaHI/Rjw=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeyStroke.class\r
+SHA1-Digest: xcTPBRotDFWyjD8BNgymKE2gS+Y=\r
+\r
+Name: org/eclipse/jface/bindings/keys/KeyBinding.class\r
+SHA1-Digest: kwvTdtSvRi+QnHqhh4fmIlZIyGE=\r
+\r
+Name: org/eclipse/jface/fieldassist/FieldAssistColors$1.class\r
+SHA1-Digest: TXyAp8d6WU00E1wJMO2BeGAa/Ck=\r
+\r
+Name: org/eclipse/jface/dialogs/ErrorSupportProvider.class\r
+SHA1-Digest: gvaYXsWvVx/66U1KDxG7mZv9TV4=\r
+\r
+Name: org/eclipse/jface/dialogs/MessageDialogWithToggle$1.class\r
+SHA1-Digest: OcOo7ww80O+jNlWfO8kJSoX/bmY=\r
+\r
+Name: org/eclipse/jface/viewers/DecorationContext.class\r
+SHA1-Digest: hGnn7RBTdKb3jxicuGW5ioH3rQk=\r
+\r
+Name: org/eclipse/jface/viewers/ComboBoxCellEditor.class\r
+SHA1-Digest: kgR5uSqCR6xLG6o46EDT5PxLadY=\r
+\r
+Name: org/eclipse/jface/resource/JFaceResources$1.class\r
+SHA1-Digest: Dj7mbHmRa6ff5Plfw8Vr7qPnUB8=\r
+\r
+Name: org/eclipse/jface/viewers/AbstractTableViewer$VirtualManager.cla\r
+ ss\r
+SHA1-Digest: VsUK9MANZr6j1KV9du+/kmE9pis=\r
+\r
+Name: org/eclipse/jface/preference/IPreferenceNode.class\r
+SHA1-Digest: 8tRoUpdBxtXH37yYHrB0B0E8mkY=\r
+\r
+Name: org/eclipse/jface/dialogs/TrayDialog$4.class\r
+SHA1-Digest: 8RyiTidCFwaxF0gH+BJ7WEdVJ7U=\r
+\r
+Name: org/eclipse/jface/action/images/stop.gif\r
+SHA1-Digest: BPcRKHh123ukCk1ZIcRxysDWb3Y=\r
+\r
+Name: org/eclipse/jface/viewers/TreeExpansionEvent.class\r
+SHA1-Digest: dxgpuR+QbjaE0xBNYuiCaOBkB0o=\r
+\r
+Name: org/eclipse/jface/dialogs/IDialogPage.class\r
+SHA1-Digest: rBVidzUUL1Qu/Ro2KSkJ16brX0A=\r
+\r
+Name: org/eclipse/jface/bindings/keys/formatting/IKeyFormatter.class\r
+SHA1-Digest: Uy80BmGwT0KCWMkZ1gdTijPn77A=\r
+\r
+Name: org/eclipse/jface/util/OpenStrategy$3.class\r
+SHA1-Digest: q+71OFHnpsmqfZwQpr+lveNVKeE=\r
+\r
+Name: org/eclipse/jface/fieldassist/ContentProposalAdapter$6.class\r
+SHA1-Digest: 6m+z82N0vDfXlV496kFa6qa8FIg=\r
+\r
+Name: org/eclipse/jface/viewers/ColumnViewerToolTipSupport.class\r
+SHA1-Digest: dYhZ6C6DNlIasK5ii//RWXk7FT4=\r
+\r
+Name: org/eclipse/jface/preference/StringButtonFieldEditor$2.class\r
+SHA1-Digest: so+QwMmBgkGvU/GbGvFaZe1wA9o=\r
+\r
+Name: org/eclipse/jface/window/WindowManager.class\r
+SHA1-Digest: Ti8XgX1pgBG7tLPHFVKPA4WcpRo=\r
+\r
+Name: org/eclipse/jface/resource/jfacefonts_qnx.properties\r
+SHA1-Digest: 1XnKDbwb/W15J/0x9HL752KjFIA=\r
+\r
+Name: org/eclipse/jface/viewers/deferred/LazySortedCollection.class\r
+SHA1-Digest: 6NhclZEGF0+Rs69muBm13BJXTMw=\r
+\r
--- /dev/null
+Manifest-Version: 1.0\r
+Ant-Version: Apache Ant 1.7.1\r
+Created-By: 10.0-b23 (Sun Microsystems Inc.)\r
+\r
--- /dev/null
+#Processed using Jarprocessor
+pack200.args = -E4
+pack200.conditioned = true
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 2, 2006</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+###############################################################################
+# Copyright (c) 2000, 2005 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+pluginName = JFace
+providerName = Eclipse.org
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="BPT Plugin" />
+ <meta name="description" value="Binary Partition Tree Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.BptSegmenter" />
+ <jar file="bptplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="IGC Plugin" />
+ <meta name="description" value="Interactive Graph Cuts Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.IgcSegmenter" />
+ <jar file="igcplugin.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SIOX Plugin" />
+ <meta name="description" value="Simple Interactive Object Extraction Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SioxSegmenter" />
+ <jar file="sioxplugin.jar" />
+ <jar file="sioxapi.jar" />
+</plugin>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<plugin>
+ <meta name="name" value="SRG Plugin" />
+ <meta name="description" value="Seeded Region Growing Plugin" />
+ <meta name="vendor" value="Dublin City University" />
+ <meta name="author" value="Kevin McGuinness" />
+ <meta name="email" value="kevin.mcguinness@gmail.com" />
+ <meta name="segmenter" value="ie.dcu.segment.algorithm.SrgSegmenter" />
+ <jar file="srgplugin.jar" />
+</plugin>
--- /dev/null
+app.name=Interactive Segmentation Tool
+app.package=ist
+app.version=1.3.4
+app.author=Kevin McGuinness
+app.email=kevin.mcguinness@eeng.dcu.ie
+app.website=http://kspace.cdvp.dcu.ie/public/interactive-segmentation
+app.synopsis=The CDVP Interactive Segmentation Tool
+app.main=ie.dcu.apps.ist.Main
+
+# Compilation properties
+java.debug=off
+java.source.version=1.5
+java.target.version=1.5
+
--- /dev/null
+
+OpenAction.dialog.text=Open an image or saved context
+OpenAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.ctx
+OpenAction.dialog.filter.text=Image and Context Files
+
+SaveAction.dialog.text=Save Segmentation Context
+SaveAction.dialog.filter.exts=*.ctx
+SaveAction.dialog.filter.text=Segmentation Context Files
+
+ExportViewAction.dialog.text=Export View As Image
+ExportViewAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;
+ExportViewAction.dialog.filter.text=Image Files
+
+OpenExperimentAction.dialog.text=Open an experiment file
+OpenExperimentAction.dialog.filter.exts=*.exp
+OpenExperimentAction.dialog.filter.text=Experiment Files
+
+# Actions
+action.OpenAction.text=&Open...@Command+O
+action.OpenAction.image=file:resources/icons/open.png
+action.OpenAction.tooltip=Open image
+
+action.SaveAction.text=&Save@Command+S
+action.SaveAction.image=file:resources/icons/save.png
+action.SaveAction.tooltip=Save segmentation context
+
+action.SaveAsAction.text=S&ave As...@Command+Shift+S
+action.SaveAsAction.image=file:resources/icons/save-as.png
+action.SaveAsAction.tooltip=Save segmentation context as...
+
+action.ExportViewAction.text=&Current View...@Command+Alt+V
+action.ExportViewAction.image=file:resources/icons/image.png
+action.ExportViewAction.tooltip=Export view as image
+
+action.ExportImageMapAction.text=&HTML Image Map...@Command+Alt+H
+action.ExportImageMapAction.image=file:resources/icons/html.png
+action.ExportImageMapAction.tooltip=Export objects as HTML image map
+
+action.ExportTransparentPNGAction.text=Export Transparent &PNG...@Command+Alt+P
+action.ExportTransparentPNGAction.image=file:resources/icons/image.png
+action.ExportTransparentPNGAction.tooltip=Export a PNG of the current segmentation with a transparent background
+
+action.ImportAction.text=&Import...
+action.ImportAction.image=file:resources/icons/import.png
+action.ImportAction.tooltip=Import images
+
+action.PrintAction.text=&Print@Command+P
+action.PrintAction.image=file:resources/icons/print.png
+action.PrintAction.tooltip=Print view
+
+action.ExitAction.text=E&xit@Command+Q
+action.ExitAction.image=file:resources/icons/exit.png
+action.ExitAction.tooltip=Quit the application
+
+action.UndoAction.text=&Undo@Command+Z
+action.UndoAction.image=file:resources/icons/undo.png
+action.UndoAction.tooltip=Undo last markup
+
+action.RedoAction.text=&Redo@Command+Y
+action.RedoAction.image=file:resources/icons/redo.png
+action.RedoAction.tooltip=Redo markup
+
+action.CopyAction.text=&Copy@Command+C
+action.CopyAction.image=file:resources/icons/copy.png
+action.CopyAction.tooltip=Copy view to clipboard as image
+
+action.PreferencesAction.text=&Preferences...@Command+,
+action.PreferencesAction.image=file:resources/icons/preferences.png
+action.PreferencesAction.tooltip=Change application settings
+
+action.AddFilesAction.text=&Add Files...@Command+A
+action.AddFilesAction.image=file:resources/icons/add.png
+action.AddFilesAction.tooltip=Add image files to project
+
+action.RemoveFilesAction.text=&Remove Files@Command+R
+action.RemoveFilesAction.image=file:resources/icons/remove.png
+action.RemoveFilesAction.tooltip=Remove selected image files from project
+
+action.PreviousAction.text=&Previous@Command+[
+action.PreviousAction.image=file:resources/icons/previous.png
+action.PreviousAction.tooltip=Move to previous image in directory
+
+action.NextAction.text=&Next@Command+]
+action.NextAction.image=file:resources/icons/next.png
+action.NextAction.tooltip=Move to next image in directory
+
+action.HelpAction.text=&Help Contents...@Command+F1
+action.HelpAction.image=file:resources/icons/help.png
+action.HelpAction.tooltip=Show application help browser
+action.HelpAction.helpURL=http://kspace.cdvp.dcu.ie/public/interactive-segmentation/doc/help.html
+
+action.AboutAction.text=&About...
+action.AboutAction.image=file:resources/icons/about.png
+action.AboutAction.tooltip=About the application
+action.AboutAction.aboutImage=file:resources/icons/about-box.png
+
+action.OpenExperimentAction.text=Open &Experiment...
+action.OpenExperimentAction.image=file:resources/icons/experiment.png
+action.OpenExperimentAction.tooltip=Open an experiment file and switch to experiment mode
+
+action.ConfigureSegmenterAction.text=&Configure Segmenter...@Command+Shift+C
+action.ConfigureSegmenterAction.image=file:resources/icons/preferences.png
+action.ConfigureSegmenterAction.tooltip=Configure segmenter parameters
+
+action.SelectSegmenterAction.NA=Algorithm not available on this platform
+
+
+
--- /dev/null
+
+OpenAction.dialog.text=Open an image or saved context
+OpenAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.ctx
+OpenAction.dialog.filter.text=Image and Context Files
+
+SaveAction.dialog.text=Save Segmentation Context
+SaveAction.dialog.filter.exts=*.ctx
+SaveAction.dialog.filter.text=Segmentation Context Files
+
+ExportViewAction.dialog.text=Export View As Image
+ExportViewAction.dialog.filter.exts=*.jpg;*.jpeg;*.png;*.gif;*.bmp;
+ExportViewAction.dialog.filter.text=Image Files
+
+OpenExperimentAction.dialog.text=Open an experiment file
+OpenExperimentAction.dialog.filter.exts=*.exp
+OpenExperimentAction.dialog.filter.text=Experiment Files
+
+# Actions
+action.OpenAction.text=&Open...@Ctrl+O
+action.OpenAction.image=file:resources/icons/open.png
+action.OpenAction.tooltip=Open image
+
+action.SaveAction.text=&Save@Ctrl+S
+action.SaveAction.image=file:resources/icons/save.png
+action.SaveAction.tooltip=Save segmentation context
+
+action.SaveAsAction.text=S&ave As...@Ctrl+Shift+S
+action.SaveAsAction.image=file:resources/icons/save-as.png
+action.SaveAsAction.tooltip=Save segmentation context as...
+
+action.ExportViewAction.text=&Current View...@Ctrl+Shift+V
+action.ExportViewAction.image=file:resources/icons/image.png
+action.ExportViewAction.tooltip=Export view as image
+
+action.ExportImageMapAction.text=&HTML Image Map...@Ctrl+Shift+H
+action.ExportImageMapAction.image=file:resources/icons/html.png
+action.ExportImageMapAction.tooltip=Export objects as HTML image map
+
+action.ExportTransparentPNGAction.text=Export Transparent &PNG...@Ctrl+Shift+P
+action.ExportTransparentPNGAction.image=file:resources/icons/image.png
+action.ExportTransparentPNGAction.tooltip=Export a PNG of the current segmentation with a transparent background
+
+
+action.ImportAction.text=&Import...
+action.ImportAction.image=file:resources/icons/import.png
+action.ImportAction.tooltip=Import images
+
+action.PrintAction.text=&Print@Ctrl+P
+action.PrintAction.image=file:resources/icons/print.png
+action.PrintAction.tooltip=Print view
+
+action.ExitAction.text=E&xit@Ctrl+Q
+action.ExitAction.image=file:resources/icons/exit.png
+action.ExitAction.tooltip=Quit the application
+
+action.UndoAction.text=&Undo@Ctrl+Z
+action.UndoAction.image=file:resources/icons/undo.png
+action.UndoAction.tooltip=Undo last markup
+
+action.RedoAction.text=&Redo@Ctrl+Y
+action.RedoAction.image=file:resources/icons/redo.png
+action.RedoAction.tooltip=Redo markup
+
+action.CopyAction.text=&Copy@Ctrl+C
+action.CopyAction.image=file:resources/icons/copy.png
+action.CopyAction.tooltip=Copy view to clipboard as image
+
+action.PreferencesAction.text=&Preferences...
+action.PreferencesAction.image=file:resources/icons/preferences.png
+action.PreferencesAction.tooltip=Change application settings
+
+action.AddFilesAction.text=&Add Files...@Ctrl+A
+action.AddFilesAction.image=file:resources/icons/add.png
+action.AddFilesAction.tooltip=Add image files to project
+
+action.RemoveFilesAction.text=&Remove Files@Ctrl+R
+action.RemoveFilesAction.image=file:resources/icons/remove.png
+action.RemoveFilesAction.tooltip=Remove selected image files from project
+
+action.PreviousAction.text=&Previous@Ctrl+[
+action.PreviousAction.image=file:resources/icons/previous.png
+action.PreviousAction.tooltip=Move to previous image in directory
+
+action.NextAction.text=&Next@Ctrl+]
+action.NextAction.image=file:resources/icons/next.png
+action.NextAction.tooltip=Move to next image in directory
+
+action.HelpAction.text=&Help Contents...@Ctrl+F1
+action.HelpAction.image=file:resources/icons/help.png
+action.HelpAction.tooltip=Show application help browser
+action.HelpAction.helpURL=http://kspace.cdvp.dcu.ie/public/interactive-segmentation/doc/help.html
+
+action.AboutAction.text=&About...
+action.AboutAction.image=file:resources/icons/about.png
+action.AboutAction.tooltip=About the application
+action.AboutAction.aboutImage=file:resources/icons/about-box.png
+
+action.OpenExperimentAction.text=Open &Experiment...
+action.OpenExperimentAction.image=file:resources/icons/experiment.png
+action.OpenExperimentAction.tooltip=Open an experiment file and switch to experiment mode
+
+action.ConfigureSegmenterAction.text=&Configure Segmenter...@Ctrl+Shift+C
+action.ConfigureSegmenterAction.image=file:resources/icons/preferences.png
+action.ConfigureSegmenterAction.tooltip=Configure segmenter parameters
+
+action.SelectSegmenterAction.NA=Algorithm not available on this platform
--- /dev/null
+
+AppRecentMenu.icon=file:resources/icons/open-recent.png
+ExportMenu.icon=file:resources/icons/export.png
+
+# Preferences Dialog
+PrefsDialog.title=Preferences
+PrefsDialog.application=Application
+PrefsDialog.status=Status Area
+PrefsDialog.view=View Preferences
+PrefsDialog.experiment=Experiment Mode
+PrefsDialog.general.tab=General
+PrefsDialog.close.text=&Close
+PrefsDialog.reset.text=&Reset
+PrefsDialog.enable-feedback.text=Show position and color in the status area
+PrefsDialog.foreground-color.text=Foreground color for markup
+PrefsDialog.background-color.text=Background color for markup
+PrefsDialog.experiment-embedded.text=Show experiment panel embedded in main window
+PrefsDialog.confirm-exit.text=Confirm exit when application is closed
+
+# Experiment Panel
+ExperimentPanel.description.title=Task Description
+ExperimentPanel.timeout-message=The time allocated for this task is up!
+ExperimentPanel.experiment-complete-message=The experiment has been completed
+ExperimentPanel.button.text.start=Start
+ExperimentPanel.button.text.finish=Finish
--- /dev/null
+
+AppRecentMenu.icon=file:resources/icons/open-recent.png
+ExportMenu.icon=file:resources/icons/export.png
+
+# Preferences Dialog
+PrefsDialog.title=Preferences
+PrefsDialog.application=Application
+PrefsDialog.status=Status Area
+PrefsDialog.view=View Preferences
+PrefsDialog.experiment=Experiment Mode
+PrefsDialog.general.tab=General
+PrefsDialog.close.text=&Close
+PrefsDialog.close.icon=file:resources/icons/close.png
+PrefsDialog.reset.text=&Reset
+PrefsDialog.reset.icon=file:resources/icons/reset.png
+PrefsDialog.enable-feedback.text=Show position and color in the status area
+PrefsDialog.foreground-color.text=Foreground color for markup
+PrefsDialog.background-color.text=Background color for markup
+PrefsDialog.experiment-embedded.text=Show experiment panel embedded in main window
+PrefsDialog.confirm-exit.text=Confirm exit when application is closed
+
+# Experiment Panel
+ExperimentPanel.description.title=Task Description
+ExperimentPanel.timeout-message=The time allocated for this task is up!
+ExperimentPanel.experiment-complete-message=The experiment has been completed
+ExperimentPanel.button.text.start=Start
+ExperimentPanel.button.text.finish=Finish
--- /dev/null
+SegmentationView.Action.Foreground.text=Markup Foreground
+SegmentationView.Action.Foreground.tooltip=Markup Foreground Pixels
+SegmentationView.Action.Foreground.image=file:resources/icons/foreground.png
+SegmentationView.Action.Background.text=Markup Background
+SegmentationView.Action.Background.tooltip=Markup Background Pixels
+SegmentationView.Action.Background.image=file:resources/icons/background.png
+SegmentationView.Action.ZoomIn.text=Zoom In
+SegmentationView.Action.ZoomIn.tooltip=Zoom In
+SegmentationView.Action.ZoomIn.image=file:resources/icons/zoom-in.png
+SegmentationView.Action.ZoomOut.text=Zoom Out
+SegmentationView.Action.ZoomOut.tooltip=Zoom Out
+SegmentationView.Action.ZoomOut.image=file:resources/icons/zoom-out.png
+SegmentationView.Action.ZoomOriginal.text=Zoom Original
+SegmentationView.Action.ZoomOriginal.tooltip=Zoom to Original Size
+SegmentationView.Action.ZoomOriginal.image=file:resources/icons/zoom-original.png
+SegmentationView.Action.ZoomBestFit.text=Zoom Best Fit
+SegmentationView.Action.ZoomBestFit.tooltip=Zoom to Best Fit
+SegmentationView.Action.ZoomBestFit.image=file:resources/icons/zoom-best-fit.png
+SegmentationView.Action.Repaint.text=Refresh
+SegmentationView.Action.Repaint.tooltip=Refresh and repaint view
+SegmentationView.Action.Repaint.image=file:resources/icons/refresh.png
+SegmentationView.Action.Undo.text=&Undo@Ctrl+Z
+SegmentationView.Action.Undo.tooltip=Undo last markup
+SegmentationView.Action.Undo.image=file:resources/icons/undo.png
+SegmentationView.Action.Redo.text=&Redo@Ctrl+Y
+SegmentationView.Action.Redo.tooltip=Redo markup
+SegmentationView.Action.Redo.image=file:resources/icons/redo.png
+SegmentationView.Action.Clear.text=&Clear@Ctrl+DELETE
+SegmentationView.Action.Clear.tooltip=Clear all markup
+SegmentationView.Action.Clear.image=file:resources/icons/clear.png
+SegmentationView.Action.SetBrushSize.text=Set &Brush@Ctrl+B
+SegmentationView.Action.SetBrushSize.tooltip=Set the markup brush size
+SegmentationView.Action.SetBrushSize.image=file:resources/icons/brush.png
+SegmentationView.Action.AutoApply.text=Auto Segment@Ctrl+Shift+A
+SegmentationView.Action.AutoApply.tooltip=Toggle auto segment
+SegmentationView.Action.AutoApply.image=file:resources/icons/auto.png
+SegmentationView.Action.Apply.text=Segment@Ctrl+Shift+S
+SegmentationView.Action.Apply.tooltip=Apply new markup now
+SegmentationView.Action.Apply.image=file:resources/icons/apply.png
+SegmentationView.Action.SetPainter.text=View :
+SegmentationView.Action.SetPainter.tooltip=Select how to view the image/segmentation.
+SegmentationView.Action.SetPainter.image=
+SegmentationView.Action.SegmenterOptions.text=Configure Segmenter@Ctrl+Shift+C
+SegmentationView.Action.SegmenterOptions.tooltip=Configure the segmenter
+SegmentationView.Action.SegmenterOptions.image=file:resources/icons/preferences.png
+SegmentationView.Action.SetLabel.text=Annotate :
+SegmentationView.Action.SetLabel.tooltip=Annotate the segmented piece.
+SegmentationView.Action.SetLabel.image=
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head><meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"><title>Interactive Segmentation Tool - User Guide</title><style type="text/css">
+.guicommand {
+ font-family: "Courier New",Courier,monospace;
+ font-weight: normal;
+ color: #990100;
+}
+.guishortcut {
+ font-family: "Courier New",Courier,monospace;
+ color: #666666;
+ font-style: italic;
+}
+.section {
+ font-weight: bold;
+ color: #404040;
+}
+.all {
+ margin: 0px;
+ padding: 10px 15px 50px;
+ background-color: white;
+}
+body {
+ margin: 0px 10px;
+ padding: 0px;
+ border-right-width: 1px;
+ border-left-width: 1px;
+ font-family: Arial,Helvetica,sans-serif;
+ border-right-style: dotted;
+ border-left-style: dotted;
+ background-color: #eff0ea;
+}
+h1 {
+ margin: 0px;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ padding-top: 10px;
+ padding-bottom: 15px;
+ text-align: center;
+ color: #404040;
+}
+h2 {
+ text-align: center;
+ color: #404040;
+}
+h3 {
+ color: #404040;
+}
+li {
+ padding-bottom: 10px;
+}
+.footer {
+ font-size: x-small;
+ text-align: center;
+}
+
+</style></head>
+<body>
+<div class="all">
+<h1>Interactive Segmentation Tool</h1>
+<h2>User Guide</h2>
+<h3>Usage</h3>
+In this section I will outline how to perform typical operations using
+the tool, such as opening images, extracting objects and exporting
+segmentation masks.<br>
+<br>
+<span class="section">Open an Image</span> <br>
+To open an image, select <span class="guicommand">File->Open</span><span class="guishortcut"></span>. Recently open files can
+be accessed using the <span class="guicommand">File->Open
+Recent</span> menu. Alternatively, an image file can be dragged
+from your file system and dropped into the application to open it.
+Currently images of type JPEG, PNG, GIF, and BMP are supported.<br>
+<br>
+<span class="section">Select a Segmenter</span><br>
+There are several different segmenters available in the tool. These can
+be selected from the <span class="guicommand">Tools</span>
+menu<span class="guicommand"></span>. To select one,
+click on it.<br>
+<br>
+<span class="section">Segmenting the Image </span><br class="section">
+Once you have an image open and a segmenter selected, you can segment
+the image by drawing scribbles on the image. First, move the mouse over
+the object you want to extract. Left click and drag inside the object
+to draw some scribbles inside the object. Now, move the mouse outside
+the object to some background pixels. Using the right mouse button,
+scribble outside the object to select some background. If using Mac
+OSX, or if you do not have a right mouse button (Graphics Tablet etc),
+you can use <span style="font-family: monospace;">Ctrl</span>
+and click instead of right click.<br>
+<br>
+The image will be segmented and the foreground will be brightened and
+the background darkened. If you are not happy with the segmentation,
+you can add more foreground and background scribbles.<br>
+<br>
+<span class="section">Undo, Redo and Clear</span><br>
+If you make a mistake, it can be undone by selecting <span class="guicommand">Edit->Undo</span><span style="font-weight: bold;"><span style="font-weight: bold;"></span> </span>or
+clicking the undo <img style="width: 16px; height: 16px;" alt="Undo Button" src="icons/undo.png"> button in the
+toolbar. To redo, select <span class="guicommand">Edit->Redo </span><span style="font-weight: bold;"></span>or click the
+redo <img style="width: 16px; height: 16px;" alt="Redo Button" src="icons/redo.png"> button. To
+remove all forground and background markup, click on the clear <img style="width: 16px; height: 16px;" alt="Clear Button" src="icons/reset.png"> button in the toolbar.<br>
+<br>
+<span class="section">Zooming</span><br>
+To get a closer look at the image, the toolbar can also be
+used to zoom-in on the image. To zoom-in by 10% use the <img style="width: 16px; height: 16px;" alt="Zoom In" src="icons/zoom-in.png"> button. To zoom-out by 10% use
+the <img style="width: 16px; height: 16px;" alt="Zoom Out" src="icons/zoom-out.png"> button. To
+zoom the image such that it fits into the window without needing
+scrollbars, use the <img style="width: 16px; height: 16px;" alt="Zoom Best Fit" src="icons/zoom-best-fit.png">
+button. To restore the image to its original size use the <img style="width: 16px; height: 16px;" alt="Zoom Original" src="icons/zoom-original.png"> button.<br>
+<br>
+<span class="section">Brush Size</span><br>
+To change the brush size, use the <img style="width: 16px; height: 16px;" alt="Paintbrush" src="icons/brush.png"> button to show the brush size
+chooser. It is often useful to use a bigger brush to make markings more
+visible or to quickly mark up more pixels.<br>
+<br>
+<span class="section">Views</span><br>
+It is sometimes useful to view the segmentation results in different
+ways. The default selected view is called "Combined" and shows the
+image, it's segmentation highlighted and the scribbles all overlayed on
+the same image. By using the drop down menu, you can select a different
+view:<br>
+<ul>
+<li><span style="font-style: italic;">Combined</span>:
+Shows the image with foreground areas brightened and background areas
+darkened. The foreground and background scribbles are overlayed.</li>
+<li><span style="font-style: italic;">Original:</span>
+Shows the original image, without any segmentation or scribbles
+overlayed.</li>
+<li><span style="font-style: italic;">Markup:</span>
+Shows only the foreground and background markup (scribbles).</li>
+<li><span style="font-style: italic;">Mask:</span>
+Shows the segmentation mask. That is, an image such that the foreground
+area is completely white and the background area completely black.</li>
+<li><span style="font-style: italic;">Foreground
+Only:</span> Removes all background elements and displays only
+the foreground object.</li>
+<li><span style="font-style: italic;">Outline
+Overlayed: </span>Displays and outline of the object overlayed
+onto the original image.</li>
+</ul>
+<strong>Note:</strong><span style="font-weight: bold;">
+</span>Only the combined view displays the foreground and
+background markup (scribbles). So if you are wondering where the
+scribbles disappeared to, select the combined view to make them visible.<br>
+<br class="section">
+<span class="section">Saving and Exporting</span><br>
+To save an work in progress segmentation, select <span class="guicommand">File->Save</span> or <span class="guicommand">File->Save As</span>. This
+will save the current segmentation and markup as a context file (<span style="font-family: monospace;">.ctx</span>). When
+finished segmenting an image, you may want to export the view as an
+image. To export the current view select <span class="guicommand">File->Export</span>.
+For example, if you wanted to save the segmentation mask, select the
+mask view and then <span class="guicommand">File->Export</span>.
+<br>
+<br class="section">
+<span class="section">Configuring a Segmenter</span><br>
+Some segmenters have parameters and options that can are configurable.
+To configure the selected segmenter, select <span class="guicommand">Tools->Configure
+Segmenter</span>, or click the <img style="width: 16px; height: 16px;" alt="Configure" src="icons/configure.png"> button in the toolbar.<br>
+<br class="section">
+<span class="section">Navigating Directories of Images</span><br>
+To quickly jump to the next or previous image in the current directory
+(the one containing the open image), use the <span class="guicommand">Go->Next</span> and <span class="guicommand">Go->Previous</span> menu
+items.<br>
+<br>
+<span class="section">Experiments</span><br class="section">
+Experiment files can be opened using the <span class="guicommand">Tools->Open
+Experiment</span> menu item.<br><br><br>
+<div class="footer">Kevin McGuinness 2008
+</div>
+</div>
+</body></html>
\ No newline at end of file
--- /dev/null
+ist (1.3.4-1) stable; urgency=low
+
+ * Support for Mac OS X leopard (64 bit JVM)
+
+ * Fixed bug in transparent PNG export on Windows platforms
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Thur, 27 Mar 2010 19:10:00 +0000
+
+ist (1.3.3-1) stable; urgency=low
+
+ * Updated API documentation
+
+ * Larger dialog for segmenter options
+
+ * Linux plugin builds now use GCC-4.1 for better compatibility
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 7 Oct 2009 19:00:00 +0000
+
+ist (1.3.2-1) stable; urgency=low
+
+ * Moved more components to the API layer. This includes painters and the image
+ control. This allows other apps to share core components.
+
+ * Added export transparent PNG option
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 7 Oct 2009 18:00:00 +0000
+
+ist (1.3.1-1) stable; urgency=low
+
+ * Updated export HTML feature to export XHTML
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 2 Sep 2009 18:00:00 +0000
+
+ist (1.3.0-1) stable; urgency=low
+
+ * Major restructuring of application to ease plugin development
+
+ * New version of SWT
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Mon, 24 Aug 2009 18:00:00 +0000
+
+ist (1.2.5-1) stable; urgency=low
+
+ * Added code for automated evaluation
+
+ * Update man page and run templates
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Mon, 9 Mar 2009 16:21:00 +0000
+
+ist (1.2.4-1) stable; urgency=medium
+
+ * Fixed erratic behavior of SIOX segmenter
+
+ * Added a lot of code for automated evaluation
+
+ * Added option to force segmenters to retain all markup pixels in mask.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Fri, 23 Jan 2009 18:00:00 +0000
+
+ist (1.2.3-1) stable; urgency=medium
+
+ * Fixed redo bug (redo was calling undo)
+
+ * Segmentation context does not create an Image unless asked to now. This
+ prevents it initializing the swt Display object unless it needs to, allowing
+ console applications that use the core segmentation stuff to be created
+ without initializing the Display.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Thu, 8 Jan 2009 15:00:00 +0000
+
+ist (1.2.2-1) stable; urgency=low
+
+ * Fixed application name in menu bar on OS X
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 6 Jan 2009 15:00:00 +0000
+
+ist (1.2.1-1) stable; urgency=low
+
+ * Use command key instead of ctrl key for shortcuts on Mac OSX
+
+ * Zip file for app bundle on OS X: it's better supported than tar.gz
+
+ * Modified about box to display SWT version
+
+ * Added standard Command+, binding for preferences on Mac
+
+ * Use small icons in menus on all platforms
+
+ * Removed icons in preference dialog buttons on Mac OS
+
+ * Improved and included several new icons
+
+ * Fixed visual bug on linux: horizontal separators didn't look right in
+ the export dialog.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Wed, 17 Dec 2008 17:00:00 +0000
+
+ist (1.2.0-1) stable; urgency=low
+
+ * New feature: export to HTML image maps integrated. Supports exporting
+ polygons and rectangles for now. Can export several rollover effects using
+ the javascript image swapping technique.
+
+ * Fixed bug on Mac OS X: application did not always exit properly
+
+ * Open browser window to show the HTML page when export completes
+
+ * Option to turn on or off the open browser window behavior
+
+ * Best practices: use var for global image preloads.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 16 Dec 2008 21:37:00 +0000
+
+ist (1.1.0-1) stable; urgency=low
+
+ * Several new features are planned for introduction, so the version number
+ has been incremented to 1.1
+
+ * Changed context file format. This is now stored as a zip archive including
+ the image, mask and annotations, so that it is more "self-contained". The
+ new file format is NOT compatible with the old one.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 29 Feb 2008 12:25:00 +0000
+
+ist (1.0.6-1) stable; urgency=low
+
+ * Added startup check for gdiplus.dll for platforms where it's needed but not
+ installed by default. Program will now show a useful message and exit gracefully
+ instead of spewing stack traces on the console and behaving oddly.
+
+ * Fixed bug that caused the auto-apply toggle button to be always disabled
+ after first image load.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 28 Feb 2008 10:36:00 +0000
+
+ist (1.0.5-1) stable; urgency=medium
+
+ * Fixed bug that caused the brush control (and some other toolbar buttons) to be
+ disabled after an experiment was cancelled.
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 12 Feb 2008 13:25:00 +0000
+
+ist (1.0.4-1) stable; urgency=low
+
+ * Updated the integrated help file (help.html)
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 28 Jan 2008 16:40:00 +0000
+
+ist (1.0.3-1) stable; urgency=low
+
+ * MacOSX PowerPC now supported
+
+ * Mostly non-functional changes on Linux, refactoring and the like
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 25 Jan 2008 16:08:00 +0000
+
+ist (1.0.2-1) stable; urgency=low
+
+ * Application icon added
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 16 Jan 2008 19:43:00 +0000
+
+ist (1.0.1-1) stable; urgency=low
+
+ * Created debian package
+
+ * Support added for 64 bit linux
+
+ -- Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie> Tue, 15 Jan 2008 19:43:00 +0000
--- /dev/null
+ist
+
+Copyright: Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie>
+ Center for Digital Video Processing
+ Dublin City University
+
+2008-01-14
+
+The home page of ist is at:
+https://www.kspace.cdvp.dcu.ie/public/interactive-segmentation
--- /dev/null
+# freedesktop.org desktop entry
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Interactive Segmentation Tool
+Comment=Interactively extract objects from images using various algorithms
+Icon=ist
+Exec=ist
+Terminal=false
+Categories=GTK;Graphics
--- /dev/null
+#!/bin/sh
+
+# Setup
+data=@data@
+main=@main@
+jars=@jars@
+
+# Change directory
+cd "${data}"
+
+# This is needed for the browser widget on linux
+if [ -z "${MOZILLA_FIVE_HOME}" ]; then
+ # Check if common locations exist
+ if [ -d "/usr/lib/firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/firefox
+ elif [ -d "/usr/lib/mozilla-firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla-firefox
+ elif [ -d "/usr/lib/mozilla" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla
+ fi
+fi
+
+# Set library path
+if [ -n "${MOZILLA_FIVE_HOME}" ]; then
+ export LD_LIBRARY_PATH=${MOZILLA_FIVE_HOME}:${LD_LIBRARY_PATH}
+fi
+
+# Launch
+if [ -n "${JAVA_HOME}" ]; then
+ ${JAVA_HOME}/bin/java -classpath "${jars}" ${main} $*
+else
+ java -classpath "${jars}" ${main} $*
+fi
+
+cd "$OLDPWD"
+
--- /dev/null
+.TH "ist" "1" "1.3.3" "Kevin McGuinness" "Graphics"
+.SH "NAME"
+.LP
+ist \- Interactive Segmentation Tool
+.SH "SYNTAX"
+.LP
+ist [options]
+
+.SH "DESCRIPTION"
+.LP
+Launches the interactive segmentation tool graphical user interface
+
+.SH "ENVIRONMENT VARIABLES"
+.LP
+.TP
+\fBMOZILLA_FIVE_HOME\fP
+Specifies the location of the mozilla 5 browser
+(firefox etc.). This is usually something like
+/usr/lib/firefox
+
+.SH "EXAMPLES"
+.LP
+To run this program the standard way type:
+.LP
+$ ist
+
+.SH "AUTHORS"
+.LP
+Kevin McGuinness <kevin.mcguinness@eeng.dcu.ie>
--- /dev/null
+#!/bin/sh
+
+sudo update-desktop-database
+sudo gtk-update-icon-cache /usr/share/icons/hicolor
--- /dev/null
+#!/bin/sh
+
+sudo update-desktop-database
+sudo gtk-update-icon-cache /usr/share/icons/hicolor
--- /dev/null
+#!/bin/sh
+
+jars=istapp.jar:istapi.jar:swt.jar:jface.jar
+main=ie.dcu.apps.ist.Main
+
+# Change directory
+cd "`dirname "$0"`"
+
+# This is needed for the SWT browser widget on linux
+if [ -z "${MOZILLA_FIVE_HOME}" ]; then
+
+ # Check if common locations exist
+ if [ -d "/usr/lib/firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/firefox
+ elif [ -d "/usr/lib/mozilla-firefox" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla-firefox
+ elif [ -d "/usr/lib/mozilla" ]; then
+ export MOZILLA_FIVE_HOME=/usr/lib/mozilla
+ fi
+
+fi
+
+# Set library path
+if [ -n "${MOZILLA_FIVE_HOME}" ]; then
+ export LD_LIBRARY_PATH=${MOZILLA_FIVE_HOME}:${LD_LIBRARY_PATH}
+fi
+
+if [ -n "${JAVA_HOME}" ]; then
+ ${JAVA_HOME}/bin/java -classpath "${jars}" ${main} $*
+else
+ java -classpath "${jars}" ${main} $*
+fi
+
+cd "$OLDPWD"
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleName</key>
+ <string>@name@</string>
+ <key>CFBundleShortVersionString</key>
+ <string>@version@</string>
+ <key>CFBundleGetInfoString</key>
+ <string>@synopsis@ (v@version@)</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <string>false</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleExecutable</key>
+ <string>@exename@</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleIconFile</key>
+ <string>@icons@</string>
+ </dict>
+</plist>
--- /dev/null
+#!/bin/sh
+
+# What kinda mac are we on
+CPU=`uname -p`
+
+# Absolute path to where the app was launched from
+APP_PACKAGE="`dirname "$0"`/../.."
+APP_PACKAGE="`cd "${APP_PACKAGE}"; pwd`"
+
+# Locations of resources, jars, and libs
+LIB_DIR="Libraries/${CPU}"
+
+# Move to the resource directory
+cd "${APP_PACKAGE}/Contents/Resources"
+
+# Establish whether we should use the 32 bit or 64 bit cocoa library
+SWT_LIBNAME=swt-cocoa-32.jar
+java -version 2>&1 | grep "64-Bit" > /dev/null
+if [ "$?" -eq "0" ]; then
+ SWT_LIBNAME=swt-cocoa-64.jar
+fi
+
+echo "SWT Library: ${SWT_LIBNAME}"
+
+# Launch app
+java \
+ -XstartOnFirstThread \
+ -classpath "Java/istapp.jar:Java/istapi.jar:Java/jface.jar:Java/${SWT_LIBNAME}" \
+ -Dorg.eclipse.swt.internal.carbon.noFocusRing \
+ -Dorg.eclipse.swt.internal.carbon.smallFonts \
+ @appmain@ $*
+
+# Go back to the old working directory
+cd "${OLDPWD}"
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1" standalone="yes" ?>
+
+<!--
+ IzPack install file for the Interactive Segmentation Tool
+-->
+
+<installation version="1.0">
+
+ <!-- The info section. -->
+ <info>
+ <appname>@name@</appname>
+ <appversion>@version@</appversion>
+ <authors>
+ <author name="@author@" email="@email@"/>
+ </authors>
+ <url>@website@</url>
+ <javaversion>1.5</javaversion>
+ </info>
+
+ <!-- The gui preferences indication. -->
+ <guiprefs width="640" height="480" resizable="no"/>
+
+ <!-- The locale section. -->
+ <locale>
+ <langpack iso3="eng"/>
+ </locale>
+
+ <!-- The resources section. -->
+ <resources>
+ <res src="resources/plaf/windows/shortcuts.xml" id="shortcutSpec.xml"/>
+ </resources>
+
+ <!-- Shell-link for windows. -->
+ <native type="izpack" name="ShellLink.dll" />
+
+ <!-- The panels section. -->
+ <panels>
+ <panel classname="HelloPanel"/>
+ <panel classname="TargetPanel"/>
+ <panel classname="PacksPanel"/>
+ <panel classname="InstallPanel"/>
+ <panel classname="ShortcutPanel"/>
+ <panel classname="FinishPanel"/>
+ </panels>
+
+ <!-- The packs section. -->
+ <packs>
+ <pack name="Base" required="yes">
+ <description> Base installation files </description>
+ <fileset dir="build/packages/windows" targetdir="${INSTALL_PATH}" >
+ <include name="**/*" />
+ </fileset>
+ </pack>
+ </packs>
+
+</installation>
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+ <JVMSearchPath>registry</JVMSearchPath>
+ <JVMSearchPath>javahome</JVMSearchPath>
+ <JVMSearchPath>jrepath</JVMSearchPath>
+ <JVMSearchPath>jdkpath</JVMSearchPath>
+ <JVMSearchPath>exepath</JVMSearchPath>
+ <JVMSearchPath>jview</JVMSearchPath>
+ <jarLocation>@input@</jarLocation>
+ <iconLocation>@icon@</iconLocation>
+ <currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+ <embeddedJar>true</embeddedJar>
+ <executableName>@output@</executableName>
+ <initialMemoryHeap>-1</initialMemoryHeap>
+ <mainClassName>com.izforge.izpack.installer.Installer</mainClassName>
+ <maximumMemoryHeap>-1</maximumMemoryHeap>
+ <maximumVersion></maximumVersion>
+ <minimumVersion>1.5</minimumVersion>
+ <skeletonName>Windowed Wrapper</skeletonName>
+ <skeletonProperties>
+ <key>Message</key>
+ <value>Java has not been found on your computer.
+ Do you want to download it?</value>
+ </skeletonProperties>
+ <skeletonProperties>
+ <key>Debug</key>
+ <value>0</value>
+ </skeletonProperties>
+</jsmoothproject>
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothproject>
+ <JVMSearchPath>registry</JVMSearchPath>
+ <JVMSearchPath>javahome</JVMSearchPath>
+ <JVMSearchPath>jrepath</JVMSearchPath>
+ <JVMSearchPath>jdkpath</JVMSearchPath>
+ <JVMSearchPath>exepath</JVMSearchPath>
+ <JVMSearchPath>jview</JVMSearchPath>
+ <classPath>istapp.jar</classPath>
+ <classPath>istapi.jar</classPath>
+ <classPath>swt-win.jar</classPath>
+ <classPath>jface.jar</classPath>
+ <currentDirectory>${EXECUTABLEPATH}</currentDirectory>
+ <embeddedJar>false</embeddedJar>
+ <executableName>ist.exe</executableName>
+ <iconLocation>application.ico</iconLocation>
+ <initialMemoryHeap>-1</initialMemoryHeap>
+ <mainClassName>ie.dcu.apps.ist.Main</mainClassName>
+ <maximumMemoryHeap>-1</maximumMemoryHeap>
+ <minimumVersion>1.5.0</minimumVersion>
+ <skeletonName>Windowed Wrapper</skeletonName>
+ <skeletonProperties>
+ <key>Message</key>
+ <value>This program needs Java to run.
+ Please download it at http://www.java.com</value>
+ </skeletonProperties>
+ <skeletonProperties>
+ <key>Debug</key>
+ <value>0</value>
+ </skeletonProperties>
+</jsmoothproject>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\r
+<shortcuts>\r
+ <skipIfNotSupported/>\r
+ <programGroup defaultName="IST" location="applications"/>\r
+ \r
+ <shortcut\r
+ name="Interactive Segmentation Tool"\r
+ target="$INSTALL_PATH\ist.exe"\r
+ workingDirectory="$INSTALL_PATH"\r
+ description="Launch the Interactive Segmentation Tool"\r
+ initialState="normal"\r
+ programGroup="yes"\r
+ desktop="yes"\r
+ applications="no"\r
+ startMenu="no"\r
+ startup="no"\r
+ >\r
+ <createForPack name="Base"/>\r
+ </shortcut>\r
+\r
+ <shortcut\r
+ name="Uninstall"\r
+ target="${JAVA_HOME}\bin\java.exe"\r
+ commandLine="-jar "$INSTALL_PATH/Uninstaller/uninstaller.jar""\r
+ description="Uninstall Interactive Segmentation Tool"\r
+ initialState="noShow"\r
+ programGroup="yes"\r
+ desktop="no"\r
+ applications="no"\r
+ startMenu="no"\r
+ startup="no"\r
+ >\r
+ <createForPack name="Base"/>\r
+ </shortcut>\r
+\r
+</shortcuts>\r
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<jsmoothskeleton>
+<debug>false</debug>
+<description>SKEL_SIMPLEWRAPPER_DESCRIPTION</description>
+<executableName>jwrap.exe</executableName>
+<resourceCategory>JAVA</resourceCategory>
+<resourceJarId>102</resourceJarId>
+<resourcePropsId>103</resourcePropsId>
+<shortName>Windowed Wrapper</shortName>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_DESCRIPTION</description>
+<idName>Message</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_MESSAGE_LABEL</label>
+<type>textarea</type>
+<value>Java has not been found on your computer. Do you want to download it?</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_SIMPLEWRAPPER_PROPERTY_URL_DESCRIPTION</description>
+<idName>URL</idName>
+<label>SKEL_SIMPLEWRAPPER_PROPERTY_URL_LABEL</label>
+<type>string</type>
+<value>http://www.java.com</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_DESCRIPTION</description>
+<idName>SingleProcess</idName>
+<label>SKEL_GENERIC_PROPERTY_SINGLEPROCESS_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_SINGLEINSTANCE_DESCRIPTION</description>
+<idName>SingleInstance</idName>
+<label>SKEL_GENERIC_SINGLEINSTANCE</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_JNISMOOTH_DESCRIPTION</description>
+<idName>JniSmooth</idName>
+<label>SKEL_GENERIC_JNISMOOTH</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+<skeletonProperties>
+<description>SKEL_GENERIC_PROPERTY_DEBUG_DESCRIPTION</description>
+<idName>Debug</idName>
+<label>SKEL_GENERIC_PROPERTY_DEBUG_LABEL</label>
+<type>boolean</type>
+<value>0</value>
+</skeletonProperties>
+</jsmoothskeleton>
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.swt.SwtUtils;
+
+import java.util.*;
+import java.util.logging.Logger;
+import java.util.prefs.*;
+
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Manages application preferences
+ *
+ * @author Kevin McGuinness
+ */
+public class AppPrefs {
+
+ public interface Keys {
+ public static final String ENABLE_FEEDBACK = "enable-feedback";
+ public static final String FOREGROUND_COLOR = "foreground-color";
+ public static final String BACKGROUND_COLOR = "background-color";
+ public static final String EXPERIMENT_EMBEDDED = "experiment-embedded";
+ public static final String CONFIRM_EXIT = "confirm-exit";
+ }
+
+ public interface SupportedTypes {
+ public static final Class<Integer> INTEGER = Integer.class;
+ public static final Class<Double> DOUBLE = Double.class;
+ public static final Class<Float> FLOAT = Float.class;
+ public static final Class<Short> SHORT = Short.class;
+ public static final Class<Byte> BYTE = Byte.class;
+ public static final Class<Long> LONG = Long.class;
+ public static final Class<String> STRING = String.class;
+ public static final Class<Boolean> BOOLEAN = Boolean.class;
+ public static final Class<RGB> RGB = RGB.class;
+ }
+
+
+ private final Logger log;
+ private final Preferences prefs;
+ private final List<PreferenceChangeListener> listeners;
+
+
+ public AppPrefs() {
+ this.log = Logger.getLogger(getClass().getName());
+ this.prefs = Preferences.userRoot().node(Application.APP_ID);
+ this.listeners = new LinkedList<PreferenceChangeListener>();
+
+ addSwtThreadAdaptionListener();
+ }
+
+
+ private void addSwtThreadAdaptionListener() {
+ prefs.addPreferenceChangeListener(new PreferenceChangeListener() {
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ firePreferenceChangedSynced(evt);
+ }
+ });
+ }
+
+
+ private void firePreferenceChangedSynced(final PreferenceChangeEvent evt) {
+
+ // Delegate to event SWT event dispatching thread
+ Display.getDefault().syncExec(new Runnable() {
+ public void run() {
+ firePreferenceChanged(evt);
+ }
+ });
+
+ }
+
+
+ private void firePreferenceChanged(PreferenceChangeEvent evt) {
+ for (PreferenceChangeListener listener : listeners) {
+ listener.preferenceChange(evt);
+ }
+ }
+
+
+ public void sync() {
+ try {
+ prefs.sync();
+ } catch (BackingStoreException e) {
+ log.warning("Error syncing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public void flush() {
+ try {
+ prefs.flush();
+ } catch (BackingStoreException e) {
+ log.warning("Error writing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public void clear() {
+ try {
+ prefs.clear();
+ } catch (BackingStoreException e) {
+ log.warning("Error clearing preferences: " + e.getMessage());
+ }
+ }
+
+
+ public String[] keys() {
+ try {
+ return prefs.keys();
+ } catch (BackingStoreException e) {
+ log.warning("Error retrieving preference keys: " + e.getMessage());
+ }
+ return new String[0];
+ }
+
+
+ public void remove(String key) {
+ prefs.remove(key);
+ }
+
+
+ public <T> T get(Class<T> clazz, String key, T def) {
+ String value = (def != null) ?
+ get(key, def.toString()) : get(key, null);
+
+ return decode(clazz, value, def);
+ }
+
+
+ public <T> void put(Class<T> clazz, String key, T value) {
+ prefs.put(key, encode(clazz, value));
+ }
+
+
+ public void put(String key, String value) {
+ prefs.put(key, value);
+ }
+
+
+ public String get(String key, String def) {
+ return prefs.get(key, def);
+ }
+
+
+ public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
+ listeners.add(pcl);
+ }
+
+
+ public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
+ listeners.remove(pcl);
+ }
+
+
+ public <T> String encode(Class<T> clazz, T value) {
+ if (clazz.equals(RGB.class)) {
+ return encodeRGB((RGB) value);
+ } else if (value != null) {
+ return String.valueOf(value);
+ } else {
+ return null;
+ }
+ }
+
+
+ public <T> T decode(Class<T> clazz, String str, T def) {
+ if (str == null) {
+ return def;
+ } else if (isNumeric(clazz)) {
+ return clazz.cast(decodeNumeric(clazz, str, def));
+ } else if (isString(clazz)) {
+ return clazz.cast(str);
+ } else if (isBoolean(clazz)) {
+ return clazz.cast(decodeBoolean(str, (Boolean) def));
+ } else if (clazz.equals(RGB.class)) {
+ return clazz.cast(decodeRGB(str, (RGB) def));
+ } else {
+ log.warning("Unable to decode class: " + clazz.getName());
+ }
+
+ return def;
+ }
+
+
+ private boolean isString(Class<?> clazz) {
+ return clazz.equals(String.class);
+ }
+
+
+ private boolean isBoolean(Class<?> clazz) {
+ return clazz.equals(Boolean.class);
+ }
+
+
+
+ private boolean isNumeric(Class<?> clazz) {
+ return
+ clazz.equals(Integer.class) ||
+ clazz.equals(Double.class) ||
+ clazz.equals(Float.class) ||
+ clazz.equals(Short.class) ||
+ clazz.equals(Byte.class) ||
+ clazz.equals(Long.class);
+ }
+
+
+ private RGB decodeRGB(String str, RGB def) {
+ if (str.startsWith("#")) {
+ try {
+ int hex = Integer.parseInt(str.substring(1), 16);
+ return SwtUtils.int2rgb(hex);
+ } catch (NumberFormatException e) {
+ log.warning("Not a valid color: " + str);
+ }
+ }
+ return def;
+ }
+
+
+ private String encodeRGB(RGB rgb) {
+ return String.format("#%06x", SwtUtils.rgb2int(rgb));
+ }
+
+
+ private boolean decodeBoolean(String str, boolean def) {
+ return str.equalsIgnoreCase("true") ||
+ (str.equalsIgnoreCase("false") ? false : def);
+ }
+
+
+ private <T> T decodeNumeric(Class<T> clazz, String s, T def) {
+ try {
+ if (clazz.equals(Integer.class)) {
+ return clazz.cast(Integer.parseInt(s));
+
+ } else if (clazz.equals(Double.class)) {
+ return clazz.cast(Double.parseDouble(s));
+
+ } else if (clazz.equals(Float.class)) {
+ return clazz.cast(Float.parseFloat(s));
+
+ } else if (clazz.equals(Short.class)) {
+ return clazz.cast(Short.parseShort(s));
+
+ } else if (clazz.equals(Byte.class)) {
+ return clazz.cast(Byte.parseByte(s));
+
+ } else if (clazz.equals(Long.class)) {
+ return clazz.cast(Long.parseLong(s));
+
+ }
+ } catch (NumberFormatException ex) {
+ log.warning("Invalid numeric value in preferences: " + s);
+ }
+ return def;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import static ie.dcu.segment.annotate.AnnotationType.*;
+import static ie.dcu.apps.ist.AppPrefs.Keys.*;
+import static ie.dcu.apps.ist.AppPrefs.SupportedTypes.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.apps.ist.AppPrefs.*;
+
+import java.util.prefs.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Manages updating various components when preferences change.
+ *
+ * @author Kevin McGuinness
+ */
+class AppPrefsManager implements PreferenceChangeListener {
+
+ private final AppWindow window;
+ private final ColorRegistry colorRegistry;
+ private final AppPrefs prefs;
+
+
+ public AppPrefsManager(AppWindow window) {
+ this.window = window;
+ this.colorRegistry = JFaceResources.getColorRegistry();
+ this.prefs = loadPreferences();
+ }
+
+
+ private AppPrefs loadPreferences() {
+ AppPrefs prefs = new AppPrefs();
+ prefs.addPreferenceChangeListener(this);
+ return prefs;
+ }
+
+
+ public AppPrefs getPrefs() {
+ return prefs;
+ }
+
+
+ public void update() {
+ updateFeedbackEnabled();
+ updateBackgroundColor();
+ updateForegroundColor();
+ updateExperimentEmbedded();
+ }
+
+
+ public void updateExperimentEmbedded() {
+ boolean on = prefs.get(BOOLEAN, Keys.EXPERIMENT_EMBEDDED, false);
+ window.setExperimentModeEmbedded(on);
+ }
+
+
+ public void updateFeedbackEnabled() {
+ boolean on = prefs.get(BOOLEAN, Keys.ENABLE_FEEDBACK, true);
+ window.setEnableFeedback(on);
+ }
+
+
+ public void updateForegroundColor() {
+ RGB color = prefs.get(RGB, Keys.FOREGROUND_COLOR, DEFAULT_FOREGROUND);
+ colorRegistry.put(AnnotationType.Foreground.key, color);
+ window.getView().repaint();
+ }
+
+
+ public void updateBackgroundColor() {
+ RGB color = prefs.get(RGB, Keys.BACKGROUND_COLOR, DEFAULT_BACKGROUND);
+ colorRegistry.put(AnnotationType.Background.key, color);
+ window.getView().repaint();
+ }
+
+
+ public void preferenceChange(PreferenceChangeEvent evt) {
+ String pref = evt.getKey();
+
+ if (pref.equals(ENABLE_FEEDBACK)) {
+ updateFeedbackEnabled();
+
+ } else if (pref.equals(FOREGROUND_COLOR)) {
+ updateForegroundColor();
+
+ } else if (pref.equals(BACKGROUND_COLOR)) {
+ updateBackgroundColor();
+
+ } else if (pref.equals(EXPERIMENT_EMBEDDED)) {
+ updateExperimentEmbedded();
+ }
+
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.recent.*;
+
+import java.io.*;
+
+/**
+ * Manages applications recent files list.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppRecentFiles extends RecentFiles {
+ public static final String APP_SETTINGS_PATH = ".ist";
+ public static final String HISTORY_FILE_NAME = ".history";
+
+ private static AppRecentFiles history;
+
+
+ private AppRecentFiles(File store) {
+ super(store);
+ }
+
+
+ public static AppRecentFiles getInstance() {
+ if (history == null) {
+ history = create();
+ }
+ return history;
+ }
+
+
+ private static AppRecentFiles create() {
+ AppRecentFiles history = null;
+ try {
+ history = new AppRecentFiles(getHistoryFile());
+ history.load();
+ } catch (IOException e) {
+ getLogger().warning("Unable to create history file: " + e);
+ }
+
+ return history;
+ }
+
+
+ @Override
+ public void load() {
+ try {
+ super.load();
+ } catch (IOException e) {
+ getLogger().warning("Error loading recent documents list: " + e);
+ }
+ }
+
+ @Override
+ public void store() {
+ try {
+ super.store();
+ } catch (IOException e) {
+ getLogger().warning("Error storing recent documents list: " + e);
+ }
+ }
+
+
+ private static File getHistoryFile() throws IOException {
+ File file = new File(getSettingsDirectory(), HISTORY_FILE_NAME);
+ if (file.exists()) {
+ // Check we can write
+ if (file.canWrite()) {
+ return file;
+ }
+ } else {
+ return file;
+ }
+
+ throw new IOException("History file is unwritable");
+ }
+
+
+ private static File getSettingsDirectory() throws IOException {
+ String home = System.getProperty("user.home");
+ File settings = new File(home, APP_SETTINGS_PATH);
+
+ if (!settings.exists()) {
+ if (settings.mkdirs()) {
+ return settings;
+ }
+ } else {
+ return settings;
+ }
+
+ throw new IOException("Unable to create application settings directory");
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.actions.ActionManager;
+import ie.dcu.apps.ist.actions.HoverMenuManager;
+import ie.dcu.apps.ist.actions.OpenAction;
+import ie.dcu.apps.ist.actions.OpenRecentAction;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+/**
+ * Recent files menu.
+ *
+ * @author Kevin McGuinness
+ */
+class AppRecentMenu extends HoverMenuManager {
+ private final AppRecentFiles history;
+ private final EmptyAction empty;
+ private final AppWindow window;
+ private IAction action;
+
+ public AppRecentMenu(AppWindow window, IMenuManager parent) {
+ super("Open &Recent");
+
+ this.window = window;
+ setParent(parent);
+ history = AppRecentFiles.getInstance();
+ add(empty = new EmptyAction());
+ addMenuListener(menuListener);
+
+ ActionManager actions = window.getActions();
+ if (actions != null) {
+ OpenAction openAction = actions.get(OpenAction.class);
+ openAction.addPropertyChangeListener(actionListener);
+ action = openAction;
+ }
+
+ try {
+ Properties props = window.getProperties();
+ String icon = props.getProperty("AppRecentMenu.icon",
+ "file:icons/open-recent.png");
+ setImageURL(new URL(icon));
+ } catch (MalformedURLException e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public void fill(Menu parent, int index) {
+ super.fill(parent, index);
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setEnabled(action.isEnabled());
+ }
+ }
+
+
+ private void buildMenu() {
+ removeAll();
+
+ if (history.empty()) {
+ add(empty);
+ } else {
+ for (File file : history) {
+ add(new OpenRecentAction(window.getActions(), file));
+ }
+ }
+
+ update(false);
+ }
+
+
+ private final class EmptyAction extends Action {
+ public EmptyAction() {
+ setText("No Recent Files");
+ setEnabled(false);
+ }
+ };
+
+
+ private final IMenuListener menuListener = new IMenuListener() {
+ public void menuAboutToShow(IMenuManager manager) {
+ buildMenu();
+ }
+ };
+
+
+ private final IPropertyChangeListener actionListener
+ = new IPropertyChangeListener() {
+
+ public void propertyChange(PropertyChangeEvent event) {
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setEnabled(action.isEnabled());
+ }
+ }
+ };
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import java.net.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Application status bar message type.
+ *
+ * @author Kevin McGuinness
+ */
+public enum AppStatus {
+ Information, Warning, Error;
+
+ public Image getIcon() {
+ String key = key();
+ Image image = JFaceResources.getImage(key);
+ if (image == null) {
+ ImageRegistry registry = JFaceResources.getImageRegistry();
+ ImageDescriptor descriptor = createImageDescriptor();
+ registry.put(key, descriptor);
+ image = registry.get(key);
+ }
+ return image;
+ }
+
+ private ImageDescriptor createImageDescriptor() {
+ try {
+ return ImageDescriptor.createFromURL(new URL(key()));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String key() {
+ switch (this) {
+ case Information:
+ return "file:resources/icons/dialog-information.png";
+ case Warning:
+ return "file:resources/icons/dialog-warning.png";
+ default:
+ return "file:resources/icons/dialog-error.png";
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.apps.ist.actions.*;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.exp.Experiment;
+import ie.dcu.apps.ist.views.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.*;
+import ie.dcu.swt.layout.LayoutFactory;
+import ie.dcu.util.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.Properties;
+import java.util.logging.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+import org.eclipse.jface.window.ApplicationWindow;
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Main interactive segmentation tool application window.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppWindow extends ApplicationWindow implements FileDropListener {
+ private static final Logger log = Logger.getLogger("AppWindow");
+
+ private final Properties props;
+
+ private AppPrefsManager prefsManager;
+ private ActionManager actions;
+ private Composite content;
+ private SegmentationView view;
+ private Shell shell;
+ private ImageObserver observer;
+ private Experiment experiment;
+ private ExperimentPanel experimentPanel;
+ private boolean experimentModeEmbedded;
+
+
+ public AppWindow() {
+ super(null);
+
+ props = loadProperties();
+
+ addMenuBar();
+ addStatusLine();
+ }
+
+
+ @Override
+ protected boolean canHandleShellCloseEvent() {
+ if (!ExitAction.confirmExit(this)) {
+ return false;
+ }
+ return super.canHandleShellCloseEvent();
+ }
+
+
+ private Properties loadProperties() {
+ try {
+ return Application.loadProperties(getPropertiesFile());
+ } catch (IOException e) {
+ log.severe("Unable to load properties file " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getPropertiesFile() {
+ return OsUtils.isMacOS() ? "application.mac" : "application";
+ }
+
+
+ public void updateWindowTitle() {
+ String title;
+ if (hasContext()) {
+ String file = getContext().getFile().getName();
+ title = String.format("%s [%s]", Application.APP_NAME, file);
+
+ } else {
+ title = Application.APP_NAME;
+ }
+
+ if (isExperimentMode()) {
+ title += " - Experiment Mode";
+ }
+
+ getShell().setText(title);
+ }
+
+
+ private SegmentationView createView(Composite parent) {
+
+ // Load props
+ Properties props;
+ try {
+ props = Application.loadProperties("view");
+ } catch (IOException e) {
+ log.severe("Error loading view properties " + e.getLocalizedMessage());
+ throw new RuntimeException(e);
+ }
+
+ // Create view
+ SegmentationView view = new SegmentationView(props, parent, 0);
+
+ boolean blocksOnFork = false;
+
+ // Set runnable context
+ view.setRunnableContext(this, blocksOnFork);
+
+ // Add observer
+ observer = new ImageObserver(view);
+
+ // Return new view
+ return view;
+ }
+
+
+ private static ImageDescriptor createImageDescriptor(String url) {
+ try {
+ return ImageDescriptor.createFromURL(new URL(url));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private void updateEnabledActions() {
+ ActionManager a = getActions();
+ boolean ctxAvailable = hasContext();
+ boolean experimentMode = isExperimentMode();
+
+ a.setEnabled(OpenAction.class, !experimentMode);
+ a.setEnabled(PreferencesAction.class, !experimentMode);
+ a.setEnabled(SaveAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(SaveAsAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(ExportViewAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(ExportTransparentPNGAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(PrintAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(CopyAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(UndoAction.class, ctxAvailable && view.canUndo());
+ a.setEnabled(RedoAction.class, ctxAvailable && view.canRedo());
+ a.setEnabled(NextAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(PreviousAction.class, ctxAvailable && !experimentMode);
+ a.setEnabled(OpenExperimentAction.class, !experimentMode);
+
+ updateExportImageMapAction();
+
+ if (experimentMode) {
+
+ boolean changeSelection = false;
+
+ // Enable experiment segmenters
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ Segmenter segmenter = s.getSegmenter();
+ boolean enabled = experiment.using(segmenter);
+ s.setEnabled(enabled);
+
+ if (!s.isEnabled() && s.isChecked()) {
+ // The selected segmenter has been disabled,
+ // we need to select a different one
+ changeSelection = true;
+ s.setChecked(false);
+ }
+ }
+
+ if (changeSelection) {
+ // Select the first available segmenter
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ if (experiment.using(s.getSegmenter())) {
+ s.setChecked(true);
+ s.run();
+ break;
+ }
+ }
+ }
+
+ } else {
+ // Enable all select segmenter actions
+ for (SelectSegmenterAction s : a.getSegmentationActions()) {
+ s.setEnabled(true);
+ }
+ }
+ }
+
+ private void updateExportImageMapAction() {
+ ActionManager a = getActions();
+ SegmentationContext ctx = getContext();
+
+ if (ctx != null && !isExperimentMode()) {
+ AnnotationManager am = ctx.getAnnotations();
+ boolean hasObject = am.hasForegroundAnnotation()
+ && am.hasBackgroundAnnotation();
+ a.setEnabled(ExportImageMapAction.class, hasObject);
+
+ } else {
+ a.setEnabled(ExportImageMapAction.class, false);
+ }
+ }
+
+
+ private void updateUndoRedoActions() {
+ ActionManager a = getActions();
+ boolean ctxAvailable = hasContext();
+ a.setEnabled(UndoAction.class, ctxAvailable && view.canUndo());
+ a.setEnabled(RedoAction.class, ctxAvailable && view.canRedo());
+ }
+
+
+ @Override
+ protected void configureShell(Shell shell) {
+ this.shell = shell;
+ super.configureShell(shell);
+ updateWindowTitle();
+ loadIcons(shell);
+ shell.setSize(800, 600);
+ SwtUtils.center(shell);
+ }
+
+
+ private void loadIcons(Shell shell) {
+ final String[] icons = {
+ "resources/icons/icon-16.png",
+ "resources/icons/icon-24.png",
+ "resources/icons/icon-32.png",
+ "resources/icons/icon-48.png"
+ };
+
+ Image[] images = new Image[icons.length];
+ Display display = shell.getDisplay();
+
+ try {
+ int i = 0;
+ for (String file : icons) {
+ images[i++] = new Image(display, file);
+ }
+
+ shell.setImages(images);
+
+ } catch (SWTException e) {
+ log.warning("Error loading application window icons: "
+ + e.getLocalizedMessage());
+ }
+ }
+
+
+ @Override
+ protected Control createContents(Composite parent) {
+ content = new Composite(parent, SWT.NONE);
+
+ content.setLayout(LayoutFactory.createGridLayout(0, 0, 2, false));
+
+ view = createView(content);
+ view.setLayoutData(LayoutFactory.createGridData());
+ view.addContextChangeListener(contextListener);
+
+ SwtUtils.createFileDropTarget(view, this);
+
+ // Done creating contents
+ getPrefsManager().update();
+ updateEnabledActions();
+
+ return content;
+ }
+
+
+ @Override
+ protected boolean showTopSeperator() {
+ return false;
+ }
+
+
+ @Override
+ protected MenuManager createMenuManager() {
+ MenuManager bar = new MenuManager();
+
+ ActionManager actions = getActions();
+
+ MenuManager file = addMenu(bar, "&File");
+ file.add(actions.get(OpenAction.class));
+
+ file.add(new AppRecentMenu(this, file));
+
+ file.add(new Separator());
+ file.add(actions.get(SaveAction.class));
+ file.add(actions.get(SaveAsAction.class));
+ file.add(new Separator());
+
+ // Export menu ->
+ MenuManager exportMenu = addMenu(file, "&Export",
+ props.getProperty("ExportMenu.icon"));
+
+ exportMenu.add(actions.get(ExportViewAction.class));
+ exportMenu.add(actions.get(ExportImageMapAction.class));
+ exportMenu.add(actions.get(ExportTransparentPNGAction.class));
+
+ file.add(new Separator());
+ file.add(actions.get(PrintAction.class));
+ file.add(new Separator());
+ file.add(actions.get(ExitAction.class));
+
+ MenuManager edit = addMenu(bar, "&Edit");
+
+ edit.add(actions.get(UndoAction.class));
+ edit.add(actions.get(RedoAction.class));
+ edit.add(new Separator());
+ edit.add(actions.get(CopyAction.class));
+ edit.add(new Separator());
+ edit.add(actions.get(PreferencesAction.class));
+
+ MenuManager tools = addMenu(bar, "&Tools");
+ tools.add(actions.get(OpenExperimentAction.class));
+ tools.add(new Separator());
+
+ for (SelectSegmenterAction a : actions.getSegmentationActions()) {
+ tools.add(a);
+ }
+
+ tools.add(new Separator());
+ tools.add(actions.get(ConfigureSegmenterAction.class));
+
+ MenuManager go = addMenu(bar, "&Go");
+ go.add(actions.get(NextAction.class));
+ go.add(actions.get(PreviousAction.class));
+
+
+ MenuManager help = addMenu(bar, "&Help");
+
+ help.add(actions.get(HelpAction.class));
+ help.add(actions.get(AboutAction.class));
+
+ if (OsUtils.isMacOSX()) {
+ // Enhance the UI on the Mac
+ IAction aboutAction = actions.get(AboutAction.class);
+ IAction prefAction = actions.get(PreferencesAction.class);
+ Listener quitListener = new Listener() {
+ public void handleEvent(Event evt) {
+ getActions().get(ExitAction.class).runWithEvent(evt);
+ }
+ };
+ CocoaUIEnhancer enhancer = new CocoaUIEnhancer(Application.APP_NAME);
+ enhancer.hookApplicationMenu(Display.getDefault(),
+ quitListener, aboutAction, prefAction);
+ }
+
+ return bar;
+ }
+
+
+ protected static MenuManager addMenu(MenuManager parent, String text) {
+ MenuManager menu = new HoverMenuManager(text);
+ parent.add(menu);
+ return menu;
+ }
+
+ protected static MenuManager addMenu(MenuManager parent,
+ String text, String image) {
+
+ MenuManager menu;
+ try {
+ menu = new HoverMenuManager(text, image != null ?
+ new URL(image) : null);
+ } catch (MalformedURLException e) {
+ log.log(Level.WARNING, "Malformed URL", e);
+ menu = new HoverMenuManager(text);
+ }
+
+ parent.add(menu);
+ return menu;
+ }
+
+
+
+ public void setExperiment(Experiment ex) {
+ if (experiment != ex) {
+ experiment = ex;
+ setContext(null);
+
+ if (ex == null) {
+
+ if (experimentPanel != null) {
+ experimentPanel.dispose();
+ experimentPanel = null;
+ content.layout();
+ }
+ } else {
+
+ if (experimentModeEmbedded) {
+ experimentPanel = new ExperimentPanel(this, content, SWT.NONE);
+ GridData data = new GridData(SWT.FILL, SWT.FILL, false, true);
+ data.widthHint = 180;
+ data.verticalIndent = 5;
+ experimentPanel.setLayoutData(data);
+ content.layout();
+
+ } else {
+ ExperimentPanel.open(this);
+ }
+ }
+ updateEnabledActions();
+ updateWindowTitle();
+ }
+ }
+
+
+ public Experiment getExperiment() {
+ return experiment;
+ }
+
+
+ public boolean isExperimentMode() {
+ return experiment != null;
+ }
+
+
+ public void setExperimentModeEmbedded(boolean on) {
+ this.experimentModeEmbedded = on;
+ }
+
+
+ public AppPrefs getPrefs() {
+ return getPrefsManager().getPrefs();
+ }
+
+
+ AppPrefsManager getPrefsManager() {
+ if (prefsManager == null) {
+ prefsManager = new AppPrefsManager(this);
+ }
+ return prefsManager;
+ }
+
+
+ public Properties getProperties() {
+ return props;
+ }
+
+
+ public ActionManager getActions() {
+ if (actions == null) {
+ actions = new ActionManager(this);
+ }
+ return actions;
+ }
+
+
+ public Shell getShell() {
+ Shell shell = super.getShell();
+ if (shell == null) {
+ return this.shell;
+ }
+ return shell;
+ }
+
+
+ public SegmentationView getView() {
+ return view;
+ }
+
+
+ public SegmentationContext getContext() {
+ if (view != null) {
+ return view.getContext();
+ }
+ return null;
+ }
+
+
+ public File getContextFile() {
+ if (hasContextFile()) {
+ return getContext().getFile();
+ }
+ return null;
+ }
+
+
+ public Image getIcon(String url) {
+ Image image = JFaceResources.getImage(url);
+ if (image == null) {
+ ImageRegistry registry = JFaceResources.getImageRegistry();
+ ImageDescriptor descriptor = createImageDescriptor(url);
+ registry.put(url, descriptor);
+ image = registry.get(url);
+ }
+ return image;
+ }
+
+
+ public boolean hasContext() {
+ if (view != null) {
+ return view.getContext() != null;
+ }
+ return false;
+ }
+
+
+ public boolean hasContextFile() {
+ return getContext() != null ? getContext().hasContextFile() : false;
+ }
+
+
+ public void setEnableFeedback(boolean on) {
+ if (observer != null) {
+ observer.setEnabled(on);
+ }
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+ if (ctx != null) {
+ // Set a segmenter if one has not been set
+ if (!view.hasSegmenter()) {
+ SegmenterRegistry registry = SegmenterRegistry.getInstance();
+ view.setSegmenter(registry.getDefault());
+ }
+ }
+
+ // Set context
+ view.setContext(ctx);
+ updateWindowTitle();
+ }
+
+
+ public void status(String message) {
+ setStatus(message);
+ }
+
+
+ public void status(String format, Object... args) {
+ setStatus(String.format(format, args));
+ }
+
+
+ public void status(Image image, String format, Object... args) {
+ StatusLineManager manager = getStatusLineManager();
+ if (manager != null) {
+ manager.setMessage(image, String.format(format, args));
+ }
+ }
+
+
+ public void status(AppStatus s, String format, Object... args) {
+ if (format == null) {
+ setStatus(null);
+
+ } else {
+ StatusLineManager manager = getStatusLineManager();
+ if (manager != null) {
+ if (s == AppStatus.Error) {
+ manager.setErrorMessage(s.getIcon(), String.format(format, args));
+ } else {
+ manager.setMessage(s.getIcon(), String.format(format, args));
+ }
+ }
+ }
+ }
+
+
+ public void drop(FileDropEvent evt) {
+ System.out.println("drop received");
+ for (File f : evt.files()) {
+ if (isAcceptable(f)) {
+ getActions().get(OpenAction.class).open(f);
+ break;
+ }
+ }
+ }
+
+
+ public boolean isAcceptable(File file) {
+ String ext = FileUtils.getExtension(file);
+ return (ext.equals(".ctx") || SwtUtils.getImageFormat(file) != -1);
+ }
+
+
+ private void handleContextChanged(ContextChangedEvent evt) {
+
+ // Remove listener from old context
+ if (evt.oldContext != null) {
+ evt.oldContext.removeAnnotationListener(annotationListener);
+ }
+
+ // Add listener to new context
+ if (evt.newContext != null) {
+ evt.newContext.addAnnotationListener(annotationListener);
+ }
+
+ // Update application state
+ updateEnabledActions();
+ }
+
+
+ private void handleAnnotationPerformed(AnnotationEvent e) {
+
+ // Update state of undo and redo menu items
+ updateUndoRedoActions();
+ updateExportImageMapAction();
+ }
+
+
+ private final ContextChangeListener contextListener = new ContextChangeListener() {
+ public void contextChanged(ContextChangedEvent evt) {
+ handleContextChanged(evt);
+ }
+ };
+
+
+ private final AnnotationListener annotationListener = new AnnotationAdapter() {
+ public void annotationsChanged(AnnotationEvent e) {
+ handleAnnotationPerformed(e);
+ }
+ };
+
+
+ private class ImageObserver implements MouseMoveListener, ContextChangeListener {
+ private static final String MONOSPACE_FONT = "Monospace";
+
+ private final ImageControl ctrl;
+ private ImageData image;
+ private boolean inside;
+ private boolean enabled;
+
+
+ public ImageObserver(SegmentationView view) {
+ view.addContextChangeListener(this);
+ view.getCanvas().addMouseMoveListener(this);
+ ctrl = view.getImageControl();
+ contextChanged(view.getContext());
+ FontDescriptor fd = FontDescriptor.createFrom(MONOSPACE_FONT, 8, SWT.NORMAL);
+ JFaceResources.getFontRegistry().put(MONOSPACE_FONT, fd.getFontData());
+ }
+
+
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ status(null);
+ Font font = JFaceResources.getDefaultFont();
+ getStatusLineManager().getControl().setFont(font);
+ this.inside = false;
+ this.enabled = enabled;
+ }
+ }
+
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+
+ public void mouseMove(MouseEvent e) {
+ if (isEnabled()) {
+ Point pt = getImagePoint(e);
+ if (pt != null) {
+ inside(pt, getColor(pt));
+ } else {
+ outside();
+ }
+ }
+ }
+
+
+ private void outside() {
+ if (inside) {
+ status(null);
+ Font font = JFaceResources.getDefaultFont();
+ getStatusLineManager().getControl().setFont(font);
+ inside = false;
+ }
+ }
+
+
+ private final Point getImagePoint(MouseEvent e) {
+ if (image != null) {
+ Point pt = new Point(e.x, e.y);
+ if (ctrl.imageContains(pt)) {
+ return ctrl.canvasToImage(pt);
+ }
+ }
+ return null;
+ }
+
+
+ private final RGB getColor(Point pt) {
+
+ int pixel = image.getPixel(pt.x, pt.y);
+ return image.palette.getRGB(pixel);
+ }
+
+
+ private void inside(Point pt, RGB c) {
+ if (!inside) {
+ Font font = JFaceResources.getFont(MONOSPACE_FONT);
+ getStatusLineManager().getControl().setFont(font);
+ }
+
+ status(AppStatus.Information,
+ "Location [%4d,%4d] Color [%3d,%3d,%3d]",
+ pt.x, pt.y, c.red, c.green, c.blue);
+
+ inside = true;
+ }
+
+
+ public void contextChanged(ContextChangedEvent evt) {
+ contextChanged(evt.newContext);
+ }
+
+
+ public void contextChanged(SegmentationContext ctx) {
+ if (ctx != null) {
+ image = ctx.getImageData();
+ } else {
+ image = null;
+ }
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.util.*;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+
+import static ie.dcu.util.FileUtils.pathJoin;
+import static ie.dcu.util.OsUtils.*;
+
+/**
+ * Base application. Contains information strings and methods to load
+ * application wide resources.
+ *
+ * @author Kevin McGuinness
+ */
+public class Application {
+ public static final String APP_ID = "ist";
+ public static final String APP_NAME = "Interactive Segmentation and Annotation Tool";
+ public static final String APP_VERSION = "1.3.4";
+ public static final String APP_RESOURCE_DIR = "resources";
+
+ /**
+ * Default locations to look for plugins
+ */
+ public static final String[] DEFAULT_PLUGIN_SEARCH_PATH
+ = defaultPluginSearchPath();
+
+ /**
+ * Loads a properties file from the application resource area
+ * (resources/config)
+ *
+ * @param name
+ * The properties file name. ".properties" will be appended if
+ * necessary.
+ * @return A Properties object
+ * @throws IOException
+ * If there is an error creating the Properties object.
+ */
+ public static Properties loadProperties(String name) throws IOException {
+ if (!name.endsWith(".properties")) {
+ name = name + ".properties";
+ }
+
+ String path = FileUtils.pathJoin(APP_RESOURCE_DIR, "config", name);
+ File file = new File(path);
+ System.out.println("Path:" + file);
+ return PropsUtils.load(file);
+ }
+
+ /**
+ * Returns the users plugins folder
+ */
+ public static String userPluginsFolder() {
+ return pathJoin(userHome(), ".ist", "plugins");
+ }
+
+ /**
+ * Returns the applications base folder
+ */
+ public static String applicationFolder() {
+ String apphome = System.getenv("IST_HOME");
+ if (apphome != null) {
+ File file = new File(apphome);
+ if (file.isDirectory()) {
+ return file.getAbsolutePath();
+ }
+ }
+
+ ProtectionDomain domain = Application.class.getProtectionDomain();
+ CodeSource source = domain.getCodeSource();
+ URL location = source.getLocation();
+ String path = location.getPath();
+ try {
+
+ path = URLDecoder.decode(path, "UTF-8");
+ if (isWindows()) {
+ path = path.substring(1);
+ }
+
+ path = new File(path).getParent();
+ } catch (UnsupportedEncodingException e) {
+ path = System.getProperty("user.dir");
+ }
+
+ return path;
+ }
+
+ public static String applicationPluginsFolder() {
+ return pathJoin(applicationFolder(), "plugins");
+ }
+
+ public static String osxPluginSearchPath() {
+ return pathJoin(userHome(), "Library",
+ "Application Support", APP_NAME, "Plugins");
+ }
+
+ private static String[] defaultPluginSearchPath() {
+ ArrayList<String> paths = new ArrayList<String>();
+ paths.add(userPluginsFolder());
+ if (isMacOSX()) {
+ paths.add(osxPluginSearchPath());
+ }
+ paths.add(applicationPluginsFolder());
+ return paths.toArray(new String[paths.size()]);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.eval.*;
+
+import java.util.*;
+
+public class EvaluatorRegistry {
+ private static EvaluatorRegistry instance;
+ private final Set<Evaluator> evaluators;
+
+ private EvaluatorRegistry() {
+ evaluators = new LinkedHashSet<Evaluator>();
+ init();
+ }
+
+
+ private void init() {
+ add(new ConfusionMatrixEvaluator());
+ add(new BoundaryAccuracyEvaluator());
+ }
+
+
+ public static EvaluatorRegistry getInstance() {
+ if (instance == null) {
+ instance = new EvaluatorRegistry();
+ }
+ return instance;
+ }
+
+
+ public Evaluator find(String name) {
+ for (Evaluator e : evaluators) {
+
+ // Check name
+ String ename = e.getName();
+ if (ename.equals(name)) {
+ return e;
+ }
+
+ // Check class name
+ String cname = e.getClass().getName();
+ if (cname.equals(name)) {
+ return e;
+ }
+
+ // Check simple name
+ String sname = e.getClass().getSimpleName();
+ if (sname.equals(name)) {
+ return e;
+ }
+ }
+
+ return null;
+ }
+
+
+ public void add(Evaluator evaluator) {
+ evaluators.add(evaluator);
+ }
+
+
+ public Set<Evaluator> evaluators() {
+ return Collections.unmodifiableSet(evaluators);
+ }
+
+
+ public Iterator<Evaluator> iterator() {
+ return evaluators.iterator();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.plugin.*;
+import ie.dcu.segment.Segmenter;
+
+import java.util.logging.Logger;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Application launcher.
+ *
+ * @author Kevin McGuinness
+ */
+public class Main {
+ public static final Logger log = Logger.getLogger("Main");
+
+ /**
+ * Message to show when GDI+ is not installed.
+ */
+ private static final String NO_GDIPLUS =
+ "Unable to load required library: GDI+ (gdiplus.dll)\n\n" +
+ "The GDI+ library needs to be installed before this program " +
+ "can be used on this platform. See the note at the end of " +
+ "the download page for further details.";
+
+
+ public static void main(String[] args) {
+ System.out.print(Application.DEFAULT_PLUGIN_SEARCH_PATH);
+ for (String s: Application.DEFAULT_PLUGIN_SEARCH_PATH) {
+ System.out.println(s);
+ }
+
+ Display.setAppName(Application.APP_NAME);
+ check();
+ loadPlugins();
+ AppWindow window = new AppWindow();
+ window.setBlockOnOpen(true);
+ window.open();
+ System.exit(0);
+ }
+
+ private static void loadPlugins() {
+ PluginManager manager = new PluginManager();
+ for (String path : Application.DEFAULT_PLUGIN_SEARCH_PATH) {
+ manager.searchPath().add(path);
+ }
+ manager.loadPlugins();
+ for (Plugin plugin : manager.plugins()) {
+ String classname = plugin.getMetadata("segmenter");
+ if (classname != null) {
+ loadSegmenterPlugin(plugin, classname);
+ }
+ }
+ }
+
+ private static void loadSegmenterPlugin(Plugin plugin, String classname) {
+ try {
+ Segmenter segmenter = (Segmenter)
+ plugin.loadObject(classname);
+
+ SegmenterRegistry.getInstance().add(segmenter);
+
+ } catch (PluginException e) {
+ log.severe("Unable to load plugin: " + e);
+ }
+ }
+
+ public static boolean contains(String[] args, String argument) {
+ for (String arg : args) {
+ while (arg.startsWith("-")) {
+ arg = arg.substring(1);
+ }
+
+ if (arg.equals(argument)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void check() {
+ // Check for potential missing GDI+ on Win2K, NT, ME 98
+ String os = System.getProperty("os.name");
+ if (os.equals("Windows 2000") ||
+ os.equals("Windows NT") ||
+ os.equals("Windows ME") ||
+ os.equals("Windows 98"))
+ {
+ try {
+ System.loadLibrary("gdiplus");
+ } catch (RuntimeException e) {
+ exit(NO_GDIPLUS);
+ }
+ }
+ }
+
+ static void exit(String message) {
+ Display display = Display.getDefault();
+ Rectangle rect = display.getBounds();
+
+ Shell shell = display.getActiveShell();
+ if (shell == null) {
+ shell = new Shell(display, SWT.NO_TRIM);
+ shell.setBounds(rect.width/2, rect.height/2, 0, 0);
+ shell.open();
+ shell.setText("Error");
+ }
+
+ MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
+ box.setText("Error");
+ box.setMessage(message);
+ box.open();
+ display.dispose();
+ System.exit(1);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.segment.painters.*;
+
+import java.util.*;
+
+public class PainterRegistry {
+ private final LinkedHashMap<String, SegmentationPainter> painters;
+
+ public PainterRegistry() {
+ painters = new LinkedHashMap<String, SegmentationPainter>();
+ init();
+ }
+
+
+ private void init() {
+ add(new CombinedPainter());
+ add(new OriginalPainter());
+ add(new MarkupPainter());
+ add(new MaskPainter());
+ add(new ForegroundOnlyPainter());
+ add(new OutlineOverlayPainter());
+ add(new LabelPainter());
+ }
+
+
+ public void add(SegmentationPainter painter) {
+ painters.put(painter.getName(), painter);
+ }
+
+
+ public SegmentationPainter get(String painter) {
+ return painters.get(painter);
+ }
+
+
+ public Collection<SegmentationPainter> values() {
+ return painters.values();
+ }
+
+
+ public void dispose() {
+ for (SegmentationPainter p : values()) {
+ p.dispose();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist;
+
+import ie.dcu.segment.*;
+
+import java.util.*;
+
+/**
+ * Registry for segmenters.
+ *
+ * @author Kevin McGuinness
+ */
+public class SegmenterRegistry implements Iterable<Segmenter> {
+ private static final String DEFAULT_SEGMENTER_NAME = "IgcSegmenter";
+ private static SegmenterRegistry instance;
+ private final Set<Segmenter> segmenters;
+
+ private SegmenterRegistry() {
+ segmenters = new LinkedHashSet<Segmenter>();
+ init();
+ }
+
+
+ private void init() {
+ // OutlineSegmenter is not very useful
+ // add(new OutlineSegmenter());
+
+ // Geodesic Active Contours is too sensitive to parameters
+ // for general use on natural images.
+ // add(new SeededGacSegmenter());
+
+ //add(new SrgSegmenter());
+ //add(new IgcSegmenter());
+ //add(new BptSegmenter());
+ //add(new SioxSegmenter());
+ //add(new SpatiogramSegmenter());
+ }
+
+
+ public static SegmenterRegistry getInstance() {
+ if (instance == null) {
+ instance = new SegmenterRegistry();
+ }
+ return instance;
+ }
+
+
+ public Segmenter find(String name) {
+ for (Segmenter s : segmenters) {
+
+ // Check name
+ String ename = s.getName();
+ if (ename.equals(name)) {
+ return s;
+ }
+
+ // Check class name
+ String cname = s.getClass().getName();
+ if (cname.equals(name)) {
+ return s;
+ }
+
+ // Check simple name
+ String sname = s.getClass().getSimpleName();
+ if (sname.equals(name)) {
+ return s;
+ }
+ }
+
+ return null;
+ }
+
+
+ public boolean isDefault(Segmenter s) {
+ if (s != null) {
+ if (segmenters.size() == 1 && segmenters.contains(s)) {
+ return true;
+ }
+ return s.getClass().getSimpleName().equals(DEFAULT_SEGMENTER_NAME);
+ }
+ return false;
+ }
+
+
+ public Segmenter getDefault() {
+ for (Segmenter s : segmenters) {
+ if (isDefault(s)) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+
+ public void add(Segmenter segmenter) {
+ segmenters.add(segmenter);
+ }
+
+
+ public Set<Segmenter> segmenters() {
+ return Collections.unmodifiableSet(segmenters);
+ }
+
+
+ public Iterator<Segmenter> iterator() {
+ return segmenters.iterator();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.Application;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.*;
+
+
+/**
+ * Show an about dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class AboutAction extends AppAction {
+
+ private AboutBox dialog;
+
+ public AboutAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ if (dialog == null) {
+ dialog = new AboutBox();
+ }
+ dialog.open();
+ }
+
+
+ private class AboutBox extends Dialog {
+
+ private static final String MESSAGE =
+ " Kevin McGuinness <a href=\"mailto:kevin.mcguinness@eeng.dcu.ie\">" +
+ "kevin.mcguinness@eeng.dcu.ie</a>\n" +
+ " Center for Digital Video Processing\n" +
+ " Dublin City University.\n" +
+ " <a href=\"http://www.cdvp.dcu.ie\">http://www.cdvp.dcu.ie</a>";
+
+
+ private Composite composite;
+
+
+ protected AboutBox() {
+ super(window);
+ }
+
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(LayoutFactory.createGridLayout(5, 5));
+
+ String image = string("aboutImage");
+ addImage(image);
+ addLabel(String.format("Version %s", Application.APP_VERSION), SWT.CENTER);
+ addLabel(" SWT Version " + swtVersionString(), SWT.CENTER);
+ addLabel(" Developed By:", SWT.LEFT);
+ addLink(MESSAGE);
+
+ return composite;
+ }
+
+ private String swtVersionString() {
+ return String.format("%d (%s)", SWT.getVersion(), SWT.getPlatform());
+ }
+
+
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent,
+ IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
+ }
+
+
+ @Override
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText("About");
+ }
+
+
+ private void addLink(String string) {
+ Link link = new Link(composite, 0);
+ link.setText(string);
+ link.setLayoutData(LayoutFactory.createGridData());
+ }
+
+
+ private void addLabel(String text, int alignment) {
+ Label label = new Label(composite, 0);
+ label.setText(text);
+ label.setAlignment(alignment);
+ label.setLayoutData(LayoutFactory.createGridData());
+ label.setFont(JFaceResources.getBannerFont());
+ }
+
+ private void addImage(String url) {
+ Label label = new Label(composite, 0);
+ label.setImage(window.getIcon(url));
+ label.setLayoutData(LayoutFactory.createGridData());
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.Segmenter;
+import ie.dcu.util.OsUtils;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Manages application actions.
+ *
+ * @author Kevin McGuinness
+ */
+public class ActionManager {
+ private static final Logger log = Logger.getLogger("ActionManager");
+
+ private final AppWindow window;
+ private final Map<String, AppAction> actions;
+ private ArrayList<SelectSegmenterAction> segmenterActions;
+ private Properties props;
+
+
+ public ActionManager(AppWindow window) {
+
+ this.window = window;
+ this.actions = new HashMap<String, AppAction>();
+
+ try {
+ this.props = Application.loadProperties(getPropertiesFile());
+ } catch (IOException e) {
+ log.severe("Error loading action properties: " +
+ e.getLocalizedMessage());
+ throw new RuntimeException(e);
+ }
+
+ init();
+ }
+
+
+ private String getPropertiesFile() {
+ return OsUtils.isMacOS() ? "actions.mac" : "actions";
+ }
+
+
+ Properties getProperties() {
+ return props;
+ }
+
+
+ AppWindow getWindow() {
+ return window;
+ }
+
+
+ public void init() {
+ add(new OpenAction(this));
+ add(new SaveAction(this));
+ add(new SaveAsAction(this));
+ add(new AboutAction(this));
+ add(new CopyAction(this));
+ add(new ExitAction(this));
+ add(new ExportViewAction(this));
+ add(new ExportImageMapAction(this));
+ add(new ExportTransparentPNGAction(this));
+ add(new HelpAction(this));
+ add(new PreferencesAction(this));
+ add(new PrintAction(this));
+ add(new RedoAction(this));
+ add(new UndoAction(this));
+ add(new NextAction(this));
+ add(new PreviousAction(this));
+ add(new OpenExperimentAction(this));
+ add(new ConfigureSegmenterAction(this));
+
+ // Add select segmenter actions
+ SegmenterRegistry registry = SegmenterRegistry.getInstance();
+ for (Segmenter s : registry) {
+ add(new SelectSegmenterAction(this, registry, s));
+ }
+ }
+
+
+ public void add(AppAction action) {
+ actions.put(action.id(), action);
+ }
+
+
+ public <T extends AppAction> T get(Class<T> clazz) {
+ return clazz.cast(actions.get(id(clazz)));
+ }
+
+
+ public <T extends AppAction> T get(Class<T> clazz, String id) {
+ return clazz.cast(actions.get(id));
+ }
+
+
+ public void setEnabled(String id, boolean enabled) {
+ AppAction action = actions.get(id);
+ if (action != null) {
+ action.setEnabled(enabled);
+ }
+ }
+
+
+ public <T extends AppAction> void setEnabled(Class<T> clazz, boolean enabled) {
+ AppAction action = get(clazz);
+ if (action != null) {
+ action.setEnabled(enabled);
+ }
+ }
+
+
+ public Collection<SelectSegmenterAction> getSegmentationActions() {
+ if (segmenterActions == null) {
+ segmenterActions = new ArrayList<SelectSegmenterAction>();
+ for (AppAction a : actions.values()) {
+ if (a instanceof SelectSegmenterAction) {
+ segmenterActions.add((SelectSegmenterAction) a);
+ }
+ }
+ }
+
+ return Collections.unmodifiableCollection(segmenterActions);
+ }
+
+
+ private String id(Class<?> clazz) {
+ return clazz.getName();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.util.logging.*;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.swt.graphics.*;
+
+import ie.dcu.apps.ist.*;
+
+/**
+ * Root class for all application actions.
+ *
+ * @author Kevin McGuinness
+ */
+public class AppAction extends ConfiguredAction {
+
+ // Make easier for subclasses to set status
+ protected static final AppStatus Information = AppStatus.Information;
+ protected static final AppStatus Warning = AppStatus.Warning;
+ protected static final AppStatus Error = AppStatus.Error;
+
+ // Logger
+ protected final Logger log = Logger.getLogger(getClass().getSimpleName());
+
+ // Window
+ protected final AppWindow window;
+
+ // Action manager
+ protected final ActionManager manager;
+
+
+ public AppAction(ActionManager m) {
+ super(m.getProperties());
+ manager = m;
+ window = m.getWindow();
+ }
+
+
+ public AppAction(ActionManager m, int style) {
+ super(m.getProperties(), style);
+ manager = m;
+ window = m.getWindow();
+ }
+
+
+ @Override
+ public void arm() {
+ super.arm();
+ status(AppStatus.Information, getDescription());
+ }
+
+
+ @Override
+ public void disarm() {
+ super.disarm();
+ window.setStatus(null);
+ }
+
+
+ public String id() {
+ return getClass().getName();
+ }
+
+
+ protected Image icon(String key) {
+ String prop = string(key);
+ if (prop != null) {
+ return window.getIcon(prop);
+ }
+ return null;
+ }
+
+
+ protected void error(String message) {
+ MessageDialog.openError(window.getShell(), "Error", message);
+ }
+
+
+ protected void error(String format, Object... args) {
+ error(String.format(format, args));
+ }
+
+
+ protected void warning(String message) {
+ MessageDialog.openWarning(window.getShell(), "Warning", message);
+ }
+
+
+ protected void warning(String format, Object... args) {
+ warning(String.format(format, args));
+ }
+
+
+ protected void info(String message) {
+ MessageDialog.openInformation(window.getShell(), "Information", message);
+ }
+
+
+ protected void info(String format, Object... args) {
+ info(String.format(format, args));
+ }
+
+
+ protected void log(Level level, String message, Throwable th) {
+ log.log(level, message, th);
+ }
+
+
+ protected String property(String key) {
+ return properties.getProperty(key);
+ }
+
+
+ protected void status(AppStatus s, String format, Object... args) {
+ window.status(s, format, args);
+ }
+
+
+ protected void status(String format, Object... args) {
+ status(Information, format, args);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+/**
+ * Show the configure segmenter dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class ConfigureSegmenterAction extends AppAction {
+
+ public ConfigureSegmenterAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canShowOptions()) {
+ view.showSegmenterOptions();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.net.*;
+import java.util.*;
+import java.util.logging.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+
+/**
+ * Hover Action configured from a properties file.
+ *
+ * @author Kevin McGuinness
+ */
+public class ConfiguredAction extends HoverAction {
+ private static final Logger log = Logger.getLogger("ConfiguredAction");
+
+ protected final Properties properties;
+ private final String id;
+
+
+ public ConfiguredAction(Properties properties) {
+ this(properties, null, AS_PUSH_BUTTON);
+ }
+
+
+ public ConfiguredAction(Properties properties, int style) {
+ this(properties, null, style);
+ }
+
+
+ public ConfiguredAction(Properties properties, String id, int style) {
+ super(null, style);
+
+ assert (properties != null);
+ this.properties = properties;
+
+ if (id != null) {
+ this.id = id;
+ } else {
+ this.id = getClass().getSimpleName();
+ }
+
+ configure();
+ }
+
+
+ private void configure() {
+
+ // Set id
+ setId(id);
+
+ // Set text
+ setText(string("text"));
+
+ // Set description
+ String description;
+ if ((description = string("description")) != null) {
+ setDescription(description);
+ }
+
+ // Set tooltip
+ String tooltip;
+ if ((tooltip = string("tooltip")) != null) {
+ setToolTipText(tooltip);
+ }
+
+ // Set accelerator
+ Integer keycode;
+ if ((keycode = accelerator("accelerator")) != null) {
+ setAccelerator(keycode);
+ }
+
+ // Set images
+ ImageDescriptor im;
+
+ if ((im = image("image")) != null) {
+ setImageDescriptor(im);
+ }
+
+ if ((im = image("hoverImage")) != null) {
+ setHoverImageDescriptor(im);
+ }
+
+ if ((im = image("disabledImage")) != null) {
+ setDisabledImageDescriptor(im);
+ }
+
+ // Set status
+ Boolean status;
+
+ if ((status = bool("checked") != null)) {
+ setChecked(status);
+ }
+
+ if ((status = bool("enabled") != null)) {
+ setEnabled(status);
+ }
+ }
+
+
+ protected final Integer accelerator(String key) {
+ String value = string(key);
+
+ if (value == null) {
+ return null;
+ }
+
+ int code = Action.convertAccelerator(value);
+
+ if (code == 0) {
+ log.warning("No accelerator: " + value);
+ return null;
+ }
+
+ if (code == -1) {
+ log.warning("Not a valid accelerator keycode: " + value);
+ return null;
+ }
+
+ return code;
+ }
+
+
+ protected final Boolean bool(String key) {
+ String value = string(key);
+
+ if (value == null) {
+ return null;
+ }
+
+ if (value.trim().equals("true")) {
+ return true;
+ } else if (value.trim().equals("false")) {
+ return false;
+ } else {
+ log.warning("Not a boolean value: " + value);
+ return null;
+ }
+ }
+
+
+ protected final ImageDescriptor image(String key) {
+ String url = string(key);
+
+ if (url == null) {
+ return null;
+ }
+
+ try {
+ return ImageDescriptor.createFromURL(new URL(url));
+
+ } catch (MalformedURLException e) {
+ log.warning("Invalid URL for image " + url);
+ return null;
+ }
+ }
+
+
+ protected final String string(String key) {
+ return properties.getProperty(key(key));
+ }
+
+
+ protected final String key(String name) {
+ return String.format("action.%s.%s", id, name);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.*;
+import java.awt.image.BufferedImage;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Copy view to clipboard.
+ *
+ * @author Kevin McGuinness
+ */
+public class CopyAction extends AppAction {
+
+ public CopyAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+ copy();
+ }
+ }
+
+
+ private void copy() {
+ setClipboard(paintContext());
+ status("View copied to clipboard as image");
+ }
+
+
+ private static BufferedImage convert(Image image) {
+ BufferedImage im = ImageConverter.convert(image.getImageData());
+ image.dispose();
+ return im;
+ }
+
+
+ public void setClipboard(Image image) {
+ ImageSelection selection = new ImageSelection(convert(image));
+ Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
+ clip.setContents(selection, null);
+ }
+
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+
+
+ private static class ImageSelection implements Transferable {
+ private BufferedImage image;
+
+
+ public ImageSelection(BufferedImage image) {
+ this.image = image;
+ }
+
+
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] { DataFlavor.imageFlavor };
+ }
+
+
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return DataFlavor.imageFlavor.equals(flavor);
+ }
+
+
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException
+ {
+ if (!DataFlavor.imageFlavor.equals(flavor)) {
+ throw new UnsupportedFlavorException(flavor);
+ }
+ return image;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+
+/**
+ * Terminate the application.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExitAction extends AppAction {
+ public ExitAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (confirmExit(window)) {
+ window.close();
+ System.exit(0);
+ }
+ }
+
+ public static boolean confirmExit(AppWindow window) {
+ boolean confirm = window.getPrefs().get(
+ Boolean.class, AppPrefs.Keys.CONFIRM_EXIT, true);
+
+ if (confirm) {
+ return MessageDialog.openConfirm(
+ window.getShell(), "Confirm",
+ "Really exit the application?");
+ }
+
+ return true;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.dialogs.ExportDialog;
+import ie.dcu.apps.ist.export.imagemap.*;
+import ie.dcu.apps.ist.views.*;
+import ie.dcu.segment.*;
+import ie.dcu.swt.ImageConverter;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.program.Program;
+
+/**
+ * Export segmentation results as HTML image maps
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportImageMapAction extends AppAction{
+
+ public ExportImageMapAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+
+ // Get options from user
+ ExportDialog dialog = new ExportDialog(window.getShell());
+ ExportDialog.Result result = dialog.open();
+
+ if (result != null) {
+
+ // Grab image and mask
+ SegmentationContext ctx = window.getContext();
+ SegmentationMask mask = ctx.getMask();
+ BufferedImage image = ImageConverter.convert(ctx.getImageData());
+
+ // Setup exporter
+ Exporter exporter = new Exporter(image, mask);
+ exporter.setEffect(result.effect);
+ exporter.setHtmlFile(result.html);
+ exporter.setImageFile(result.image);
+ exporter.setObjectLink(result.link);
+ exporter.setExportShape(result.shape);
+ exporter.setObjectDescription(result.description);
+ String label = SegmentationView.comboLabel.getText();
+ if(label.indexOf('{') != -1)
+ {
+ label = label.substring(0,label.indexOf('{')-1);
+ }
+ exporter.setTitle(label);
+
+ // Export
+ try {
+ exporter.export(result.folder);
+ } catch (IOException e) {
+ handleError(e);
+ return;
+ } catch (ExportException e) {
+ handleError(e);
+ return;
+ }
+ // for opening the image after saving as ImageMap
+ if (result.open) {
+ File file = new File(result.folder, result.html);
+ Program program = Program.findProgram(".html");
+ if (program != null) {
+ program.execute(file.getAbsolutePath());
+ }
+ }
+ }
+ }
+ }
+
+ private void handleError(Exception e) {
+ log(Level.WARNING, "Error exporting view as HTML image map", e);
+
+ // Show error dialog
+ error("Error exporting view as HTML image map: %s",
+ e.getLocalizedMessage());
+
+ // Set status message
+ status(Error, "Error exporting view as HTML image map");
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.FileDialog;
+
+public class ExportTransparentPNGAction extends AppAction {
+
+private static final String DEFAULT_SUFFIX = ".png";
+
+ private FileDialog dialog;
+ private String directory;
+
+ public ExportTransparentPNGAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ directory = file.getParent();
+ export(file);
+ }
+ }
+ }
+
+
+ private void export(File file) {
+ ImageData im = createSemiTransparentImage();
+ try {
+ SwtUtils.saveImage(im, file);
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private ImageData createSemiTransparentImage() {
+ SegmentationContext ctx = window.getContext();
+ ImageData image = ctx.getImageData();
+ SegmentationMask mask = ctx.getMask();
+ ImageData result = new ImageData(image.width, image.height, 32, image.palette);
+
+ int[] pixels = new int[image.width];
+ byte[] alphas = new byte[image.width];
+
+ for (int y = 0; y < image.height; y++) {
+ image.getPixels(0, y, image.width, pixels, 0);
+
+ int offset = y * mask.cols;
+ for (int i = 0; i < mask.cols; i++) {
+ if (mask.values[i+offset] == SegmentationMask.BACKGROUND) {
+ alphas[i] = (byte) 0x00;
+ } else {
+ alphas[i] = (byte) 0xff;
+ }
+ }
+
+ result.setPixels(0, y, image.width, pixels, 0);
+ result.setAlphas(0, y, image.width, alphas, 0);
+ }
+
+ return result;
+ }
+
+
+ private void handleError(File file, IOException e) {
+ // Log
+ log(Level.WARNING, "Error exporting view as image", e);
+
+ // Show error dialog
+ error("Error exporting view as image %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error exporting view as image %s", file.getName());
+ }
+
+
+ private File getFile() {
+ createExportDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return checkFile(path);
+ }
+
+ return null;
+ }
+
+
+ private File checkFile(String path) {
+ File file = new File(path);
+
+ if (SwtUtils.getImageFormat(file) != -1) {
+ return file;
+ }
+
+ // Unknown file extension, so assume jpg
+ return new File(FileUtils.replaceExtension(path, DEFAULT_SUFFIX));
+ }
+
+
+ private void createExportDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText("Export Transparent PNG");
+ dialog.setFilterExtensions(new String[]{"*.png"});
+ dialog.setFilterNames(new String[]{"PNG Images"});
+ }
+
+ dialog.setFileName(getFileName());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ // Unfortunately this is buggy on SWT/GTK and
+ // the filter path isin't always set :'-(
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ private String getFileName() {
+ File file = window.getContext().getFile();
+ return FileUtils.replaceExtension(file.getName(), DEFAULT_SUFFIX);
+ }
+
+
+ private String getDirectory() {
+ if (directory == null) {
+ File folder = window.getContext().getFolder();
+ directory = (folder != null) ?
+ directory = folder.getAbsolutePath() : null;
+ }
+
+ return directory;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+import java.util.logging.Level;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.*;
+
+
+/**
+ * Export current view as image.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportViewAction extends AppAction {
+
+ private static final String DEFAULT_SUFFIX = ".png";
+
+ private FileDialog dialog;
+ private String directory;
+
+ public ExportViewAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ directory = file.getParent();
+ export(file);
+ }
+ }
+ }
+
+
+ private void export(File file) {
+ Image im = paintContext();
+
+ try {
+ SwtUtils.saveImage(im, file);
+ status("View successfully exported as %s", file.getName());
+
+ } catch (IOException e) {
+
+ handleError(file, e);
+
+ } finally {
+
+ im.dispose();
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+ // Log
+ log(Level.WARNING, "Error exporting view as image", e);
+
+ // Show error dialog
+ error("Error exporting view as image %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error exporting view as image %s", file.getName());
+ }
+
+
+ private File getFile() {
+ createExportDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return checkFile(path);
+ }
+
+ return null;
+ }
+
+
+ private File checkFile(String path) {
+ File file = new File(path);
+
+ if (SwtUtils.getImageFormat(file) != -1) {
+ return file;
+ }
+
+ // Unknown file extension, so assume jpg
+ return new File(FileUtils.replaceExtension(path, DEFAULT_SUFFIX));
+ }
+
+
+ private void createExportDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("ExportViewAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+
+ dialog.setFileName(getFileName());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ // Unfortunately this is buggy on SWT/GTK and
+ // the filter path isin't always set :'-(
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ private String getFileName() {
+ File file = window.getContext().getFile();
+ return FileUtils.replaceExtension(file.getName(), DEFAULT_SUFFIX);
+ }
+
+
+ private String getDirectory() {
+ if (directory == null) {
+ File folder = window.getContext().getFolder();
+ directory = (folder != null) ?
+ directory = folder.getAbsolutePath() : null;
+ }
+
+ return directory;
+ }
+
+
+
+ private String[] getFilters() {
+ return new String[] { property("ExportViewAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("ExportViewAction.dialog.filter.text") };
+ }
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.swt.layout.LayoutFactory;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Show a help dialog.
+ *
+ * @author Kevin McGuinness
+ */
+public class HelpAction extends AppAction {
+
+ private HelpBox dialog;
+
+
+ public HelpAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ if (dialog == null) {
+ dialog = new HelpBox();
+ }
+
+ if (dialog.available()) {
+ dialog.open();
+ } else {
+ info("Help browser unavailable on this system");
+ }
+ }
+
+
+ /**
+ * Help dialog box, just wraps a browser.
+ */
+ private class HelpBox {
+ private Shell shell;
+ private Composite content;
+ private Browser browser;
+
+
+ public HelpBox() {
+ createShell();
+ createContent();
+ }
+
+
+ private void createShell() {
+ shell = new Shell(window.getShell(), SWT.SHELL_TRIM);
+
+ // Prevent the shell from disposing on close
+ shell.addShellListener(new ShellAdapter() {
+ public void shellClosed(ShellEvent evt) {
+ evt.doit = false;
+ close();
+ }
+ });
+
+ // Use a fill layout
+ shell.setLayout(new GridLayout());
+
+ // Set size and title
+ shell.setSize(640, 480);
+ shell.setText("Help");
+ }
+
+
+ private void createContent() {
+ // Create content pane
+ content = new Composite(shell, SWT.BORDER);
+ content.setLayout(new FillLayout());
+ content.setLayoutData(LayoutFactory.createGridData());
+
+ // Add browser widget
+ try {
+ browser = new Browser(content, SWT.NONE);
+ } catch (SWTError e) {
+
+ // Log warning and return
+ log.warning("Browser unavailable: " + e.getMessage());
+ browser = null;
+ return;
+ }
+
+ // Go to help home page
+ home();
+ }
+
+
+ public void home() {
+ String urlString = string("helpURL");
+ if (urlString != null) {
+ go(urlString);
+ } else {
+ browser.setText("Help file is unavailable");
+ }
+ }
+
+
+ public void go(String url) {
+ browser.setUrl(url);
+ }
+
+
+ public boolean available() {
+ return browser != null;
+ }
+
+
+ public void open() {
+ shell.setVisible(true);
+ }
+
+
+ public void close() {
+ shell.setVisible(false);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.resource.*;
+
+/**
+ * Default implementation of {@link IHoverAction}.
+ *
+ * @author Kevin McGuinness
+ */
+public class HoverAction extends Action implements IHoverAction {
+ private boolean armed = false;
+
+ /**
+ * Creates a new action with no text and no image. Configure the action later
+ * using the set methods.
+ */
+ public HoverAction() {
+ super();
+ }
+
+
+ /**
+ * Creates a new action with the given text and no image.
+ *
+ * @param text
+ * the string used as the text for the action, or <code>null</code>
+ * if there is no text
+ */
+ public HoverAction(String text) {
+ super(text);
+ }
+
+
+ /**
+ * Creates a new action with the given text and image.
+ *
+ * @param text
+ * the action's text, or <code>null</code> if there is no text
+ * @param image
+ * the action's image, or <code>null</code> if there is no image
+ */
+ public HoverAction(String text, ImageDescriptor image) {
+ super(text, image);
+ }
+
+
+ /**
+ * Creates a new action with the given text and style.
+ *
+ * @param text
+ * the action's text, or <code>null</code> if there is no text.
+ * @param style
+ * one of
+ * <code>AS_PUSH_BUTTON</code>,
+ * <code>AS_CHECK_BOX</code>,
+ * <code>AS_DROP_DOWN_MENU</code>,
+ * <code>AS_RADIO_BUTTON</code>, and
+ * <code>AS_UNSPECIFIED</code>.
+ */
+ public HoverAction(String text, int style) {
+ super(text, style);
+ }
+
+
+ /**
+ * Sets the armed property to <code>true</code>.
+ */
+ public void arm() {
+ armed = true;
+ }
+
+
+ /**
+ * Sets the armed property to <code>false</code>.
+ */
+ public void disarm() {
+ armed = false;
+ }
+
+
+ /**
+ * Returns <code>true</code> if armed.
+ */
+ public boolean isArmed() {
+ return armed;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.widgets.ImageMenuManager;
+
+import java.net.URL;
+import java.util.*;
+
+import org.eclipse.jface.action.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Menu Manager that notifies instances of IHoverAction when they are armed and
+ * disarmed.
+ *
+ * @author Kevin McGuinness
+ */
+public class HoverMenuManager extends ImageMenuManager {
+
+ /**
+ * Stores items that have hover listeners added to them.
+ */
+ private final Set<MenuItem> hoverItems = new HashSet<MenuItem>();
+
+
+ /**
+ * Create hover menu manager.
+ */
+ public HoverMenuManager() {
+ init();
+ }
+
+
+ /**
+ * Create hover menu manager with id and text.
+ *
+ * @param text
+ * the text for the menu, or <code>null</code> if none.
+ * @param imageURL
+ * the menu image url, or <code>null</code>.
+ */
+ public HoverMenuManager(String text, URL imageURL) {
+ super(text, imageURL);
+ init();
+ }
+
+
+ /**
+ * Create hover menu manager text.
+ *
+ * @param text
+ * the text for the menu, or <code>null</code> if none.
+ */
+ public HoverMenuManager(String text) {
+ super(text);
+ init();
+ }
+
+
+ private void init() {
+ addMenuListener(menuListener);
+ }
+
+
+ private void addHoverListeners() {
+ Menu menu = getMenu();
+ for (MenuItem item : menu.getItems()) {
+ addHoverListener(item);
+ }
+ }
+
+
+ private void addHoverListener(MenuItem item) {
+ IHoverAction action = getHoverAction(item);
+ if (action != null) {
+ addHoverListener(item, action);
+ }
+ }
+
+
+ private void addHoverListener(MenuItem item, IHoverAction action) {
+ if (!hoverItems.contains(item)) {
+ item.addArmListener(new HoverListener(action));
+ hoverItems.add(item);
+ }
+ }
+
+
+ private void disarmAll() {
+ Menu menu = getMenu();
+ for (MenuItem item : menu.getItems()) {
+ disarm(item);
+ }
+ }
+
+
+ private void disarm(MenuItem item) {
+ IHoverAction action = getHoverAction(item);
+ if (action != null) {
+ disarm(action);
+ }
+ }
+
+
+ private void disarm(IHoverAction action) {
+ if (action.isArmed()) {
+ action.disarm();
+ }
+ }
+
+
+ private IHoverAction getHoverAction(MenuItem item) {
+ Object data = item.getData();
+ if (data instanceof ActionContributionItem) {
+ IAction action = ((ActionContributionItem) data).getAction();
+
+ if (action instanceof IHoverAction) {
+ return (IHoverAction) action;
+ }
+ }
+ return null;
+ }
+
+
+ private IMenuListener menuListener = new IMenuListener2() {
+ public void menuAboutToShow(IMenuManager manager) {
+ addHoverListeners();
+ }
+
+ public void menuAboutToHide(IMenuManager manager) {
+ disarmAll();
+ }
+ };
+
+
+ private final class HoverListener implements ArmListener {
+ private final IHoverAction action;
+
+ public HoverListener(IHoverAction action) {
+ this.action = action;
+ }
+
+
+ public void widgetArmed(ArmEvent e) {
+ disarmAll();
+ action.arm();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import org.eclipse.jface.action.*;
+
+/**
+ * Extension of IAction that supports arming when the mouse hovers over the
+ * item. This may be useful for displaying descriptions of menu items in the
+ * status bar, for example. To use hover actions the {@link HoverMenuManager}
+ * must be used.
+ *
+ * @author Kevin McGuinness
+ */
+public interface IHoverAction extends IAction {
+
+
+ /**
+ * Executed when the action is hovered/armed.
+ */
+ public void arm();
+
+
+ /**
+ * Executed when the action is de-hovered/disarmed.
+ */
+ public void disarm();
+
+
+ /**
+ * Query if the action is armed.
+ *
+ * @return <code>true</code> if armed.
+ */
+ public boolean isArmed();
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Jump to the next image in the directory
+ *
+ * @author Kevin McGuinness
+ */
+public class NextAction extends AppAction {
+ // Supported file extensions
+ static final String EXTENSIONS = "jpg;jpeg;png;gif;bmp;ctx";
+
+ private final java.io.FileFilter imageFilter;
+
+
+ public NextAction(ActionManager m) {
+ super(m);
+
+ // Create an image file filter
+ imageFilter = new SimpleFileFilter(EXTENSIONS);
+ }
+
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ // Get currently opened file
+ File file = window.getContext().getFile();
+
+ // Find the one after & open it
+ File next = findNext(file);
+ if (next != null) {
+ manager.get(OpenAction.class).open(next);
+ }
+ }
+ }
+
+
+ private File findNext(File file) {
+ File dir = file.getParentFile();
+
+ if (dir != null) {
+
+ // List the images
+ File[] files = dir.listFiles(imageFilter);
+
+ // Need at least one
+ if (files.length > 1) {
+
+ // Search for ourself...
+ for (int i = 0; i < files.length; i++) {
+
+ if (files[i].equals(file)) {
+
+ // Use next file (wrap back to first)
+ return files[(i+1)%files.length];
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
+
+class SimpleFileFilter implements java.io.FileFilter {
+
+ /**
+ * Holds the list of extensions. It is expected it will be quite
+ * short, so we use an ArrayList.
+ */
+ private final ArrayList<String> extensions;
+
+
+ /**
+ * Construct a simple file extension filter based on a semicolon separated
+ * list of file extensions.
+ *
+ * @param extensions
+ * A list of file extensions (ex. jpeg;jpg;png)
+ */
+ public SimpleFileFilter(String extensions) {
+ String[] exts = extensions.split(";");
+ this.extensions = new ArrayList<String>(exts.length);
+
+ for (String s : exts) {
+ if ((s = s.trim()).length() != 0) {
+ if (s.startsWith("*")) {
+ s = s.substring(1);
+ }
+
+ if (!s.startsWith(".")) {
+ s = "." + s;
+ }
+
+ this.extensions.add(s);
+ }
+ }
+ }
+
+
+ public boolean accept(File pathname) {
+ if (pathname.isFile()) {
+ String name = pathname.getName();
+
+ // List of extensions should be fairly short..
+ for (String ext : extensions) {
+ if (name.endsWith(ext)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.SegmentationContext;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Open an image or segmentation context.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public OpenAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ File file = getFile();
+ if (file != null) {
+ open(file);
+ }
+ }
+
+
+ public boolean open(File file) {
+ String name = file.getName();
+ try {
+ if (SegmentationContext.isContextFile(file)) {
+
+ // Load context
+ window.setContext(SegmentationContext.load(file));
+ status("Opened segmentation context %s successfully", name);
+ } else {
+
+ // Create context
+ window.setContext(SegmentationContext.create(file));
+ status("Opened image file %s successfully", name);
+ }
+
+ // Save history
+ AppRecentFiles.getInstance().add(file);
+
+ // Ok
+ return true;
+
+ } catch (IOException e) {
+
+ handleError(file, e);
+ status(Warning, "Proplem opening file %s", name);
+
+ return false;
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Get appropriate message
+ String message = e.getCause() == null ? e.getLocalizedMessage() :
+ e.getCause().getLocalizedMessage();
+
+ // Log warning
+ log.warning(String.format(
+ "Unable to open %s\n Problem: %s", file, message
+ ));
+
+ // Show warning dialog
+ warning("Unable to open %s:\n%s", file.getName(), message);
+ }
+
+
+ private void createOpenDialog() {
+ // Create dialog if necessary
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.OPEN | SWT.SHEET);
+ dialog.setText(property("OpenAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+ }
+
+ private String[] getFilters() {
+ return new String[] { property("OpenAction.dialog.filter.exts") };
+ }
+
+ private String[] getFilterNames() {
+ return new String[] { property("OpenAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createOpenDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.exp.*;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Open an experiment file and switch to experiment mode.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenExperimentAction extends AppAction {
+
+ private FileDialog dialog;
+
+
+ public OpenExperimentAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+ File file = getFile();
+ if (file != null) {
+ open(file);
+ }
+ }
+
+
+ private void open(File file) {
+ try {
+ Experiment ex = ExperimentFactory.getInstance().load(file);
+ window.setExperiment(ex);
+
+ } catch (IOException ex) {
+ handleError(file, ex);
+ } catch (FormatException ex) {
+ handleError(file, ex);
+ }
+ }
+
+
+ private void handleError(File file, Exception ex) {
+ // Get appropriate message
+ String mesg = ex.getMessage();
+
+ // Log warning
+ log.warning(String.format(
+ "Unable to open experiment file %s\n Problem: %s", file, mesg
+ ));
+
+ // Show warning dialog
+ warning("Unable to open experiment file %s:\n%s", file.getName(), mesg);
+ }
+
+
+ private File getFile() {
+ createOpenDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void createOpenDialog() {
+ // Create dialog if necessary
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.OPEN | SWT.SHEET);
+ dialog.setText(property("OpenExperimentAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+ }
+ }
+
+ private String[] getFilters() {
+ return new String[] {
+ property("OpenExperimentAction.dialog.filter.exts")
+ };
+ }
+
+ private String[] getFilterNames() {
+ return new String[] {
+ property("OpenExperimentAction.dialog.filter.text")
+ };
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+
+/**
+ * Action to open a recent file.
+ *
+ * @author Kevin McGuinness
+ */
+public class OpenRecentAction extends AppAction {
+ private final File file;
+
+ public OpenRecentAction(ActionManager m, File file) {
+ super(m);
+ this.file = file;
+ setText(file.getName());
+ String path = file.getAbsolutePath();
+ setToolTipText(path);
+ setDescription(String.format("Open file %s", path));
+ }
+
+ public void run() {
+ manager.get(OpenAction.class).open(file);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.dialogs.*;
+
+/**
+ * Show preferences dialog box.
+ *
+ * @author Kevin McGuinness
+ */
+public class PreferencesAction extends AppAction {
+ private PrefsDialog dialog;
+
+ public PreferencesAction(ActionManager m) {
+ super(m);
+ }
+
+
+ public void run() {
+ if (dialog == null) {
+ dialog = new PrefsDialog(window);
+ }
+ dialog.open();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+
+/**
+ * Jump to previous file in current directory.
+ *
+ * @author Kevin McGuinness
+ */
+public class PreviousAction extends AppAction {
+ private final java.io.FileFilter imageFilter;
+
+
+ public PreviousAction(ActionManager m) {
+ super(m);
+
+ // Create an image file filter
+ imageFilter = new SimpleFileFilter(NextAction.EXTENSIONS);
+ }
+
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ // Get currently opened file
+ File file = window.getContext().getFile();
+
+ // Find the one before & open it
+ File next = findPrevious(file);
+ if (next != null) {
+ manager.get(OpenAction.class).open(next);
+ }
+ }
+ }
+
+
+
+ private File findPrevious(File file) {
+ File dir = file.getParentFile();
+
+ if (dir != null) {
+
+ // List the images
+ File[] files = dir.listFiles(imageFilter);
+
+ // Need at least one
+ if (files.length > 1) {
+
+ // Search for ourself...
+ for (int i = 0; i < files.length; i++) {
+
+ if (files[i].equals(file)) {
+
+ // Use previous file (wrap back to last)
+ return files[(i > 0) ? i - 1 : files.length - 1];
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.printing.*;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Send current view to printer.
+ *
+ * @author Kevin McGuinness
+ */
+public class PrintAction extends AppAction {
+
+ private static final String JOB_NAME = "Interactive Segmentation Tool";
+ private PrintDialog dialog;
+
+ public PrintAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ status("Printing...");
+ PrinterData data = getPrintData();
+ if (data != null) {
+ print(data);
+ status("Printing complete");
+ } else {
+ status(null);
+ }
+ }
+ }
+
+
+ private PrinterData getPrintData() {
+ createPrintDialog();
+ return dialog.open();
+ }
+
+
+ private void print(PrinterData data) {
+ Printer printer = new Printer(data);
+ try {
+ print(printer);
+
+ } finally {
+ printer.dispose();
+ }
+ }
+
+
+ private void print(Printer printer) {
+ if (printer.startJob(JOB_NAME)) {
+ if (printer.startPage()) {
+ GC gc = new GC(printer);
+ try {
+ paint(gc, printer);
+ } finally {
+ gc.dispose();
+ }
+ printer.endPage();
+ }
+ printer.endJob();
+ }
+ }
+
+
+ private void paint(GC gc, Printer printer) {
+ Image im = paintContext();
+ try {
+ gc.drawImage(im, 30, 30);
+ } finally {
+ im.dispose();
+ }
+ }
+
+
+ private void createPrintDialog() {
+ if (dialog == null) {
+ dialog = new PrintDialog(window.getShell(), SWT.SHEET);
+ }
+ }
+
+
+ private Image paintContext() {
+ SegmentationView view = window.getView();
+ SegmentationContext ctx = window.getContext();
+ SegmentationPainter painter = view.getPainter();
+ ObservableImage im = createCompatibleImage(ctx);
+ painter.paint(ctx, im);
+ return im.getImage();
+ }
+
+
+ private ObservableImage createCompatibleImage(SegmentationContext ctx) {
+ Image im = new Image(Display.getCurrent(), ctx.getBounds());
+ return new ObservableImage(im);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+
+/**
+ * Redo last annotation action.
+ *
+ * @author Kevin McGuinness
+ */
+public class RedoAction extends AppAction {
+ public RedoAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canRedo()) {
+ view.redo();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Save segmentation context.
+ *
+ * @author Kevin McGuinness
+ */
+public class SaveAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public SaveAction(ActionManager m) {
+ super(m);
+ }
+
+
+ @Override
+ public void run() {
+
+ if (window.hasContext()) {
+
+ File file;
+ if (window.hasContextFile()) {
+ file = window.getContextFile();
+ } else {
+ file = getFile();
+ }
+
+ if (file != null) {
+ save(file);
+ }
+ }
+ }
+
+
+ private void createSaveDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("SaveAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ dialog.setFileName(getFileName());
+ }
+
+
+ private String getDirectory() {
+ File folder = window.getContext().getFolder();
+ if (folder != null) {
+ return folder.getAbsolutePath();
+ }
+
+ return null;
+ }
+
+
+ private String getFileName() {
+ return window.getContext().getDefaultFilename();
+ }
+
+
+ private String[] getFilters() {
+ return new String[] { property("SaveAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("SaveAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createSaveDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void save(File file) {
+ try {
+ window.getContext().save(file);
+ window.updateWindowTitle();
+ status("Saved context %s successfully", file.getName());
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Log
+ log(Level.SEVERE, "Error saving file", e);
+
+ // Show error dialog
+ error("Error saving segmentation context %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error saving segmentation context %s", file.getName());
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import java.io.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Save segmentation context as.
+ *
+ * @author Kevin McGuinness
+ */
+public class SaveAsAction extends AppAction {
+
+ private FileDialog dialog;
+
+ public SaveAsAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ if (window.hasContext()) {
+ File file = getFile();
+ if (file != null) {
+ save(file);
+ }
+ }
+ }
+
+
+ private void createSaveDialog() {
+ if (dialog == null) {
+ dialog = new FileDialog(window.getShell(), SWT.SAVE | SWT.SHEET);
+ dialog.setText(property("SaveAction.dialog.text"));
+ dialog.setFilterExtensions(getFilters());
+ dialog.setFilterNames(getFilterNames());
+
+ String dir = getDirectory();
+ if (dir != null) {
+ dialog.setFilterPath(dir);
+ }
+ }
+
+ dialog.setFileName(getFileName());
+ }
+
+
+ private String getDirectory() {
+ File folder = window.getContext().getFolder();
+ if (folder != null) {
+ return folder.getAbsolutePath();
+ }
+
+ return null;
+ }
+
+
+
+ private String getFileName() {
+ return window.getContext().getDefaultFilename();
+ }
+
+
+ private String[] getFilters() {
+ return new String[] { property("SaveAction.dialog.filter.exts") };
+ }
+
+
+ private String[] getFilterNames() {
+ return new String[] { property("SaveAction.dialog.filter.text") };
+ }
+
+
+ private File getFile() {
+ createSaveDialog();
+
+ String path = dialog.open();
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+
+
+ private void save(File file) {
+ try {
+ window.getContext().save(file);
+ window.updateWindowTitle();
+ status("Saved context %s successfully", file.getName());
+ } catch (IOException e) {
+ handleError(file, e);
+ }
+ }
+
+
+ private void handleError(File file, IOException e) {
+
+ // Log
+ log(Level.SEVERE, "Error saving file", e);
+
+ // Show error dialog
+ error("Error saving segmentation context %s: %s",
+ file.getName(), e.getLocalizedMessage()
+ );
+
+ // Set status message
+ status(Error, "Error saving segmentation context %s", file.getName());
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.SegmenterRegistry;
+import ie.dcu.apps.ist.views.SegmentationView;
+import ie.dcu.segment.Segmenter;
+
+/**
+ * Segmenter radio buttons.
+ *
+ * @author Kevin McGuinness
+ */
+public class SelectSegmenterAction extends AppAction {
+ private final Segmenter segmenter;
+
+ public SelectSegmenterAction(ActionManager m, SegmenterRegistry r, Segmenter s) {
+ super(m, AS_RADIO_BUTTON);
+ this.segmenter = s;
+ setText(s.getName());
+ setToolTipText(s.getDescription());
+
+ if (!s.isAvailable()) {
+ super.setEnabled(false);
+ setChecked(false);
+
+ String naText = string("NA");
+ setToolTipText(naText);
+ setDescription(naText);
+ } else {
+ setChecked(r.isDefault(s));
+ }
+ }
+
+ public void setEnabled(boolean enabled) {
+ // Disallow enabling unavailable segmentation algorithms
+ if (!enabled || (segmenter.isAvailable() && enabled)) {
+ super.setEnabled(enabled);
+ }
+ }
+
+
+ public Segmenter getSegmenter() {
+ return segmenter;
+ }
+
+
+ @Override
+ public String id() {
+ return segmenter.getClass().getName();
+ }
+
+
+ @Override
+ public void run() {
+ if (isChecked()) {
+ SegmentationView view = window.getView();
+ view.setSegmenter(segmenter);
+ }
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.actions;
+
+import ie.dcu.apps.ist.views.SegmentationView;
+
+/**
+ * Undo last annotation.
+ *
+ * @author Kevin McGuinness
+ */
+public class UndoAction extends AppAction {
+ public UndoAction(ActionManager m) {
+ super(m);
+ }
+
+ @Override
+ public void run() {
+ SegmentationView view = window.getView();
+ if (view.canUndo()) {
+ view.undo();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.controllers;
+
+
+
+import ie.dcu.segment.annotate.*;
+import ie.dcu.swt.*;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.Canvas;
+
+public class AnnotationTool extends MouseMotionAdapter {
+ private final AnnotationManager manager;
+ private ImageControl view;
+ private Canvas canvas;
+
+ private Annotation current;
+ private AnnotationType type;
+ private int lineWidth;
+ private Point last;
+ private boolean invert;
+
+
+ public AnnotationTool(AnnotationManager manager) {
+ this (manager, null);
+ }
+
+ /**
+ *
+ * @param manager
+ * @param view
+ */
+ public AnnotationTool(AnnotationManager manager, ImageControl view) {
+ assert (manager != null);
+
+ this.manager = manager;
+ this.current = null;
+ this.lineWidth = 1;
+ this.type = AnnotationType.Foreground;
+
+ attach(view);
+ }
+
+
+ /**
+ *
+ * @param view
+ */
+ public void attach(ImageControl view) {
+ detach();
+
+ if (view != null) {
+ this.view = view;
+ canvas = view.getCanvas();
+ canvas.addMouseListener(this);
+ canvas.addMouseMoveListener(this);
+ }
+ }
+
+
+
+ public void detach() {
+ if (view != null) {
+ canvas.removeMouseListener(this);
+ canvas.removeMouseMoveListener(this);
+ view = null;
+ canvas = null;
+ current = null;
+ }
+ }
+
+
+ /**
+ * Returns the image control associated with the annotation tool.
+ */
+ public ImageControl getView() {
+ return view;
+ }
+
+
+ /**
+ * @return the line width
+ */
+ public int getLineWidth() {
+ return lineWidth;
+ }
+
+
+ /**
+ * @param width the line width to set
+ */
+ public void setLineWidth(int width) {
+ assert (width > 0);
+ this.lineWidth = width;
+ }
+
+
+ /**
+ * @return the annotation type
+ */
+ public AnnotationType getType() {
+ return type;
+ }
+
+
+ /**
+ * @param type the annotation type to set
+ */
+ public void setType(AnnotationType type) {
+ assert (type != null);
+ this.type = type;
+ }
+
+
+
+ public void begin(Point pt) {
+
+ // Create a new annotation
+ current = new Annotation(getTypeForUse(), lineWidth, view.canvasToImage(pt));
+
+ // Give immediate visual feedback
+ feedback(pt);
+
+ // Set last
+ last = pt;
+ }
+
+
+ public void append(Point pt) {
+
+ // Update the annotation
+ current.add(view.canvasToImage(pt));
+
+ // Give immediate visual feedback
+ feedback(last, pt);
+
+ // Set last
+ last = pt;
+ }
+
+
+ public void commit(Point pt) {
+ // Clip points to image bounds
+ clip(last, pt);
+
+ // Add point to annotation
+ current.add(view.canvasToImage(pt));
+
+ // Add annotation to annotation manager
+ manager.add(current);
+
+ // Give immediate visual feedback
+ feedback(last, pt);
+
+ // Mark current annotation as null
+ current = null;
+ }
+
+
+ public void cancel() {
+
+ // Repaint dirty area
+ view.repaint(current.getBounds());
+
+ // Discard annotation
+ current = null;
+ }
+
+
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ ObservableImage image = view.getImage();
+ if (image != null) {
+
+ if (e.button == 1 || e.button == 3) {
+
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+
+ // Check for right mouse button or ctrl modifier
+
+ if (e.button == 3 || (e.stateMask & SWT.CTRL) != 0) {
+ // Invert type
+ invert = true;
+ } else {
+ invert = false;
+ }
+
+ if (current != null) {
+ // Cancel last action
+ cancel();
+ }
+
+ begin(pt);
+ }
+ }
+ }
+
+ return;
+ }
+
+
+ @Override
+ public void mouseMove(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ if (current != null) {
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+ // Normal case: append
+ append(pt);
+
+ } else {
+ // Line drawn outside image
+
+ if (view.getImage() != null) {
+
+ commit(pt);
+
+ } else {
+ // No image: cancel
+ cancel();
+ }
+ }
+ }
+
+ }
+
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ if (view == null || !view.isEnabled()) {
+ current = null;
+ return;
+ }
+
+ if (current != null) {
+ Point pt = new Point(e.x, e.y);
+
+ if (view.imageContains(pt)) {
+
+ // Normal case: commit
+ commit(pt);
+ } else {
+
+ // Line drawn outside image
+ if (view.getImage() != null) {
+
+ commit(pt);
+ } else {
+
+ // No image
+ cancel();
+ }
+ }
+ }
+ }
+
+
+ private void clip(Point p, Point q) {
+ SwtUtils.clip(view.getCanvasImageBounds(), p, q);
+ }
+
+
+ private GC createFeedbackGC() {
+ GC gc = new GC(canvas);
+ gc.setAntialias(SWT.ON);
+ gc.setLineCap(SWT.CAP_ROUND);
+ gc.setLineJoin(SWT.JOIN_ROUND);
+ gc.setLineWidth((int) Math.floor(lineWidth * view.getZoom()));
+ gc.setForeground(getTypeForUse().getColor());
+ return gc;
+ }
+
+
+ private void feedback(Point p1) {
+ feedback(p1, new Point(p1.x+1, p1.y));
+ }
+
+
+ private void feedback(Point p1, Point p2) {
+ GC gc = createFeedbackGC();
+ gc.setClipping(getClip(p1, p2));
+ gc.drawLine(p1.x, p1.y, p2.x, p2.y);
+ gc.dispose();
+ }
+
+
+ private Rectangle getClip(Point p1, Point p2) {
+ int x1 = Math.min(p1.x, p2.x) - lineWidth;
+ int x2 = Math.max(p1.x, p2.x) + lineWidth;
+ int y1 = Math.min(p1.y, p2.y) - lineWidth;
+ int y2 = Math.max(p1.y, p2.y) + lineWidth;
+ Rectangle r = new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ return r.intersection(view.getCanvasImageBounds());
+ }
+
+
+ private AnnotationType getTypeForUse() {
+ return (invert) ? type.invert() : type;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.controllers;
+
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+
+public class MouseMotionAdapter extends MouseAdapter implements MouseMoveListener {
+
+ public MouseMotionAdapter() {
+ }
+
+
+ public MouseMotionAdapter(Control control) {
+ add(control);
+ }
+
+
+ public void add(Control control) {
+ control.addMouseListener(this);
+ control.addMouseMoveListener(this);
+ }
+
+
+ public void mouseMove(MouseEvent e) {
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.dialogs;
+
+import ie.dcu.apps.ist.export.imagemap.AreaShape;
+import ie.dcu.apps.ist.export.imagemap.RolloverEffect;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import java.io.File;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.MessageBox;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class ExportDialog extends Dialog {
+
+ public class Result {
+ public final File folder;
+ public final String html;
+ public final String image;
+ public final String link;
+ public final String description;
+ public final AreaShape shape;
+ public final RolloverEffect effect;
+ public final boolean open;
+
+ Result() {
+ folder = new File(folderText.getText().trim());
+ html = htmlText.getText().trim();
+ image = imageText.getText().trim();
+ link = linkText.getText().trim();
+ description = descriptionText.getText();
+ shape = getShape();
+ effect = getEffect();
+ open = openButton.getSelection();
+ }
+ };
+
+ // Result
+ private Result result;
+
+ // Top level components
+ private Shell shell;
+ private Composite content;
+ private Composite widgets;
+ private Composite buttons;
+
+ // Widgets
+ private Text folderText;
+ private Button browseButton;
+ private Text htmlText;
+ private Text imageText;
+ private Text linkText;
+ private Button effectCheck;
+ private Combo effectCombo;
+ private Button[] shapeRadios;
+ private Text descriptionText;
+ private Button openButton;
+
+ // Dialog buttons
+ private Button cancelButton;
+ private Button exportButton;
+
+ // Effects
+ private final String[] ROLLOVER_EFFECTS = {
+ "Outline Object", "Darken Background", "Highlight Object"
+ };
+
+ // Shapes
+ private final String[] SHAPES = {
+ "Polygon", "Rectangle", "Circle"
+ };
+
+ // Default title
+ private static final String TITLE = "Export HTML Image Map";
+
+ public ExportDialog(Shell shell) {
+ super(shell);
+ setText(TITLE);
+ }
+
+ public ExportDialog(Shell shell, int style) {
+ super(shell, style);
+ setText(TITLE);
+ }
+
+ public Result open() {
+ Shell parent = getParent();
+ shell = new Shell(parent, SWT.DIALOG_TRIM |
+ SWT.APPLICATION_MODAL | SWT.SHEET);
+ shell.setText(getText());
+ shell.setLayout(new FillLayout());
+
+ createUI();
+
+ shell.pack();
+
+ SwtUtils.center(parent, shell);
+
+ shell.open();
+ Display display = parent.getDisplay();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) display.sleep();
+ }
+
+ return result;
+ }
+
+ private void createUI() {
+ // Overall layout
+ content = new Composite(shell, 0);
+ content.setLayout(LayoutFactory.createGridLayout(0, 0, 1, false));
+ widgets = new Composite(content, 0);
+ widgets.setLayout(LayoutFactory.createGridLayout(10, 5, 3, false));
+ widgets.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ hline(content);
+ buttons = new Composite(content, 0);
+ buttons.setLayout(LayoutFactory.createGridLayout(10, 5, 3, false));
+ buttons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+
+ // Form content
+ label(widgets, "Export folder:");
+ folderText = text(widgets, "");
+ browseButton = button(widgets, "Browse...");
+ label(widgets, "HTML file name:");
+ htmlText = text(widgets, "imagemap.html");
+ spacer(widgets);
+ label(widgets, "Image file name:");
+ imageText = text(widgets, "image.png");
+ spacer(widgets);
+ hline(widgets);
+ label(widgets, "Export shape:");
+ shapeRadios = radios(widgets, SHAPES);
+ // TODO: Enable when available
+ shapeRadios[2].setEnabled(false);
+ spacer(widgets);
+ label(widgets, "Object link:");
+ linkText = text(widgets, "http://");
+ spacer(widgets);
+ label(widgets, "Object description:");
+ descriptionText = text(widgets, "");
+ spacer(widgets);
+ effectCheck = checkbox(widgets, "Rollover effect:", true, 1);
+ effectCombo = combo(widgets, ROLLOVER_EFFECTS);
+ spacer(widgets);
+ hline(widgets);
+ openButton = checkbox(widgets,
+ "Open image map in browser after export completes", true, 3);
+
+ // Button bar
+ expander(buttons);
+ cancelButton = new Button(buttons, SWT.PUSH);
+ cancelButton.setLayoutData(layout4());
+ cancelButton.setText("Cancel");
+ exportButton = new Button(buttons, SWT.PUSH);
+ exportButton.setLayoutData(layout4());
+ exportButton.setText("Export");
+
+ // Set the default button
+ shell.setDefaultButton(exportButton);
+
+ // Add listeners
+ addListeners();
+ }
+
+ private void addListeners() {
+
+ browseButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ String dir = getDirectory();
+ if (dir != null) {
+ folderText.setText(dir);
+ }
+ }
+ });
+
+ cancelButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ result = null;
+ shell.dispose();
+ }
+ });
+
+ exportButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ if (validate()) {
+ result = new Result();
+ shell.dispose();
+ }
+ }
+ });
+
+ effectCheck.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ effectCombo.setEnabled(effectCheck.getSelection());
+ }
+ });
+ }
+
+ private boolean validate() {
+
+ File folder = new File(folderText.getText());
+ if (!folder.isDirectory()) {
+ validationError("The export folder must be specified");
+ folderText.setFocus();
+ return false;
+ }
+
+ String htmlFile = htmlText.getText().trim();
+ if (htmlFile.equals("")) {
+ validationError("The HTML file name cannot be empty");
+ htmlText.setFocus();
+ return false;
+ }
+
+ String imageFile = imageText.getText().trim();
+ if (imageFile.equals("")) {
+ validationError("The image file name cannot be empty");
+ imageText.setFocus();
+ return false;
+ }
+
+ return true;
+ }
+
+ private void validationError(String message) {
+ MessageBox box = new MessageBox(shell, SWT.OK | SWT.ICON_INFORMATION);
+ box.setMessage(message);
+ box.setText("Invalid Input");
+ box.open();
+ }
+
+ private AreaShape getShape() {
+ for (Button bt : shapeRadios) {
+ if (bt.getSelection()) {
+ return AreaShape.valueOf(bt.getText());
+ }
+ }
+ return null;
+ }
+
+ private RolloverEffect getEffect() {
+ RolloverEffect effect = null;
+ if (effectCheck.getSelection()) {
+ String text = effectCombo.getText();
+ if (text.equals("Outline Object")) {
+ effect = RolloverEffect.outlineObject();
+ } else if (text.equals("Darken Background")) {
+ effect = RolloverEffect.darkenBackground();
+ } else if (text.equals("Highlight Object")) {
+ effect = RolloverEffect.brightenForeground();
+ }
+ }
+ return effect;
+ }
+
+ private String getDirectory() {
+ DirectoryDialog dialog = new DirectoryDialog(shell);
+ return dialog.open();
+ }
+
+ private Button[] radios(Composite parent, String ... items) {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(LayoutFactory.createGridLayout(5, 10, items.length, false));
+ c.setLayoutData(layout2());
+
+ Button[] bts = new Button[items.length];
+ for (int i = 0; i < bts.length; i++) {
+ bts[i] = new Button(c, SWT.RADIO);
+ bts[i].setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
+ bts[i].setText(items[i]);
+ bts[i].setSelection(i == 0);
+ }
+
+ return bts;
+ }
+
+ private Label hline(Composite parent) {
+ Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
+ data.horizontalSpan = 3;
+ data.heightHint = 10;
+ label.setLayoutData(data);
+ return label;
+ }
+
+ private Label spacer(Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ return label;
+ }
+
+ private Label expander(Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ return label;
+ }
+
+ private Combo combo(Composite parent, String ... items) {
+ Combo combo = new Combo(parent, SWT.READ_ONLY);
+ combo.setItems(items);
+ combo.setLayoutData(layout2());
+ if (items.length > 0) {
+ combo.select(0);
+ }
+ return combo;
+ }
+
+ private Button checkbox(Composite parent, String text,
+ boolean checked, int span) {
+
+ Button bt = new Button(parent, SWT.CHECK);
+ bt.setText(text);
+ bt.setSelection(checked);
+ GridData data = layout1();
+ data.horizontalSpan = span;
+ bt.setLayoutData(data);
+ return bt;
+ }
+
+ private Label label(Composite parent, String text) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(text);
+ label.setLayoutData(layout1());
+ return label;
+ }
+
+ private Text text(Composite parent, String value) {
+ Text text = new Text(parent, SWT.BORDER | SWT.SINGLE);
+ text.setText(value);
+ text.setLayoutData(layout2());
+ return text;
+ }
+
+ private Button button(Composite parent, String text) {
+ Button bt = new Button(parent, SWT.PUSH);
+ bt.setText(text);
+ bt.setLayoutData(layout3());
+ return bt;
+ }
+
+ private GridData layout1() {
+ return new GridData(SWT.LEFT, SWT.CENTER, false, false);
+ }
+
+ private GridData layout2() {
+ return new GridData(SWT.FILL, SWT.CENTER, true, false);
+ }
+
+ private GridData layout3() {
+ return new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ }
+
+ private GridData layout4() {
+ return new GridData(SWT.RIGHT, SWT.BOTTOM, false, false);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.dialogs;
+
+import static ie.dcu.segment.annotate.AnnotationType.*;
+import ie.dcu.apps.ist.*;
+import ie.dcu.apps.ist.AppPrefs.Keys;
+import ie.dcu.apps.ist.widgets.ColorSelector;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.swt.layout.LayoutFactory;
+
+import java.util.*;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public class PrefsDialog {
+ private static final String APPLICATION = "application";
+ private static final String STATUS = "status";
+ private static final String VIEW = "view";
+ private static final String TITLE = "title";
+ private static final String GENERAL = "general";
+ private static final String CLOSE = "close";
+ private static final String RESET = "reset";
+ private static final String TEXT = "text";
+ private static final String ICON = "icon";
+ private static final String TTIP = "ttip";
+ private static final String EXPERIMENT = "experiment";
+
+ private final AppWindow window;
+ private final AppPrefs prefs;
+ private final Shell shell;
+ private final Composite content;
+ private final TabFolder folder;
+ private final Composite buttonBar;
+ private final Composite generalTab;
+ private final Button resetButton;
+ private final Button closeButton;
+ private final HashMap<String, Object> controls;
+
+ private transient GridData gd;
+
+
+ public PrefsDialog(AppWindow window) {
+ this.window = window;
+ this.prefs = window.getPrefs();
+ this.shell = createShell();
+ this.content = createContent();
+ this.folder = createTabFolder();
+ this.buttonBar = createButtonBar();
+ this.generalTab = createTab(GENERAL);
+ this.resetButton = createResetButton();
+ this.closeButton = createCloseButton();
+ this.controls = new HashMap<String, Object>();
+
+ createControls();
+ loadPreferences();
+
+ SwtUtils.center(window.getShell(), shell);
+ }
+
+
+ private void createControls() {
+ // Initialize general tab
+ createBanner(generalTab, APPLICATION);
+
+ createLabel(generalTab, Keys.CONFIRM_EXIT);
+ createCheckBox(generalTab, Keys.CONFIRM_EXIT);
+
+ createBanner(generalTab, STATUS);
+
+ createLabel(generalTab, Keys.ENABLE_FEEDBACK);
+ createCheckBox(generalTab, Keys.ENABLE_FEEDBACK);
+
+ createBanner(generalTab, VIEW);
+
+ createLabel(generalTab, Keys.FOREGROUND_COLOR);
+ createColorSelector(generalTab, Keys.FOREGROUND_COLOR);
+
+ createLabel(generalTab, Keys.BACKGROUND_COLOR);
+ createColorSelector(generalTab, Keys.BACKGROUND_COLOR);
+
+ createBanner(generalTab, EXPERIMENT);
+
+ createLabel(generalTab, Keys.EXPERIMENT_EMBEDDED);
+ createCheckBox(generalTab, Keys.EXPERIMENT_EMBEDDED);
+ }
+
+
+ private void loadPreferences() {
+
+ // Load confirm exit
+ loadBool(Keys.CONFIRM_EXIT, true);
+
+ // Load enable feedback
+ loadBool(Keys.ENABLE_FEEDBACK, true);
+
+ // Load foreground color
+ loadColor(Keys.FOREGROUND_COLOR, DEFAULT_FOREGROUND);
+
+ // Load background color
+ loadColor(Keys.BACKGROUND_COLOR, DEFAULT_BACKGROUND);
+
+ // Load experiment embedded
+ loadBool(Keys.EXPERIMENT_EMBEDDED, false);
+ }
+
+
+ private Shell createShell() {
+ Shell s = new Shell(window.getShell(), SWT.SHELL_TRIM | SWT.SHEET);
+
+ // Add listener
+ s.addListener(SWT.Close, adapter);
+
+ // Use a fill layout
+ s.setLayout(new FillLayout());
+
+ // Set size and title
+ s.setSize(420, 450);
+ s.setText(property(TITLE, "Preferences"));
+
+ return s;
+ }
+
+
+ private Composite createContent() {
+
+ // Create content pane with grid layout
+ Composite c = new Composite(shell, 0);
+ c.setLayout(new GridLayout());
+ return c;
+ }
+
+
+ private TabFolder createTabFolder() {
+ // Create folder
+ TabFolder f = new TabFolder(content, SWT.TOP);
+
+ // Layout folder
+ f.setLayoutData(LayoutFactory.createGridData());
+
+ return f;
+ }
+
+
+ private Composite createButtonBar() {
+ // Create bar
+ Composite bar = new Composite(content, SWT.NONE);
+
+ // Layout bar
+ bar.setLayout(LayoutFactory.createGridLayout(0, 0, 2, true));
+ bar.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
+
+ return bar;
+ }
+
+
+ private Composite createTab(String name) {
+ // Create item
+ TabItem item = new TabItem(folder, SWT.NONE);
+ item.setText(property(name, "tab", name));
+
+ // Create control
+ Composite control = new Composite(folder, SWT.NONE);
+ control.setLayout(LayoutFactory.createGridLayout(10, 5, 2, false));
+
+ // Set control
+ item.setControl(control);
+
+ // Return control (item can be ignored)
+ return control;
+ }
+
+
+ private Button createResetButton() {
+ // Create button
+ Button bt = createButton(buttonBar, RESET, SWT.PUSH);
+
+ // Layout button
+ gd = new GridData(SWT.LEFT, SWT.BOTTOM, true, false);
+ gd.minimumWidth = 85;
+ bt.setLayoutData(gd);
+
+ // Done
+ return bt;
+ }
+
+
+ private Button createCloseButton() {
+ // Create button
+ Button bt = createButton(buttonBar, CLOSE, SWT.PUSH);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.BOTTOM, true, false);
+ gd.minimumWidth = 85;
+ bt.setLayoutData(gd);
+
+ // Done
+ return bt;
+ }
+
+
+ private Button createCheckBox(Composite tab, String key) {
+ Button bt = new Button(tab, SWT.CHECK);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ gd.horizontalIndent = 5;
+ bt.setLayoutData(gd);
+
+ // Add listener
+ bt.addListener(SWT.Selection, adapter);
+ bt.setData(key);
+
+ // Put into controls
+ controls.put(key, bt);
+
+ // Done
+ return bt;
+ }
+
+
+ private ColorSelector createColorSelector(Composite tab, String key) {
+ ColorSelector cs = new ColorSelector(tab);
+
+ // Layout button
+ gd = new GridData(SWT.RIGHT, SWT.CENTER, false, false);
+ gd.horizontalIndent = 10;
+ gd.minimumWidth = 80;
+ cs.setLayoutData(gd);
+
+ // Add listener
+ cs.addListener(SWT.Selection, adapter);
+ cs.setData(key);
+
+ // Put into controls
+ controls.put(key, cs);
+
+ // Done
+ return cs;
+ }
+
+
+ private Label createBanner(Composite tab, String key) {
+ // Create label
+ Label lb = new Label(tab, SWT.NONE);
+ lb.setText(property(key, key));
+ lb.setFont(JFaceResources.getBannerFont());
+
+ // Layout label
+ gd = new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1);
+ gd.verticalIndent = 5;
+ lb.setLayoutData(gd);
+
+ return lb;
+ }
+
+
+ private Label createLabel(Composite tab, String key) {
+ // Create label
+ Label lb = new Label(tab, SWT.NONE);
+ lb.setText(property(key, TEXT, key));
+
+ // Layout label
+ gd = new GridData(SWT.LEFT, SWT.CENTER, true, false);
+ gd.horizontalIndent = 5;
+ lb.setLayoutData(gd);
+
+ return lb;
+ }
+
+
+ private Button createButton(Composite parent, String key, int style) {
+ Button bt = new Button(parent, style);
+
+ // Configure button
+ configure(bt, key);
+
+ // Add listener
+ bt.addListener(SWT.Selection, adapter);
+
+ // Done
+ return bt;
+ }
+
+
+ protected void handleEvent(Event event) {
+ switch (event.type) {
+ case SWT.Close:
+ handleClose(event);
+ break;
+ case SWT.Selection:
+ handleSelection(event);
+ break;
+ }
+
+ }
+
+
+ private void handleClose(Event event) {
+ // Prevent the shell from disposing on close
+ event.doit = false;
+ close();
+ }
+
+
+ private void handleSelection(Event event) {
+ if (event.widget == closeButton) {
+ close();
+ } else if (event.widget == resetButton) {
+ reset();
+ } else {
+ Object data = event.widget.getData();
+ if (data instanceof String) {
+ handleSelection(event, (String) data);
+ }
+ }
+
+ }
+
+
+ private void handleSelection(Event event, String key) {
+
+ if (key.equals(Keys.ENABLE_FEEDBACK)) {
+ storeBool(key);
+
+ } else if (key.equals(Keys.FOREGROUND_COLOR)) {
+
+ storeColor(key);
+
+ } else if (key.equals(Keys.BACKGROUND_COLOR)) {
+
+ storeColor(key);
+
+ } else if (key.equals(Keys.EXPERIMENT_EMBEDDED)) {
+
+ storeBool(key);
+
+ } else if (key.equals(Keys.CONFIRM_EXIT)) {
+
+ storeBool(key);
+ }
+
+ }
+
+
+ private void storeBool(String key) {
+ Boolean bool = control(Button.class, key).getSelection();
+ prefs.put(Boolean.class, key, bool);
+ }
+
+
+ private void storeColor(String key) {
+ RGB color = control(ColorSelector.class, key).getColor();
+ if (color != null) {
+ prefs.put(RGB.class, key, color);
+ }
+ }
+
+
+ private void loadBool(String key, boolean def) {
+ Boolean bool = prefs.get(Boolean.class, key, def);
+ control(Button.class, key).setSelection(bool);
+ }
+
+
+ private void loadColor(String key, RGB def) {
+ RGB color = prefs.get(RGB.class, key, def);
+ control(ColorSelector.class, key).setColor(color);
+ }
+
+
+ public void reset() {
+ // Set defaults
+ prefs.clear();
+
+ // Reload
+ loadPreferences();
+ }
+
+
+ public void close() {
+ shell.setVisible(false);
+ }
+
+
+ public void open() {
+ shell.setVisible(true);
+ }
+
+
+ private void configure(Button bt, String key) {
+ // Read properties
+ String text = property(key, TEXT, "");
+ String icon = property(key, ICON, null);
+ String ttip = property(key, TTIP, null);
+
+ // Set values
+ bt.setText(text);
+ bt.setImage(image(icon));
+ bt.setToolTipText(ttip);
+ bt.setData(key);
+ }
+
+
+ private <T> T control(Class<T> clazz, String key) {
+ return clazz.cast(controls.get(key));
+ }
+
+
+ private String property(String key, String def) {
+ Properties p = window.getProperties();
+ return p.getProperty(String.format("PrefsDialog.%s", key), def);
+ }
+
+
+ private String property(String key, String type, String def) {
+ Properties p = window.getProperties();
+ String prop = String.format("PrefsDialog.%s.%s", key, type);
+ return p.getProperty(prop, def);
+ }
+
+
+ private Image image(String url) {
+ return (url != null) ? window.getIcon(url) : null;
+ }
+
+
+ private final Listener adapter = new Listener() {
+ public void handleEvent(Event event) {
+ PrefsDialog.this.handleEvent(event);
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public interface ContextChangeListener extends EventListener {
+ public void contextChanged(ContextChangedEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.segment.SegmentationContext;
+
+import java.util.*;
+
+
+public class ContextChangedEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public final SegmentationContext oldContext;
+ public final SegmentationContext newContext;
+
+ public ContextChangedEvent(
+ Object source,
+ SegmentationContext oldContext,
+ SegmentationContext newContext
+ ) {
+ super(source);
+ this.oldContext = oldContext;
+ this.newContext = newContext;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public class StateEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public StateEvent(Object source) {
+ super(source);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public interface StateListener extends EventListener {
+ public void stateChanged(StateEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.apps.ist.widgets.Ticker;
+
+import java.util.EventObject;
+
+public class TickerEvent extends EventObject {
+ private static final long serialVersionUID = 755693053854019177L;
+
+ private final long elapsed;
+
+ public TickerEvent(Ticker t, long elapsed) {
+ super(t);
+ this.elapsed = elapsed;
+ }
+
+ public Ticker getTicker() {
+ return (Ticker) getSource();
+ }
+
+ public long getElapsed() {
+ return elapsed;
+ }
+};
--- /dev/null
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+public interface TickerListener extends EventListener {
+ public void tick(TickerEvent evt);
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+
+import ie.dcu.apps.ist.widgets.SwtTimer;
+
+import java.util.EventObject;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class TimeoutEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public TimeoutEvent(SwtTimer source) {
+ super(source);
+ }
+
+ public SwtTimer getTimer() {
+ return (SwtTimer) getSource();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.event;
+
+import java.util.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public interface TimeoutListener extends EventListener {
+ public void timeoutOccured(TimeoutEvent evt);
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import ie.dcu.eval.Evaluator;
+import ie.dcu.apps.ist.*;
+import ie.dcu.segment.Segmenter;
+
+import java.io.*;
+import java.util.*;
+
+public class Experiment {
+ public static final int MAX_TASKS = 2500;
+ public static final int MAX_SEGMENTERS = 50;
+ public static final int MAX_EVALUATORS = 100;
+
+ private String name;
+ private File dir;
+ private File outputFile;
+ private int time;
+ private List<Segmenter> segmenters;
+ private List<Evaluator> evaluators;
+ private List<Task> tasks;
+ private StorageSelection saveUserMasks;
+ private File saveUserMasksDir;
+
+ Experiment() {
+
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+
+ public File getDirectory() {
+ return dir;
+ }
+
+
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+
+ public int getTime() {
+ return time;
+ }
+
+
+ public StorageSelection getStorageSelection() {
+ return saveUserMasks;
+ }
+
+
+ public File getStorageDirectory() {
+ return saveUserMasksDir;
+ }
+
+
+ public List<Segmenter> getSegmenters() {
+ return Collections.unmodifiableList(segmenters);
+ }
+
+
+ public List<Evaluator> getEvaluators() {
+ return Collections.unmodifiableList(evaluators);
+ }
+
+
+ public List<Task> getTasks() {
+ return Collections.unmodifiableList(tasks);
+ }
+
+
+ public boolean using(Segmenter segmenter) {
+ for (Segmenter s : segmenters) {
+ if (s.equals(segmenter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public void load(File file) throws IOException, FormatException {
+ InputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ load(in);
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+
+
+ public void load(InputStream in) throws IOException, FormatException {
+
+ // Create properties object
+ Properties props = new Properties();
+ props.load(in);
+
+ // Load name
+ name = props.getProperty("name", "Experiment");
+
+ // Load directory
+ dir = getDir(props, "dir");
+
+ // Load output file
+ outputFile = getOutputFile(props);
+
+ // Load task time
+ time = getTimeout(props, "time");
+
+ // Get save user masks selection
+ saveUserMasks = getStorageSelection(props, "save-user-masks");
+
+ // .. and directory
+ saveUserMasksDir = getUserMaskDir(props, "save-user-masks.dir");
+
+ // Load segmenters
+ segmenters = new ArrayList<Segmenter>();
+ for (int i = 1; i <= MAX_SEGMENTERS; i++) {
+ String key = "segmenters." + i;
+ String val = props.getProperty(key);
+
+ if (val == null) {
+ break;
+ }
+
+ Segmenter segmenter = findSegmenter(val);
+ if (segmenter != null) {
+ segmenters.add(segmenter);
+ } else {
+ exception("Segmenter not found: <%s>", val);
+ }
+ }
+
+ // Load evaluators
+ evaluators = new ArrayList<Evaluator>();
+ for (int i = 1; i <= MAX_EVALUATORS; i++) {
+ String key = "evaluators." + i;
+ String val = props.getProperty(key);
+
+ if (val == null) {
+ break;
+ }
+
+ Evaluator evaluator = findEvaluator(val);
+ if (evaluator != null) {
+ evaluators.add(evaluator);
+ } else {
+ exception("Evaluator not found: <%s>", val);
+ }
+ }
+
+ // Load tasks
+ tasks = new ArrayList<Task>();
+ for (int i = 1; i <= MAX_TASKS; i++) {
+
+ // Form keys
+ String imkey = String.format("task.%d.im", i);
+ String gtkey = String.format("task.%d.gt", i);
+ String desckey = String.format("task.%d.description", i);
+
+ // Check if we're done
+ if (props.getProperty(imkey) == null) {
+ break;
+ }
+
+ // Get image and ground truth file
+ File im = getFile(props, imkey);
+ File gt = getFile(props, gtkey);
+
+ // Get description
+ String desc = getDescription(props, desckey);
+
+ // Add task
+ tasks.add(new Task(im, gt, desc));
+ }
+ }
+
+
+ private static StorageSelection getStorageSelection(
+ Properties props, String key
+ ) {
+ String prop = props.getProperty(key);
+ if (prop != null) {
+ StorageSelection selection = StorageSelection.parse(prop);
+ if (selection != null) {
+ return selection;
+ }
+ }
+ return StorageSelection.None;
+ }
+
+
+ private File getUserMaskDir(Properties props, String key)
+ throws FormatException {
+
+ String fname = props.getProperty(key);
+ if (fname == null) {
+ fname = "user";
+ }
+
+ File file = new File(fname);
+ if (!file.isAbsolute()) {
+ file = new File(dir, fname);
+ }
+
+
+ if (!file.exists()) {
+ // Try and create it
+ if (!file.mkdirs()) {
+ String path = file.getAbsolutePath();
+ exception("Couldn't create directory %s", path);
+ }
+
+ } else if (!file.isDirectory()) {
+ String path = file.getAbsolutePath();
+ exception("%s is not a directory", path);
+ }
+
+ return file;
+ }
+
+
+ private static Segmenter findSegmenter(String name) {
+ return SegmenterRegistry.getInstance().find(name);
+ }
+
+
+ private static Evaluator findEvaluator(String name) {
+ return EvaluatorRegistry.getInstance().find(name);
+ }
+
+
+ private File getOutputFile(Properties props) {
+ String fname = props.getProperty("output", "output.txt");
+ File file = new File(fname);
+ if (file.isAbsolute()) {
+ return file;
+ }
+ return new File(dir, fname);
+ }
+
+
+
+ private static String getDescription(Properties props, String key)
+ throws FormatException {
+
+ String desc = props.getProperty(key);
+ if (desc == null) {
+ exception("Expected description property for %s", key);
+ }
+ return desc;
+ }
+
+
+ private File getFile(Properties props, String key)
+ throws FormatException {
+
+ String fname = props.getProperty(key);
+
+ if (fname == null) {
+ exception("Expected file property for %s", key);
+ }
+
+ File file = new File(fname);
+ if (file.isAbsolute()) {
+ if (file.isFile()) {
+ return file;
+ }
+ }
+
+ file = new File(dir, fname);
+ if (!file.isFile()) {
+ exception("File not found %s", file.getAbsolutePath());
+ }
+
+ return file;
+ }
+
+
+ private static File getDir(Properties props, String key)
+ throws FormatException {
+
+ String prop = props.getProperty(key);
+ if (prop == null) {
+ exception("Required directory element not found: %a",key);
+ }
+
+ File file = new File(prop);
+ if (!file.isDirectory()) {
+ exception("%s is not a directory", prop);
+ }
+
+ return file;
+ }
+
+
+ private static int getTimeout(Properties props, String key)
+ throws FormatException {
+
+ String prop = props.getProperty(key, "120");
+ try {
+ return Integer.parseInt(prop);
+ } catch (NumberFormatException ex) {
+ exception("%s is not an integer", key);
+ }
+
+ return 0;
+ }
+
+
+ private static void exception(String format, Object ... args)
+ throws FormatException
+ {
+ throw new FormatException(String.format(format, args));
+ }
+
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("name=<").append(name).append('>');
+ sb.append(" dir=<").append(dir.getAbsolutePath()).append('>');
+ sb.append(" output=<").append(outputFile.getAbsolutePath()).append('>');
+ sb.append(" time=<").append(time).append('>');
+
+ // Evaluators
+ sb.append(" evaluators={ ");
+ for (Evaluator e : evaluators) {
+ sb.append(e.getName()).append(' ');
+ }
+ sb.append('}');
+
+ // Tasks
+ sb.append(" tasks={ ");
+ for (Task t : tasks) {
+ sb.append(t).append(' ');
+ }
+ sb.append('}');
+
+ return sb.toString();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class ExperimentFactory {
+ private static ExperimentFactory instance;
+
+ protected ExperimentFactory() {
+
+ }
+
+ public static ExperimentFactory getInstance() {
+ if (instance == null) {
+ instance = new ExperimentFactory();
+ }
+ return instance;
+ }
+
+
+ public Experiment load(File file)
+ throws IOException, FormatException {
+
+ Experiment ex = new Experiment();
+ ex.load(file);
+ return ex;
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import ie.dcu.eval.Evaluator;
+import ie.dcu.segment.SegmentationMask;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.apps.ist.AppWindow;
+import ie.dcu.apps.ist.actions.*;
+
+import java.io.*;
+import java.util.List;
+
+public class ExperimentManager {
+
+ private final AppWindow window;
+
+ private Experiment experiment;
+ private SegmentationContext activeContext;
+ private SegmentationMask referenceMask;
+ private ExperimentResults results;
+ private List<Task> tasks;
+ private Task activeTask;
+ private int progress;
+ private boolean aborted;
+
+
+ public ExperimentManager(AppWindow window) {
+ this.window = window;
+ }
+
+
+ public void beginExperiment(Experiment ex) throws IOException {
+ experiment = ex;
+ tasks = experiment.getTasks();
+ activeContext = null;
+ activeTask = null;
+ progress = 0;
+ results = new ExperimentResults(experiment.getOutputFile());
+ results.beginDocument();
+ results.beginExperiment(experiment);
+ }
+
+
+ public void endExperiment() throws IOException {
+ results.endExperiment();
+ results.flush();
+ results.close();
+ }
+
+
+ public void abortExperiment() {
+ results.endExperiment();
+ results.close();
+ aborted = true;
+ }
+
+
+ public void beginTask() throws IOException {
+ if (isComplete()) {
+ throw new IllegalStateException();
+ }
+
+ // Set active task
+ activeTask = tasks.get(progress);
+
+ // Load context into main window
+ loadContext();
+
+ // Load reference mask
+ loadReferenceMask();
+
+ // Write task header to results
+ results.beginTask(progress, activeTask);
+ }
+
+
+ public void endTask() throws IOException {
+
+ try {
+ // Save final mask
+ StorageSelection ss = experiment.getStorageSelection();
+ if (ss != StorageSelection.None) {
+ store(String.format("task-%d-final.png", progress));
+ }
+
+ } finally {
+
+ activeTask = null;
+ activeContext = null;
+ progress++;
+ results.endTask();
+ results.flush();
+ }
+ }
+
+
+ public boolean isComplete() {
+ return tasks.size() == progress || aborted;
+ }
+
+
+ public Experiment getExperiment() {
+ return experiment;
+ }
+
+
+ public String getTaskDescription() {
+ return activeTask.getDescription();
+ }
+
+
+ public Task getActiveTask() {
+ return activeTask;
+ }
+
+
+ public int getTaskCount() {
+ return tasks.size();
+ }
+
+
+ public int getProgress() {
+ return progress;
+ }
+
+
+ public SegmentationContext getActiveContext() {
+ return activeContext;
+ }
+
+
+ public List<Evaluator> getEvaluators() {
+ return experiment.getEvaluators();
+ }
+
+
+ public File getImageFile() {
+ return activeTask.getImageFile();
+ }
+
+
+ public SegmentationMask getCurrentMask() {
+ return activeContext.getMask();
+ }
+
+
+ public SegmentationMask getReferenceMask() {
+ return referenceMask;
+ }
+
+
+ public void evaluate(int elapsed) {
+
+ if (!getCurrentMask().isUnknown()) {
+ SegmentationMask im = getCurrentMask();
+ SegmentationMask gt = getReferenceMask();
+
+ results.beginEvaluation(elapsed);
+
+ for (Evaluator evaluator : experiment.getEvaluators()) {
+ // Execute evaluator
+ evaluator.run(im, gt);
+
+ // Add results
+ addResults(evaluator);
+ }
+
+ results.endEvaluation();
+ }
+ }
+
+
+ public void store(int elapsed) throws IOException {
+ // Save current mask
+ if (!getCurrentMask().isUnknown()) {
+ // Only save non empty masks
+ StorageSelection ss = experiment.getStorageSelection();
+ if (ss == StorageSelection.All) {
+ store(String.format("task-%d-time-%d.png", progress, elapsed));
+ }
+ }
+ }
+
+
+ private void store(String name) throws IOException {
+ SegmentationMask im = getCurrentMask();
+ File dir = experiment.getStorageDirectory();
+ File file = new File(dir, name);
+ im.save(file);
+ }
+
+
+ private void addResults(Evaluator evaluator) {
+ String[] measures = evaluator.getMeasures();
+ for (String measure : measures) {
+ double value = evaluator.getMeasure(measure);
+ results.addMeasure(measure, value);
+ }
+ }
+
+
+ private void loadReferenceMask() throws IOException {
+ referenceMask = new SegmentationMask(activeContext.getBounds());
+ referenceMask.load(activeTask.getMaskFile());
+ }
+
+
+ private void loadContext() throws IOException {
+ ActionManager actions = window.getActions();
+ OpenAction action = actions.get(OpenAction.class);
+
+ if (!action.open(getImageFile())) {
+ throw new IOException("Unable to load image file");
+ }
+
+ activeContext = window.getContext();
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+public class ExperimentResults {
+ private static final String XML_HEADER =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n%n";
+
+ private static final String BEGIN_EXPERIMENT_TAG =
+ "<experiment name=\"%s\" time=\"%d\" dir=\"%s\">%n";
+
+ private static final String BEGIN_TASK_TAG =
+ " <task id=\"%d\" image=\"%s\" gt=\"%s\">%n";
+
+ private static final String BEGIN_EVALUATION_TAG =
+ " <evaluation time=\"%d\">%n";
+
+ private static final String MEASURE_TAG =
+ " <measure name=\"%s\" value=\"%.5f\" />%n";
+
+ private static final String END_EVALUATION_TAG =
+ " </evaluation>%n";
+
+ private static final String END_TASK_TAG =
+ " </task>%n";
+
+ private static final String END_EXPERIMENT_TAG =
+ "</experiment>%n";
+
+
+ private final PrintStream out;
+
+
+
+ ExperimentResults(File file) throws FileNotFoundException {
+ this(new FileOutputStream(file));
+ }
+
+
+ ExperimentResults(OutputStream out) {
+ this.out = new PrintStream(new BufferedOutputStream(out));
+ }
+
+
+ public void beginDocument() {
+ write(XML_HEADER);
+ }
+
+
+ public void beginExperiment(Experiment ex) {
+ String dir = ex.getDirectory().getAbsolutePath();
+ write(BEGIN_EXPERIMENT_TAG, ex.getName(), ex.getTime(), dir);
+ }
+
+
+ public void beginTask(int idx, Task task) {
+ String im = task.getImageFile().getName();
+ String gt = task.getMaskFile().getName();
+ write(BEGIN_TASK_TAG, idx, im, gt);
+ }
+
+
+ public void beginEvaluation(int time) {
+ write(BEGIN_EVALUATION_TAG, time);
+ }
+
+
+ public void endEvaluation() {
+ write(END_EVALUATION_TAG);
+ }
+
+
+ public void addMeasure(String name, double value) {
+ write(MEASURE_TAG, name, value);
+ }
+
+
+ public void endTask() {
+ write(END_TASK_TAG);
+ }
+
+
+ public void endExperiment() {
+ write(END_EXPERIMENT_TAG);
+ }
+
+
+ public void endDocument() throws IOException {
+ flush();
+ }
+
+
+ public void flush() throws IOException {
+ out.flush();
+ if (out.checkError()) {
+ throw new IOException();
+ }
+ }
+
+
+ public void close() {
+ out.close();
+ }
+
+
+ private void write(String format, Object ... args) {
+ out.printf(format, args);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.exp;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class FormatException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ *
+ */
+ public FormatException() {
+ }
+
+
+ /**
+ * @param message
+ */
+ public FormatException(String message) {
+ super(message);
+ }
+
+
+ /**
+ * @param cause
+ */
+ public FormatException(Throwable cause) {
+ super(cause);
+ }
+
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public FormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+/**
+ * Selection of what points in time to output the masks that
+ * the user has obtained so far in their interactions.
+ *
+ * @author Kevin McGuinness
+ */
+public enum StorageSelection {
+ All,
+ None,
+ Final;
+
+ public static StorageSelection parse(String s) {
+ for (StorageSelection ss : values()) {
+ if (ss.toString().equalsIgnoreCase(s)) {
+ return ss;
+ }
+ }
+ return null;
+ }
+};
--- /dev/null
+package ie.dcu.apps.ist.exp;
+
+import java.io.*;
+
+public class Task {
+
+ private final File imageFile;
+ private final File maskFile;
+ private final String description;
+
+
+ Task(File imFile, File gtFile, String desc) {
+ imageFile = imFile;
+ maskFile = gtFile;
+ description = desc;
+ }
+
+
+ public File getImageFile() {
+ return imageFile;
+ }
+
+
+ public File getMaskFile() {
+ return maskFile;
+ }
+
+
+ public String getDescription() {
+ return description;
+ }
+
+
+ public String toString() {
+ String im = imageFile.getAbsolutePath();
+ String gt = maskFile.getAbsolutePath();
+ return String.format("[ im=<%s> gt=<%s> desc=<%s> ]", im, gt, description);
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+/**
+ * The shape of the area tag for a HTML image map.
+ *
+ * @see <a href="http://www.w3.org/TR/html401/sgml/dtd.html#Shape"/>HTML 4 DTD</a>
+ *
+ * @author Kevin McGuinness
+ */
+public enum AreaShape {
+ Polygon("poly"),
+ Rectangle("rect"),
+ Circle("circle");
+
+ private String text;
+
+ private AreaShape(String text) {
+ this.text = text;
+ }
+
+ /**
+ * Returns the HTML shape attribute value.
+ */
+ public String toString() {
+ return text;
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+/**
+ * Exception thrown when there is a problem producing the export.
+ *
+ * @author Kevin McGuinness
+ */
+public class ExportException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ExportException() {
+ }
+
+ public ExportException(String message) {
+ super(message);
+ }
+
+ public ExportException(Throwable cause) {
+ super(cause);
+ }
+
+ public ExportException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import ie.dcu.image.ContourTracer;
+import ie.dcu.segment.SegmentationMask;
+import ie.dcu.util.FileUtils;
+
+import java.awt.Polygon;
+import java.awt.image.*;
+import java.io.*;
+import java.util.*;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Exports HTML image maps from a segmentation mask and an image.
+ *
+ * @author Kevin McGuinness
+ */
+public class Exporter {
+ private final SegmentationMask mask;
+ private final BufferedImage image;
+
+ private RolloverEffect effect;
+ private String htmlFile = "imagemap.html";
+ private String imageFile = "image.png";
+ private String imageName = "image";
+ private String objectDescription = "";
+ private String objectLink = "";
+ private String title = "";
+ private AreaShape exportShape = AreaShape.Polygon;
+
+ public Exporter(BufferedImage image, SegmentationMask mask) {
+ this.image = image;
+ this.mask = mask;
+ }
+
+ public RolloverEffect getEffect() {
+ return effect;
+ }
+
+ public void setEffect(RolloverEffect effect) {
+ this.effect = effect;
+ }
+
+ public String getHtmlFile() {
+ return htmlFile;
+ }
+
+ public void setHtmlFile(String htmlFile) {
+ this.htmlFile = htmlFile;
+ }
+
+ public String getImageFile() {
+ return imageFile;
+ }
+
+ public void setImageFile(String imageFile) {
+ this.imageFile = imageFile;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getObjectDescription() {
+ return objectDescription;
+ }
+
+ public void setObjectDescription(String description) {
+ this.objectDescription = description;
+ }
+
+ public String getObjectLink() {
+ return objectLink;
+ }
+
+ public void setObjectLink(String link) {
+ this.objectLink = link;
+ }
+
+ public AreaShape getExportShape() {
+ return exportShape;
+ }
+
+ public void setExportShape(AreaShape shape) {
+ this.exportShape = shape;
+ }
+
+ public void export(File folder) throws IOException, ExportException {
+ ContourTracer tracer = new ContourTracer(mask, SegmentationMask.FOREGROUND);
+ List<Polygon> trace = tracer.trace();
+
+ if (trace.size() == 0) {
+ throw new ExportException("No objects found");
+ }
+
+ List<String> preloads = getPreloads(trace);
+
+ // Write image
+ ImageIO.write(image, "png", new File(folder, imageFile));
+
+ if (effect != null) {
+ int i = 1;
+
+ // Generate effect images
+ for (Polygon object : trace) {
+ RenderedImage im = effect.createEffect(image, object);
+ File output = new File(folder, preloads.get(i++));
+ ImageIO.write(im, "png", output);
+ }
+ }
+
+ // Create image map
+ ImageMap map = new ImageMap();
+ map.setImageHref(imageFile);
+ map.setImageName(imageName);
+
+ // Add javascript preloads
+ for (String str : preloads) {
+ map.addPreload(str);
+ }
+
+ // Add areas
+ int idx = 1;
+ for (Polygon polygon : trace) {
+ MapArea area = new MapArea();
+ switch (exportShape) {
+ case Polygon:
+ area.setPolygon(polygon);
+ break;
+ case Rectangle:
+ area.setRect(polygon.getBounds());
+ break;
+ case Circle:
+ // TODO: Implement circle!
+
+ default:
+ area.setPolygon(polygon);
+ break;
+ }
+
+ area.setAlt(objectDescription);
+ area.setHref(objectLink);
+
+ if (effect != null) {
+
+
+ area.addAttr("onmouseover",
+ String.format("rollover(document.%s, image%s)",
+ imageName, idx++));
+ area.addAttr("onmouseout",
+ String.format("rollover(document.%s, image0)", imageName));
+ area.addAttr("title",getTitle());
+ }
+
+ map.addArea(area);
+ }
+
+ map.exportToFile(new File(folder, htmlFile));
+ }
+
+ private List<String> getPreloads(List<Polygon> trace) {
+ List<String> preloads = new ArrayList<String>();
+ preloads.add(imageFile);
+
+ if (effect != null) {
+
+ String basename = FileUtils.removeExtension(imageFile);
+ for (int i = 0; i < trace.size(); i++) {
+ String filename = String.format("%s-%d.png", basename, i);
+ preloads.add(filename);
+ }
+ }
+
+ return preloads;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to generate HTML tags.
+ *
+ * @author Kevin McGuinness
+ */
+class HtmlTag {
+ final String name;
+ final Map<String, String> attrs;
+
+ HtmlTag(String name) {
+ this.name = name;
+ this.attrs = new HashMap<String, String>();
+ }
+
+ HtmlTag attr(String name, String value) {
+ attrs.put(name, escape(value));
+ return this;
+ }
+
+ private static String escape(String value) {
+ return value.replaceAll("\"", "\\\\\"");
+ }
+
+ static void indent(StringBuffer sb, int indent) {
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ }
+
+ void open(StringBuffer sb, int indent) {
+ open(sb, indent, false);
+ }
+
+ void open(StringBuffer sb, int indent, boolean empty) {
+
+ // Indent
+ indent(sb, indent);
+
+ // Write start of tag
+ sb.append('<').append(name);
+
+ // Write attributes
+ boolean first = true;
+ for (Map.Entry<String, String> entry : attrs.entrySet()) {
+
+ if (!first) {
+ sb.append('\n');
+ indent(sb, indent + name.length() + 1);
+ }
+
+ sb.append(' ');
+ sb.append(entry.getKey());
+ sb.append('=');
+ sb.append('"');
+ sb.append(entry.getValue());
+ sb.append('"');
+
+ first = false;
+ }
+
+ if (empty) {
+ sb.append('/');
+ }
+ sb.append('>').append('\n');
+ }
+
+ void close(StringBuffer sb, int indent) {
+ indent(sb, indent);
+ sb.append('<');
+ sb.append('/');
+ sb.append(name);
+ sb.append('>');
+ sb.append('\n');
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Exports HTML image maps
+ *
+ * @author Kevin McGuinness
+ */
+public class ImageMap {
+ private static final String TEMPLATE_FILE = "template.html";
+
+ private String pageTitle;
+ private String imageName;
+ private String imageHref;
+ private String imageAlt;
+ private String mapName;
+ private final List<MapArea> areas;
+ private final List<String> preloads;
+
+ public ImageMap() {
+ // set defaults :
+ this.pageTitle = "Image Map";
+ this.imageName = "image";
+ this.imageHref = "";
+ this.imageAlt = "";
+ this.mapName = "imagemap";
+ this.areas = new LinkedList<MapArea>();
+ this.preloads = new LinkedList<String>();
+ }
+
+ public String getPageTitle() {
+ return pageTitle;
+ }
+
+ public void setPageTitle(String pageTitle) {
+ assert (pageTitle != null);
+ this.pageTitle = pageTitle;
+ }
+
+ public String getImageName() {
+ return imageName;
+ }
+
+ public void setImageName(String imageName) {
+ this.imageName = imageName;
+ }
+
+ public String getImageHref() {
+ return imageHref;
+ }
+
+ public void setImageHref(String imageHref) {
+ assert (imageHref != null);
+ this.imageHref = imageHref;
+ }
+
+ public String getImageAlt() {
+ return imageAlt;
+ }
+
+ public void setImageAlt(String imageAlt) {
+ assert (imageAlt != null);
+ this.imageAlt = imageAlt;
+ }
+
+ public String getMapName() {
+ return mapName;
+ }
+
+ public void setMapName(String mapName) {
+ assert (mapName != null);
+ this.mapName = mapName;
+ }
+
+ public void addArea(MapArea area) {
+ areas.add(area);
+ }
+
+ public List<MapArea> areas() {
+ return areas;
+ }
+
+ public void addPreload(String preload) {
+ this.preloads.add(preload);
+ }
+
+ public List<String> preloads() {
+ return preloads;
+ }
+
+ public String export() {
+ String template = loadTemplate();
+
+ // Create preloads buffer
+ StringBuffer preloads = new StringBuffer();
+ int idx = 0;
+ for (String s : preloads()) {
+ HtmlTag.indent(preloads, 8);
+ preloads.append("var image").append(idx);
+ preloads.append(" = new Image()\n");
+ HtmlTag.indent(preloads, 8);
+ preloads.append("image").append(idx);
+ preloads.append(".src = '").append(s).append("'\n");
+ idx++;
+ }
+
+ StringBuffer contents = new StringBuffer();
+ for (MapArea area : areas) {
+ contents.append('\n');
+ area.export(contents, 8);
+ }
+
+ Map<String, String> subs = new HashMap<String, String>();
+ subs.put("image-name", imageName);
+ subs.put("page-title", pageTitle);
+ subs.put("image-href", imageHref);
+ subs.put("image-alt", imageAlt);
+ subs.put("map-name", mapName);
+ subs.put("contents", contents.toString());
+ subs.put("preloads", preloads.toString());
+
+ return substitute(template, subs);
+ }
+
+ public void exportToFile(File file) throws IOException {
+ FileWriter writer = new FileWriter(file);
+ try {
+ writer.append(export());
+ } finally {
+ writer.close();
+ }
+ }
+
+ private String substitute(String template, Map<String, String> subs) {
+ // This could be more efficient..
+ String result = template;
+ for (String key : subs.keySet()) {
+ String regex = String.format("\\$\\{%s\\}", key);
+ result = result.replaceAll(regex, subs.get(key));
+ }
+
+ return result;
+ }
+
+ private String loadTemplate() {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ getClass().getResourceAsStream(TEMPLATE_FILE)));
+
+ StringBuffer buff = new StringBuffer();
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ buff.append(line).append('\n');
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ return buff.toString();
+ }
+
+ public String toString() {
+ return export();
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+public class MapArea {
+
+ private String href;
+ private String alt;
+ private AreaShape shape;
+ private int[] coords;
+ private final Map<String, String> attrs;
+
+ public MapArea() {
+ href = "";
+ alt = "";
+ shape = AreaShape.Polygon;
+ coords = new int[0];
+ attrs = new HashMap<String, String>();
+ }
+
+ public void addAttr(String key, String value) {
+ attrs.put(key, value);
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setHref(String href) {
+ this.href = href == null ? "" : href;
+ }
+
+ public String getAlt() {
+ return alt;
+ }
+
+ public void setAlt(String alt) {
+ this.alt = alt == null ? "" : alt;
+ }
+
+ public AreaShape getShape() {
+ return shape;
+ }
+
+ public void setShape(AreaShape shape) {
+ this.shape = shape == null ? AreaShape.Polygon : shape;
+ }
+
+ public int[] getCoords() {
+ return coords;
+ }
+
+ public void setCoords(int[] coords) {
+ this.coords = coords == null ? new int[0] : coords;
+ }
+
+ public void setRect(Rectangle rect) {
+ setShape(AreaShape.Rectangle);
+ int[] coords = {
+ rect.x,
+ rect.y,
+ rect.x + rect.width,
+ rect.y + rect.height
+ };
+ setCoords(coords);
+ }
+
+ public void setCircle(int x, int y, int r) {
+ setShape(AreaShape.Circle);
+ int[] coords = { x, y, r };
+ setCoords(coords);
+ }
+
+ public void setPolygon(Polygon poly) {
+ setShape(AreaShape.Polygon);
+ int[] coords = new int[2*poly.npoints];
+ for (int i = 0, j = 0; i < poly.npoints; i++) {
+ coords[j++] = poly.xpoints[i];
+ coords[j++] = poly.ypoints[i];
+ }
+ setCoords(coords);
+ }
+
+ public void export(StringBuffer sb, int indent) {
+ String encodedHREF = href;
+ if (href.length() != 0) {
+ try {
+ URI uri = new URI(href);
+ URL url = uri.toURL();
+ encodedHREF = url.toString();
+ } catch (URISyntaxException e) {
+ // Ignore exceptions, the href string will be used instead
+ } catch (MalformedURLException e) {
+ // Ignore exceptions, the href string will be used instead
+ }
+ }
+
+ HtmlTag tag = new HtmlTag("area");
+ tag
+ .attr("href", encodedHREF)
+ .attr("alt", alt)
+ .attr("shape", shape.toString())
+ .attr("coords", getCoordsString());
+
+ for (Entry<String, String> entry : attrs.entrySet()) {
+ tag.attr(entry.getKey(), entry.getValue());
+ }
+
+ tag.open(sb, indent, true);
+ }
+
+ public String getCoordsString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ for (int coord : coords) {
+ if (!first) {
+ sb.append(',');
+ }
+ sb.append(coord);
+ first = false;
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.export.imagemap;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * The roll-over effect for the image map.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class RolloverEffect {
+
+ public abstract RenderedImage createEffect(Image image, Polygon object);
+
+ public static RolloverEffect darkenBackground(float alpha) {
+ return new DarkenBackground(alpha);
+ }
+
+ public static RolloverEffect darkenBackground() {
+ return new DarkenBackground();
+ }
+
+ public static RolloverEffect brightenForeground(float alpha) {
+ return new BrightenForeground(alpha);
+ }
+
+ public static RolloverEffect brightenForeground() {
+ return new BrightenForeground();
+ }
+
+ public static RolloverEffect outlineObject() {
+ return new OutlineObject();
+ }
+
+ public static RolloverEffect outlineObject(Color color) {
+ return new OutlineObject(color);
+ }
+
+ public static RolloverEffect outlineObject(Color color, Stroke stroke) {
+ return new OutlineObject(color, stroke);
+ }
+
+ public static class DarkenBackground extends RolloverEffect {
+
+ private float alpha = 0.5f;
+
+ public DarkenBackground() {
+
+ }
+
+ public DarkenBackground(float alpha) {
+ this.alpha = alpha;
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Area area = new Area(new Rectangle(0,0,width,height));
+ area.subtract(new Area(object));
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(new Color(0,0,0,alpha));
+ g2.fill(area);
+ }
+
+ return im;
+ }
+ }
+
+ public static class BrightenForeground extends RolloverEffect {
+
+ private float alpha = 0.5f;
+
+ public BrightenForeground() {
+
+ }
+
+ public BrightenForeground(float alpha) {
+ this.alpha = alpha;
+ }
+
+ public float getAlpha() {
+ return alpha;
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(new Color(1,1,1,alpha));
+ g2.fill(object);
+ }
+
+ return im;
+ }
+ }
+
+ public static class OutlineObject extends RolloverEffect {
+
+ private Color color = Color.white;
+ private Stroke stroke = new BasicStroke(2.0f,
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+
+ public OutlineObject() {
+
+ }
+
+ public OutlineObject(Color color) {
+ this.color = color;
+ }
+
+ public OutlineObject(Color color, Stroke stroke) {
+ this.color = color;
+ this.stroke = stroke;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ public Stroke getStroke() {
+ return stroke;
+ }
+
+ public void setStroke(Stroke stroke) {
+ this.stroke = stroke;
+ }
+
+ @Override
+ public RenderedImage createEffect(Image image, Polygon object) {
+ int width = image.getWidth(null);
+ int height = image.getHeight(null);
+ BufferedImage im = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics g = im.getGraphics();
+ if (g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.drawImage(image, 0, 0, null);
+ g2.setColor(color);
+ g2.setStroke(stroke);
+ g2.draw(object);
+ }
+
+ return im;
+ }
+ }
+}
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+ <!-- Generated using the CDVP Interactive Segmentation Tool -->
+ <meta http-equiv="Content-type"
+ content="text/html; charset=utf-8"/>
+ <meta name="Generator"
+ content="Interactive Segmentation Tool"/>
+
+ <title>${page-title}</title>
+
+ <script type="text/javascript" language="javascript">
+
+${preloads}
+
+ function rollover(docElem, image) {
+ docElem.src = image.src
+ return true
+ }
+
+ </script>
+
+</head>
+<body>
+ <!-- Image -->
+ <img name="${image-name}"
+ src="${image-href}"
+ usemap="#${map-name}"
+ alt="${image-alt}"
+ title="${image-alt}"/>
+
+ <!-- Image map -->
+ <map name="${map-name}" id="${map-name}">
+${contents}
+ </map>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Class for managing a recent documents list.
+ *
+ * @author Kevin McGuinness
+ */
+public class RecentFiles implements Iterable<File> {
+ public static final int DEFAULT_CAPACITY = 10;
+
+
+ private final LinkedList<File> history;
+ private final List<RecentFilesListener> listeners;
+ private final File store;
+ private int capacity;
+
+
+ public RecentFiles(File store) {
+
+ // Check the store is ok
+ checkValid(store);
+
+ // Construct
+ this.history = new LinkedList<File>();
+ this.listeners = new LinkedList<RecentFilesListener>();
+ this.store = store;
+ this.capacity = DEFAULT_CAPACITY;
+
+ // Add a vm shutdown hook
+ addShutdownHook();
+ }
+
+
+ public void setCapacity(int capacity) {
+
+ // Check positive
+ if (capacity < 0) {
+ throw new IllegalArgumentException("capacity < 0");
+ }
+
+ if (this.capacity != capacity) {
+ this.capacity = capacity;
+
+ // Capacity may have reduced
+ boolean reduced = history.size() > capacity;
+ while (history.size() > capacity) {
+ history.removeLast();
+ }
+
+ // Fire change
+ if (reduced) {
+ fireHistoryChanged();
+ }
+ }
+ }
+
+
+ public int getCapacity() {
+ return capacity;
+ }
+
+
+ public void add(File file) {
+ if (!history.contains(file)) {
+ history.addFirst(file);
+
+ // Check if we have exceeded capacity
+ if (history.size() > capacity) {
+ history.removeLast();
+ }
+
+ // Fire change
+ fireHistoryChanged();
+ }
+ }
+
+
+ public void clear() {
+ if (!empty()) {
+ history.clear();
+ fireHistoryChanged();
+ }
+ }
+
+
+ public List<File> files() {
+ return Collections.unmodifiableList(history);
+ }
+
+
+ public Iterator<File> iterator() {
+ return files().iterator();
+ }
+
+
+ public boolean empty() {
+ return history.isEmpty();
+ }
+
+
+ public void load() throws IOException {
+ if (!store.exists()) {
+ // Create an empty store
+ store.createNewFile();
+ }
+
+ BufferedReader in = null;
+ try {
+ read(in = new BufferedReader(new FileReader(store)));
+
+ } finally {
+ in.close();
+ }
+ }
+
+
+ public void store() throws IOException {
+ PrintWriter out = null;
+ try {
+ write(out = new PrintWriter(store));
+
+ } finally {
+ out.close();
+ }
+ }
+
+
+ public void addListener(RecentFilesListener rfl) {
+ listeners.add(rfl);
+ }
+
+
+ public void removeListener(RecentFilesListener rfl) {
+ listeners.remove(rfl);
+ }
+
+
+ protected static Logger getLogger() {
+ return Logger.getLogger(RecentFiles.class.getName());
+ }
+
+
+ private void shutdown() {
+ try {
+ store();
+ } catch (IOException e) {
+ getLogger().warning("Error writing recent documents to store: " + e);
+ }
+ }
+
+
+ private void fireHistoryChanged() {
+ RecentFilesEvent evt = null;
+ for (RecentFilesListener listener : listeners) {
+ if (evt == null) {
+ evt = new RecentFilesEvent(this);
+ listener.historyChanged(evt);
+ }
+ }
+ }
+
+
+ private void checkValid(File store) {
+ if (store == null) {
+ throw new IllegalArgumentException("store cannot be null");
+ }
+
+ if (store.isDirectory()) {
+ throw new IllegalArgumentException("store cannot be a directory");
+ }
+
+ if (store.isFile()) {
+ if (!store.canWrite()) {
+ throw new IllegalArgumentException("store not writable");
+ }
+ }
+ }
+
+
+ private void addShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ public void run() {
+ shutdown();
+ }
+ });
+ }
+
+
+ private void read(BufferedReader in) throws IOException {
+
+ // Remember old history to check for changes
+ LinkedList<File> old = new LinkedList<File>(history);
+
+ // Clear history if not empty
+ history.clear();
+
+ // Read files (discarding no longer existent ones)
+ String line;
+ while ((line = in.readLine()) != null) {
+
+ // Stop when full
+ if (history.size() == capacity) {
+ break;
+ }
+
+ File file = new File(line);
+ if (file.exists()) {
+ history.add(file);
+ }
+ }
+
+ // Fire change if a change occurred
+ if (!history.equals(old)) {
+ fireHistoryChanged();
+ }
+ }
+
+
+ private void write(PrintWriter out) throws IOException {
+ for (File file : history) {
+ String path = file.getAbsolutePath();
+ out.println(path);
+ }
+
+ // Check for errors
+ if (out.checkError()) {
+ throw new IOException("Error writing to file");
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.util.*;
+
+public class RecentFilesEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+
+ public RecentFilesEvent(RecentFiles source) {
+ super(source);
+ }
+
+
+ public RecentFiles getList() {
+ return (RecentFiles) source;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.recent;
+
+import java.util.*;
+
+public interface RecentFilesListener extends EventListener {
+
+ public void historyChanged(RecentFilesEvent evt);
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.apps.ist.views;
+
+import ie.dcu.apps.ist.AppWindow;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.exp.*;
+import ie.dcu.apps.ist.widgets.SwtTimer;
+import ie.dcu.segment.annotate.*;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.logging.*;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * @author Kevin McGuinness
+ */
+public class ExperimentPanel extends Composite {
+
+ // Some property keys
+ private static final String DESCRIPTION_TITLE
+ = "description.title";
+
+ private static final String TIMEOUT_MESSAGE
+ = "timeout-message";
+
+ private static final String EXPERIMENT_COMPLETE_MESSAGE
+ = "experiment-complete-message";
+
+ // Type of button in use
+ private static enum ButtonType { Start, Finish };
+
+ // App window and experiment
+ private final AppWindow window;
+ private final Experiment experiment;
+ private final Logger log;
+
+ // Controls
+ private final Group group;
+ private final Button button;
+ private final SwtTimer timer;
+ private final Text text;
+
+ // Shell if the panel is inside a shell
+ private Shell shell;
+
+ // Experiment manager
+ private final ExperimentManager manager;
+ private final EvaluationListener evaluator;
+
+
+ public ExperimentPanel(AppWindow window, Composite parent, int style) {
+ super(parent, style);
+
+ // Set app window
+ this.window = window;
+ this.experiment = window.getExperiment();
+ this.log = Logger.getLogger("Experiment");
+
+
+ // Create controls
+ timer = new SwtTimer(this, SWT.NONE);
+ group = new Group(this, SWT.DEFAULT);
+ button = new Button(this, SWT.PUSH);
+ text = new Text(group, SWT.READ_ONLY | SWT.WRAP);
+
+ // Create manager and evaluator
+ manager = new ExperimentManager(window);
+ evaluator = new EvaluationListener(manager, timer);
+
+ // Setup controls
+ setupControls();
+ layoutControls();
+ addListeners();
+
+ // Begin experiment
+ beginExperiment();
+ }
+
+
+ public static void open(AppWindow win) {
+ Shell app = win.getShell();
+
+ // Style bits
+ int style = SWT.MODELESS | SWT.RESIZE | SWT.BORDER | SWT.TITLE | SWT.CLOSE;
+
+ // Construct shell
+ Shell shell = new Shell(app, style);
+
+ // Determine shell bounds
+ Rectangle bounds = new Rectangle(0,0,180,280);
+
+ // Move shell to an appropriate place (right of app window)
+ Rectangle rect = app.getBounds();
+ bounds.x = rect.x + rect.width;
+ bounds.y = rect.y + rect.height / 2 - bounds.height / 2;
+
+ // Move back if outside the screen
+ Rectangle screen = shell.getDisplay().getBounds();
+ if (bounds.x + bounds.width > screen.width) {
+ bounds.x -= bounds.x + bounds.width - screen.width;
+ }
+
+ // Set bounds
+ shell.setBounds(bounds);
+
+ // Set layout
+ shell.setLayout(new FillLayout());
+
+ // Create experiment panel
+ final ExperimentPanel panel
+ = new ExperimentPanel(win, shell, SWT.NONE);
+
+ panel.shell = shell;
+ panel.updateTitle();
+
+ // Add shell close button listener
+ shell.addShellListener(new ShellAdapter() {
+ public void shellClosed(ShellEvent e) {
+ if (!panel.manager.isComplete()) {
+ e.doit = panel.cancelExperiment();
+ }
+ }
+ });
+
+ // Open experiment panel
+ shell.open();
+ }
+
+
+ private void setupControls() {
+ group.setText(property(DESCRIPTION_TITLE));
+ text.setEnabled(false);
+ enableButton(null);
+ }
+
+
+ private void layoutControls() {
+ GridLayout gl;
+ GridData gd;
+
+ // Setup layout manager for this
+ gl = new GridLayout();
+ gl.marginTop = 5;
+ gl.verticalSpacing = 5;
+ this.setLayout(gl);
+
+ // Setup layout manager for group
+ gl = new GridLayout();
+ group.setLayout(gl);
+
+ // Layout timer
+ gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ timer.setLayoutData(gd);
+
+ // Layout group
+ gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ group.setLayoutData(gd);
+
+ // Layout button
+ gd = new GridData(SWT.FILL, SWT.FILL, true, false);
+ button.setLayoutData(gd);
+
+ // Layout text
+ gd = new GridData(SWT.FILL, SWT.FILL, true, true);
+ text.setLayoutData(gd);
+ }
+
+
+ private void addListeners() {
+ button.addListener(SWT.Selection, buttonListener);
+ timer.addTimeoutListener(timeListener);
+ }
+
+
+ /**
+ * Function to call if something goes wrong with the experiment. Shows an
+ * error message and gracefully returns the application to normal mode.
+ *
+ * @param reason
+ * Short description of the problem.
+ */
+ private void abortExperiment(String reason) {
+ timer.clear();
+ evaluator.detach();
+
+ // Show abort message
+ error("Aborting experiment!%n%s", reason);
+
+ // Tell manager of abort
+ manager.abortExperiment();
+
+ // Exit experiment mode
+ exitExperimentMode();
+ }
+
+
+ /**
+ * Does what is necessary to exit experiment mode.
+ */
+ private void exitExperimentMode() {
+ // Remove experiment view
+ window.setExperiment(null);
+
+ // Re-enable interactions
+ window.getView().setEnabled(true);
+
+ // Kill the shell
+ closeShell();
+ }
+
+
+ /**
+ * Closes the encompassing (non-application) shell, if any.
+ */
+ private void closeShell() {
+ if (shell != null) {
+ shell.close();
+ shell.dispose();
+ shell = null;
+ }
+ }
+
+
+ private boolean cancelExperiment() {
+ // Make sure the user knows what they're doing
+ boolean cancel = MessageDialog.openQuestion(window.getShell(), "Confirm",
+ "Are you sure you want to cancel the experiment?");
+
+ if (cancel) {
+ // Guess we better do what the user says, grumble grumble :-/
+ timer.clear();
+ evaluator.detach();
+ manager.abortExperiment();
+ window.setExperiment(null);
+ window.getView().setEnabled(true);
+ }
+
+ return cancel;
+ }
+
+
+ /**
+ * Start the experiment.
+ */
+ private void beginExperiment() {
+ try {
+ manager.beginExperiment(experiment);
+
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error starting experiment", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Begin first task
+ beginTask();
+ }
+
+
+ /**
+ * Finish the experiment.
+ */
+ private void endExperiment() {
+ try {
+ manager.endExperiment();
+
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error ending experiment", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Tell user experiment is complete
+ notifyExperimentComplete();
+
+ // Stop experiment mode
+ exitExperimentMode();
+ }
+
+
+ /**
+ * Load the task.
+ */
+ private void beginTask() {
+
+ // Set timer
+ timer.set(experiment.getTime());
+
+ // Begin task
+ try {
+ manager.beginTask();
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error starting task", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Disable interactions
+ window.getView().setEnabled(false);
+
+ // Update description
+ text.setText(manager.getTaskDescription());
+
+ // Update title text
+ updateTitle();
+
+ // Setup start button
+ enableButton(ButtonType.Start);
+
+ // Attach evaluator
+ evaluator.attach();
+ }
+
+
+ private void updateTitle() {
+ if (shell != null) {
+ int ntasks = manager.getTaskCount();
+ int progress = manager.getProgress();
+ shell.setText(String.format("Task [%d/%d]", progress+1, ntasks));
+ }
+ }
+
+
+ /**
+ * Start the task.
+ */
+ private void startTask() {
+
+ // Enable interactions
+ window.getView().setEnabled(true);
+
+ // Start clock
+ timer.start();
+
+ // Enable finish button
+ enableButton(ButtonType.Finish);
+ }
+
+
+ /**
+ * End the task.
+ */
+ private void endTask() {
+ // Detach evaluator
+ evaluator.detach();
+
+ // Evaluate final result
+ manager.evaluate(timer.getElapsed());
+
+ // End task
+ try {
+ manager.endTask();
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "Error ending task", e);
+ abortExperiment(e.getMessage());
+ }
+
+ // Stop and clear clock
+ timer.clear();
+ }
+
+
+ /**
+ * End the current task and start the next.
+ * End the experiment if finished.
+ */
+ private void nextTask() {
+ endTask();
+ if (manager.isComplete()) {
+ endExperiment();
+ } else {
+ beginTask();
+ }
+ }
+
+
+ private void enableButton(ButtonType type) {
+ if (type != null) {
+ String name = type.toString().toLowerCase();
+ button.setText(property("button.text." + name));
+ button.setData(type);
+ button.setEnabled(true);
+ button.setFocus();
+ } else {
+ button.setText(property("button.text.start"));
+ button.setEnabled(false);
+ button.setData(null);
+ }
+ }
+
+
+ private ButtonType getEnabledButton() {
+ return (ButtonType) button.getData();
+ }
+
+
+ private void handleButtonClicked() {
+ ButtonType btn = getEnabledButton();
+ switch (btn) {
+ case Start:
+ startTask();
+ break;
+ case Finish:
+ // Prevent clicking finish by accident
+ if (manager.getActiveContext().getAnnotations().count() == 0) {
+ getDisplay().beep();
+ } else {
+ nextTask();
+ }
+ break;
+ }
+ }
+
+
+ private void handleTimeout() {
+ // Finish task
+ endTask();
+
+ // Tell user
+ notifyTimeoutOccurred();
+
+ // Next task
+ if (manager.isComplete()) {
+ endExperiment();
+ } else {
+ beginTask();
+ }
+ }
+
+
+ private void notifyExperimentComplete() {
+ message(EXPERIMENT_COMPLETE_MESSAGE);
+ }
+
+
+ private void notifyTimeoutOccurred() {
+ // BEEP BEEP!! time up :-)
+ getDisplay().beep();
+ message(TIMEOUT_MESSAGE);
+ }
+
+
+ private void message(String key) {
+ Shell main = window.getShell();
+ MessageDialog.openInformation(main, "Information", property(key));
+ }
+
+
+ private void error(String message, Object ... args) {
+ Shell main = window.getShell();
+ MessageDialog.openError(main, "Error", String.format(message, args));
+ }
+
+
+ private String property(String key) {
+ return property(key, key);
+ }
+
+
+ private String property(String key, String def) {
+ Properties p = window.getProperties();
+ return p.getProperty(String.format("ExperimentPanel.%s", key), def);
+ }
+
+
+ private final Listener buttonListener = new Listener() {
+ public void handleEvent(Event event) {
+ handleButtonClicked();
+ }
+ };
+
+
+ private final TimeoutListener timeListener = new TimeoutListener() {
+ public void timeoutOccured(TimeoutEvent evt) {
+ handleTimeout();
+ }
+ };
+}
+
+
+class EvaluationListener implements AnnotationListener {
+ private final ExperimentManager manager;
+ private final SwtTimer timer;
+ private final Logger log;
+
+ public EvaluationListener(ExperimentManager manager, SwtTimer timer) {
+ this.manager = manager;
+ this.timer = timer;
+ this.log = Logger.getLogger("Experiment");
+ }
+
+
+ public void attach() {
+ manager.getActiveContext().addAnnotationListener(this);
+ }
+
+
+ public void detach() {
+ manager.getActiveContext().removeAnnotationListener(this);
+ }
+
+
+ private void annotationsChanged(AnnotationEvent e) {
+ // Return if there is nothing to evaluate yet
+ if (e.manager.count() == 0) {
+ return;
+ }
+
+ // Time the change occurred
+ int time = timer.getElapsed();
+ manager.evaluate(time);
+
+ // Store if necessary
+ try {
+ manager.store(time);
+ } catch (IOException ex) {
+ log.log(Level.SEVERE, "Error storing intermediate segmentation mask", e);
+ }
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationUndone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+}
+
--- /dev/null
+package ie.dcu.apps.ist.views;
+
+
+import ie.dcu.apps.ist.PainterRegistry;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.apps.ist.widgets.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.segment.options.SegmenterOptionDialog;
+import ie.dcu.segment.painters.SegmentationPainter;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.*;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.*;
+import java.util.Properties;
+import java.util.logging.*;
+import java.io.*;
+import org.json.*;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.action.*;
+import org.eclipse.jface.operation.*;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+public class SegmentationView extends Composite {
+ // Logger
+ private static final Logger log = Logger.getLogger(SegmentationView.class.getName());
+
+
+ // Segmentation view properties
+ private final Properties props;
+
+
+ // Houses various painters
+ private final PainterRegistry painters;
+
+
+ // View handling annotations
+ private final AnnotatedImageControl view;
+
+
+ // Left and right tool bar
+ private final ToolBar bar1, bar2, bar3;
+
+ private final Button assign;
+
+ //private final StyledText text;
+
+ // Control to change brush size
+ private final BrushControl brushControl;
+
+
+ // Handles various events
+ private final EventHandler eventHandler;
+
+
+ // Proxy object to catch and log exceptions thrown by the segmentation
+ // algorithm, without crashing the application.
+ private RobustSegmenterProxy segmenterProxy;
+
+ // Auto segment on update flag
+ private boolean auto = true;
+
+ // Combo box housing the selectable views
+ private Combo combo;
+
+ public static Combo comboLabel;
+
+ // Current segmentation tool
+ private Segmenter segmenter;
+
+
+ // Context to run the segmentation in, (may be null)
+ private IRunnableContext runnableContext;
+
+
+ // Flag to indicate that the runnable context blocks when fork is true
+ private boolean blocksOnFork;
+
+
+ public enum Tool {
+ Foreground,
+ Background,
+ ZoomIn,
+ ZoomOut,
+ ZoomOriginal,
+ ZoomBestFit,
+ Repaint,
+ Undo,
+ Redo,
+ Clear,
+ SetBrushSize,
+ AutoApply,
+ Apply,
+ SetPainter,
+ SetLabel,
+ AssignButton,
+ SegmenterOptions;
+
+ private ToolAction action;
+ };
+
+
+ public SegmentationView(Properties props, Composite parent, int style) {
+ super(parent, style);
+ this.props = props;
+
+ painters = new PainterRegistry();
+ bar1 = new ToolBar(this, SWT.RIGHT | SWT.FLAT);
+ bar2 = new ToolBar(this, SWT.RIGHT | SWT.FLAT);
+ bar3 = new ToolBar(this, SWT.RIGHT | SWT.FLAT);
+ assign = new Button(bar3, SWT.PUSH);
+ view = new AnnotatedImageControl(this, SWT.BORDER);
+ brushControl = new BrushControl(getShell(), SWT.BORDER);
+ eventHandler = new EventHandler();
+ segmenterProxy = new RobustSegmenterProxy();
+
+ init();
+ }
+
+
+ /**
+ * Initialize.
+ */
+ private void init() {
+ initTools();
+ createToolbar1();
+ createToolbar2();
+ createToolbar3();
+ layoutControls();
+ updatePainters();
+ addListeners();
+ updateToolStates();
+ }
+
+
+ private void initTools() {
+ for (Tool t : Tool.values()) {
+ if (t.action == null) {
+ new ToolAction(t);
+ }
+ }
+ }
+
+
+ private void addListeners() {
+ brushControl.addSelectionListener(eventHandler);
+ view.addContextChangeListener(eventHandler);
+ view.addZoomListener(eventHandler);
+ addDisposeListener(eventHandler);
+ }
+
+
+ private void createToolbar1() {
+ ToolBarManager m = new ToolBarManager(bar1);
+ m.add(getAction(Tool.Foreground));
+ m.add(getAction(Tool.Background));
+ m.add(new Separator());
+ m.add(getAction(Tool.ZoomIn));
+ m.add(getAction(Tool.ZoomOut));
+ m.add(getAction(Tool.ZoomOriginal));
+ m.add(getAction(Tool.ZoomBestFit));
+ m.add(new Separator());
+ m.add(getAction(Tool.Repaint));
+ m.add(getAction(Tool.Undo));
+ m.add(getAction(Tool.Redo));
+ m.add(getAction(Tool.Clear));
+ m.add(new Separator());
+ m.add(getAction(Tool.SetBrushSize));
+ m.add(new Separator());
+ m.add(getAction(Tool.AutoApply));
+ m.add(getAction(Tool.Apply));
+ m.add(getAction(Tool.SegmenterOptions));
+ m.update(true);
+ }
+
+
+ private void createToolbar2() {
+ SwtUtils.addLabel(bar2, getAction(Tool.SetPainter).getText());
+ combo = SwtUtils.addCombo(bar2, 115, SWT.READ_ONLY);
+ combo.setToolTipText( getAction(Tool.SetPainter).getToolTipText());
+ combo.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ execute(Tool.SetPainter, null);
+ }
+ });
+ bar2.pack();
+ }
+ /**
+ * Third tool bar for holding the Annotate Combo box.
+ */
+ private void createToolbar3() {
+ SwtUtils.addLabel(bar3, getAction(Tool.SetLabel).getText());
+ comboLabel = SwtUtils.addCombo(bar3, 130, SWT.SIMPLE);
+ comboLabel.setToolTipText( getAction(Tool.SetLabel).getToolTipText());
+ comboLabel.addKeyListener(new KeyListener() {
+ @Override
+ public void keyReleased(KeyEvent e) {
+ comboLabel.remove(0,comboLabel.getItemCount()-1);
+ setLabel(comboLabel.getText());
+ Display.getCurrent().asyncExec(new Runnable() {
+ public void run() {
+ comboLabel.setText(comboLabel.getText());
+ comboLabel.forceFocus();
+ }
+ });
+ }
+ @Override
+ public void keyPressed(KeyEvent arg0) {
+ // TODO Auto-generated method stub
+ }
+ });
+ ToolItem item = new ToolItem(bar3, SWT.SEPARATOR);
+ assign.setText("Assign");
+ item.setWidth(50);
+ item.setControl(assign);
+ assign.addSelectionListener(new SelectionListener() {
+ public void widgetSelected(SelectionEvent arg0) {
+ String lab = null;
+ if(comboLabel.getText().indexOf('{') != -1)
+ {
+ lab = comboLabel.getText().substring(0,comboLabel.getText().indexOf('{')-1);
+ }
+ else
+ {
+ lab = comboLabel.getText();
+ }
+ System.out.println("selected"+lab);
+ execute(Tool.AssignButton, null);
+ }
+ @Override
+ public void widgetDefaultSelected(SelectionEvent arg0) {
+ // TODO Auto-generated method stub
+ }
+ });
+ bar3.pack();
+ }
+
+
+ private void layoutControls() {
+ GridLayout layout = new GridLayout(3, false);
+ layout.marginHeight = 1;
+ layout.marginWidth = 2;
+ layout.horizontalSpacing = -1; // zero leaves 1 pixel gap
+ layout.verticalSpacing = 1;
+ setLayout(layout);
+
+ Point sz1 = bar1.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point sz2 = bar2.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+
+ int heightHint = Math.max(sz1.y, sz2.y);
+
+ // Layout toolbar 1
+ GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.heightHint = heightHint;
+ bar1.setLayoutData(gd);
+
+ // Layout toolbar 2
+ gd = new GridData();
+ gd.horizontalAlignment = SWT.FILL;
+ gd.heightHint = heightHint;
+ bar2.setLayoutData(gd);
+
+ // Layout view
+ gd = new GridData();
+ gd.verticalIndent = 2;
+ gd.horizontalSpan = 3;
+ gd.grabExcessHorizontalSpace = true;
+ gd.grabExcessVerticalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.verticalAlignment = SWT.FILL;
+ view.setLayoutData(gd);
+ }
+
+
+ private void updatePainters() {
+ combo.removeAll();
+ boolean first = true;
+ for (SegmentationPainter p : painters.values()) {
+ combo.add(p.getName());
+ if (first) {
+ combo.setText(p.getName());
+ first = false;
+ }
+ }
+ }
+
+
+ public void setSegmenter(Segmenter segmenter) {
+ if (this.segmenter != segmenter) {
+ SegmentationContext ctx = view.getContext();
+
+ if (this.segmenter != null && ctx != null) {
+ // Finish old segmentation
+ segmenterProxy.finish(ctx);
+
+ // Remove old segmenter
+ this.segmenter = null;
+ }
+
+ // Set new segmenter
+ this.segmenter = segmenter;
+
+ // Initialize a new segmentation
+ start(ctx);
+
+ // Update enabled buttons
+ updateToolStates();
+ }
+ }
+
+
+ public Segmenter getSegmenter() {
+ return segmenter;
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+
+ // context change handler handles initialization etc.
+ // of segmenter and context
+ view.setContext(ctx);
+ }
+
+
+ public SegmentationContext getContext() {
+ return view.getContext();
+ }
+
+
+ public void setAnnotationType(AnnotationType type) {
+ view.setAnnotationType(type);
+ }
+
+
+ public Canvas getCanvas() {
+ return view.getCanvas();
+ }
+
+
+ public ImageControl getImageControl() {
+ return view.getImageControl();
+ }
+
+
+ public void zoomIn() {
+ view.zoomIn();
+ }
+
+
+ public void zoomOut() {
+ view.zoomOut();
+ }
+
+
+ public void zoomOriginal() {
+ view.zoomOriginal();
+ }
+
+
+ public void zoomBestFit() {
+ view.zoomBestFit();
+ }
+
+
+ public void repaint() {
+ view.repaint();
+ }
+
+
+ public void undo() {
+ view.undo();
+ }
+
+
+ public void redo() {
+ view.redo();
+ }
+
+
+ public void clear() {
+ view.clear();
+ }
+
+
+ public void setBrushSize(int size) {
+ view.setLineWidth(size);
+ }
+
+
+ public void setPainter(SegmentationPainter painter) {
+ view.setPainter(painter);
+ combo.setText(painter.getName());
+ }
+
+
+ public SegmentationPainter getPainter() {
+ return view.getPainter();
+ }
+
+
+ public void setAutoApply(boolean checked) {
+ if (!this.auto && checked) {
+ // Going from off to on, resegment now
+ apply();
+ }
+ this.auto = checked;
+ }
+
+
+ public boolean isAutoApply() {
+ return this.auto;
+ }
+
+
+ public boolean canZoomIn() {
+ return view.canZoomIn();
+ }
+
+
+ public boolean canZoomOut() {
+ return view.canZoomOut();
+ }
+
+
+ public boolean canZoomOriginal() {
+ return view.canZoomOriginal();
+ }
+
+
+ public boolean canZoomBestFit() {
+ return view.canZoomBestFit();
+ }
+
+
+ public boolean canUndo() {
+ return view.canUndo();
+ }
+
+
+ public boolean canRedo() {
+ return view.canRedo();
+ }
+
+
+ public boolean canClear() {
+ return view.canClear();
+ }
+
+
+ public boolean canShowOptions() {
+ if (hasSegmenter()) {
+ return segmenter.getOptions().size() > 0;
+ }
+ return false;
+ }
+
+
+ public Action getAction(Tool t) {
+ return t.action;
+ }
+
+
+ public void showSegmenterOptions() {
+ if (canShowOptions()) {
+ SegmenterOptionDialog dialog =
+ new SegmenterOptionDialog(getShell(), segmenter);
+
+ int result = dialog.open();
+ switch (result) {
+ case SegmenterOptionDialog.OK:
+ // Re-segment
+ apply();
+ }
+ }
+ }
+
+
+ public boolean hasSegmenter() {
+ return segmenter != null;
+ }
+
+
+ public void performSegmentation(AnnotationEvent e, SegmentationContext ctx) {
+
+ if (runnableContext == null) {
+ segment(e, ctx);
+ } else {
+ segmentWithRunnableContext(e, ctx);
+ }
+
+ // Repaint everything
+ repaint();
+
+ }
+
+
+ public void setRunnableContext(IRunnableContext context, boolean blocksOnFork) {
+ runnableContext = context;
+ this.blocksOnFork = blocksOnFork;
+ }
+
+
+ public void addContextChangeListener(ContextChangeListener listener) {
+ view.addContextChangeListener(listener);
+ }
+
+
+ public void removeContextChangeListener(ContextChangeListener listener) {
+ view.removeContextChangeListener(listener);
+ }
+
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ bar1.setEnabled(enabled);
+ bar2.setEnabled(enabled);
+ view.setEnabled(enabled);
+ updateToolStates();
+ }
+
+
+ private void run(boolean fork, IRunnableWithProgress runnable) {
+ boolean wasEnabled = isEnabled();
+
+ try {
+ // Disable interactions
+ setEnabled(false);
+
+ // Run segmentation
+ runnableContext.run(fork, false, runnable);
+
+ } catch (InvocationTargetException ex) {
+
+ // Can't happen without a runtime exception occurring, so re-wrap
+ throw new RuntimeException(ex);
+
+ } catch (InterruptedException ex) {
+
+ // Can't happen because cancellable is false
+ throw new RuntimeException(ex);
+
+ } finally {
+ setEnabled(wasEnabled);
+ }
+ }
+
+
+ private void segmentWithRunnableContext(
+ final AnnotationEvent e,
+ final SegmentationContext ctx)
+ {
+ // Fork if the segmenter is slow, and only if our runnable context blocks
+ boolean fork = blocksOnFork /*&& !segmenter.isFast()*/;
+
+ run(fork, new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ monitor.beginTask("Segmenting", IProgressMonitor.UNKNOWN);
+ try {
+ segment(e, ctx);
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+ }
+
+
+ private void start(final SegmentationContext ctx) {
+ if (ctx != null && hasSegmenter()) {
+ if (runnableContext == null) {
+
+ // No runnable context
+ segmenterProxy.init(ctx);
+ segmenterProxy.update(ctx);
+
+ } else {
+
+ // Run with progress monitor
+ run(blocksOnFork, new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) {
+ monitor.beginTask("Initializing", IProgressMonitor.UNKNOWN);
+ try {
+ segmenterProxy.init(ctx);
+ segmenterProxy.update(ctx);
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+
+ }
+
+ // Repaint everything
+ repaint();
+ }
+ }
+
+
+ private void segment(AnnotationEvent e, SegmentationContext ctx) {
+ if (hasSegmenter()) {
+ if (e == null) {
+ // No event triggered this, so do a full update
+ segmenterProxy.update(ctx);
+
+ } else {
+ // Call appropriate segmenter function
+ switch (e.type) {
+ case Cleared:
+ segmenterProxy.update(ctx);
+ break;
+
+ case Undone:
+ segmenterProxy.removed(ctx, e.annotation);
+ break;
+
+ case Redone:
+ case Added:
+ segmenterProxy.added(ctx, e.annotation);
+ break;
+
+ default:
+ throw new RuntimeException();
+ }
+ }
+ }
+ }
+
+
+ private boolean isAnnotatingBackground() {
+ return view.isAnnotatingBackground();
+ }
+
+
+ private boolean isAnnotatingForeground() {
+ return view.isAnnotatingForeground();
+ }
+
+
+ private String getText(Tool a) {
+ return props.getProperty(getKey(a, "text"));
+ }
+
+
+ private String getToolTip(Tool a) {
+ return props.getProperty(getKey(a, "tooltip"));
+ }
+
+
+ private ImageDescriptor getImage(Tool a) {
+ String location = props.getProperty(getKey(a, "image"));
+ if (location != null) {
+ if (location.length() == 0) {
+ return null;
+ }
+
+ try {
+ return ImageDescriptor.createFromURL(new URL(location));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+
+ private static String getKey(Tool action, String object) {
+ return String.format("SegmentationView.Action.%s.%s", action, object);
+ }
+
+
+ private int getType(Tool a) {
+ switch (a) {
+ case Foreground:
+ case Background:
+ case AutoApply:
+ return Action.AS_CHECK_BOX;
+ case SetBrushSize:
+ return Action.AS_DROP_DOWN_MENU;
+ case SetPainter:
+ return Action.AS_UNSPECIFIED;
+ default:
+ return Action.AS_PUSH_BUTTON;
+ }
+ }
+
+
+ private void execute(Tool a, Event e) {
+ switch (a) {
+ case Foreground:
+ setAnnotationType(AnnotationType.Foreground);
+ break;
+ case Background:
+ setAnnotationType(AnnotationType.Background);
+ break;
+ case ZoomIn:
+ zoomIn();
+ break;
+ case ZoomOut:
+ zoomOut();
+ break;
+ case ZoomOriginal:
+ zoomOriginal();
+ break;
+ case ZoomBestFit:
+ zoomBestFit();
+ break;
+ case Repaint:
+ repaint();
+ break;
+ case Undo:
+ undo();
+ break;
+ case Redo:
+ redo();
+ break;
+ case Clear:
+ clear();
+ break;
+ case SetBrushSize:
+ showBrushControl(e);
+ break;
+ case AutoApply:
+ setAutoApply(a.action.isChecked());
+ break;
+ case Apply:
+ apply();
+ break;
+ case SetPainter:
+ setPainter();
+ break;
+ case AssignButton:
+ assignLabel();
+ break;
+ case SegmenterOptions:
+ showSegmenterOptions();
+ break;
+ }
+
+ updateToolStates();
+ }
+
+
+ private void updateToolStates() {
+ if (isEnabled()) {
+ Tool.Apply.action.setEnabled(!Tool.AutoApply.action.isChecked());
+ Tool.Undo.action.setEnabled(canUndo());
+ Tool.Redo.action.setEnabled(canRedo());
+ Tool.ZoomIn.action.setEnabled(canZoomIn());
+ Tool.ZoomOut.action.setEnabled(canZoomOut());
+ Tool.ZoomOriginal.action.setEnabled(canZoomOriginal());
+ Tool.ZoomBestFit.action.setEnabled(canZoomBestFit());
+ comboLabel.setEnabled(canZoomBestFit());
+ assign.setEnabled(canZoomBestFit() & !(comboLabel.getText().isEmpty()));
+ Tool.Foreground.action.setChecked(isAnnotatingForeground());
+ Tool.Background.action.setChecked(isAnnotatingBackground());
+ Tool.Clear.action.setEnabled(canClear());
+ Tool.AutoApply.action.setChecked(auto);
+ Tool.SegmenterOptions.action.setEnabled(canShowOptions());
+
+ // Always enabled if view enabled
+ Tool.SetBrushSize.action.setEnabled(true);
+ Tool.SegmenterOptions.action.setEnabled(true);
+ Tool.SetPainter.action.setEnabled(true);
+ Tool.Repaint.action.setEnabled(true);
+ Tool.Foreground.action.setEnabled(true);
+ Tool.Background.action.setEnabled(true);
+ Tool.AutoApply.action.setEnabled(true);
+
+ } else {
+ // Everything disabled
+ for (Tool t : Tool.values()) {
+ t.action.setEnabled(false);
+ }
+ }
+
+ }
+
+ private void setPainter() {
+ SegmentationPainter painter = painters.get(combo.getText());
+ setPainter(painter);
+ }
+ /*
+ Assign the label to the segment on clicking assign button and update the image segment.
+ */
+ private void assignLabel() {
+ SegmentationPainter painter = painters.get("Display Label");
+ setPainter(painter);
+ }
+
+ private void setLabel(String content) {
+ try
+ {
+ String content1 = URLEncoder.encode(content.toString(),"UTF-8");
+ URL url = new URL("http://palea.cgrb.oregonstate.edu/services/PO_web_service.php?request_type=term_search&search_value="+content1+"&inc_synonyms&branch_filter=plant_anatomy&max=20");
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ if(connection.getResponseCode() == 200)
+ {
+ String inputLine;
+ JSONObject object = null;
+ while ((inputLine = in.readLine()) != null)
+ {
+ object = new JSONObject(inputLine);
+ }
+ JSONArray array = new JSONArray(object.getString("PO_term_search_response"));
+ String listElement;
+ for(int i=0; i<array.length();i++)
+ {
+ if(array.getJSONObject(i).getString("match_type").trim().equals("synonym"))
+ {
+ listElement = (array.getJSONObject(i).getString("match")+" {"+array.getJSONObject(i).getString("is_synonym_of")+"}");
+ comboLabel.add(listElement,i);
+ }
+ else if(array.getJSONObject(i).getString("match_type").equals("term"))
+ {
+ listElement = array.getJSONObject(i).getString("match");
+ comboLabel.add(listElement,i);
+ }
+ }
+ in.close();
+ }
+ else
+ {
+ System.out.println("Response error");
+ }
+ }
+ catch(Exception ex)
+ {
+ }
+ assign.setEnabled(!(comboLabel.getText().isEmpty()));
+ }
+
+ private void apply() {
+ performSegmentation(null, view.getContext());
+ }
+
+
+ private void showBrushControl(Event e) {
+ brushControl.showBelow(bar1, (ToolItem) e.widget);
+ }
+
+
+ /**
+ * Handles a context change in the view.
+ *
+ * @param e
+ * The event.
+ */
+ private void handleContextChanged(ContextChangedEvent e) {
+ if (e.oldContext != null) {
+ e.oldContext.removeAnnotationListener(eventHandler);
+
+ // Finish old segmentation
+ segmenterProxy.finish(e.oldContext);
+ }
+
+ if (e.newContext != null) {
+ e.newContext.addAnnotationListener(eventHandler);
+
+ // Begin new segmentation (can cause deferred events
+ // to be processed, so we async exec to ensure that
+ // the other context change handlers are called before
+ // deferred events are processed)
+ final SegmentationContext ctx = e.newContext;
+ getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ start(ctx);
+ }
+ });
+
+ }
+
+ updateToolStates();
+ }
+
+
+ /**
+ * Cleans up resources when the view is disposed.
+ */
+ private void handleDisposed() {
+
+ SegmentationContext ctx = view.getContext();
+ if (ctx != null && !ctx.isDisposed()) {
+
+ // Finish any segmentation
+ segmenterProxy.finish(ctx);
+
+ // Dispose context
+ ctx.dispose();
+ }
+
+ // Dispose all painters
+ painters.dispose();
+ }
+
+
+ /**
+ * Tool bar action. Delegates running to the execute method.
+ */
+ private class ToolAction extends Action {
+ private final Tool tool;
+
+ public ToolAction(Tool tool) {
+ super(SegmentationView.this.getText(tool), getType(tool));
+ this.tool = tool;
+ setToolTipText(SegmentationView.this.getToolTip(tool));
+ setImageDescriptor(SegmentationView.this.getImage(tool));
+ tool.action = this;
+ }
+
+ public void runWithEvent(Event e) {
+ execute(tool, e);
+ }
+ }
+
+
+ /**
+ * Handles various events coming from the view and toolbar controls.
+ *
+ */
+ private final class EventHandler extends SelectionAdapter implements
+ ZoomListener,
+ SelectionListener,
+ ContextChangeListener,
+ AnnotationListener,
+ DisposeListener
+ {
+
+ public void widgetDisposed(DisposeEvent e) {
+ handleDisposed();
+ }
+
+
+ public void zoomChanged(ZoomEvent e) {
+ updateToolStates();
+ }
+
+
+ public void widgetSelected(SelectionEvent e) {
+ setBrushSize(brushControl.getBrushSize());
+ }
+
+
+ public void contextChanged(ContextChangedEvent e) {
+ handleContextChanged(e);
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation performed while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation redone while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+
+
+ public void annotationUndone(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotation undone while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+
+ updateToolStates();
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ if (!isEnabled()) {
+ log.warning("annotations cleared while not enabled");
+ }
+
+ if (auto && isEnabled()) {
+ performSegmentation(e, view.getContext());
+ }
+ updateToolStates();
+ }
+ };
+
+ /**
+ * Class that prevents segmentation algorithms crashing the application
+ * by catching any thrown exceptions and logging them.
+ *
+ * @author Kevin McGuinness
+ */
+ private class RobustSegmenterProxy {
+
+ public void init(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.init(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.init()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void update(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.update(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.update()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void added(SegmentationContext ctx, Annotation a) {
+ if (segmenter != null) {
+ try {
+ segmenter.added(ctx, a);
+ } catch (Throwable th) {
+ severe(th, "%s.added()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void removed(SegmentationContext ctx, Annotation a) {
+ if (segmenter != null) {
+ try {
+ segmenter.removed(ctx, a);
+ } catch (Throwable th) {
+ severe(th, "%s.removed()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public void finish(SegmentationContext ctx) {
+ if (segmenter != null) {
+ try {
+ segmenter.finish(ctx);
+ } catch (Throwable th) {
+ severe(th, "%s.finish()", getSegmenterClassName());
+ }
+ }
+ }
+
+ public String getSegmenterClassName() {
+ return segmenter.getClass().getSimpleName();
+ }
+
+ private void severe(Throwable th, String message, Object ... args) {
+ log.log(Level.SEVERE, String.format(message, args), th);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+
+import ie.dcu.apps.ist.controllers.AnnotationTool;
+import ie.dcu.apps.ist.event.*;
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.segment.painters.*;
+import ie.dcu.swt.*;
+import ie.dcu.swt.event.ZoomListener;
+
+import java.util.*;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+public class AnnotatedImageControl extends Composite {
+ private final List<ContextChangeListener> listeners;
+ private final ImageControl view;
+ private SegmentationContext ctx;
+ private AnnotationTool tool;
+ private SegmentationPainter painter;
+ private Cursor cursor;
+
+ public AnnotatedImageControl(Composite parent, int style) {
+ super(parent, style);
+ setLayout(new FillLayout());
+ view = new ImageControl(this, SWT.NONE);
+ listeners = new ArrayList<ContextChangeListener>(2);
+ painter = new CombinedPainter();
+ view.getCanvas().addMouseMoveListener(cursorChanger);
+ addDisposeListener(disposeListener);
+ }
+
+
+ public ImageControl getImageControl() {
+ return view;
+ }
+
+
+ public Canvas getCanvas() {
+ return view.getCanvas();
+ }
+
+
+ public SegmentationPainter getPainter() {
+ return painter;
+ }
+
+
+ public void setPainter(SegmentationPainter painter) {
+ if (this.painter != painter) {
+ this.painter = painter;
+ recreate();
+ }
+ }
+
+
+ public SegmentationContext getContext() {
+ return ctx;
+ }
+
+
+ public void setContext(SegmentationContext ctx) {
+
+ // Remember old context for event
+ SegmentationContext old = this.ctx;
+
+ // Detach old tool
+ int lineWidth = 1;
+ if (tool != null) {
+ lineWidth = tool.getLineWidth();
+ tool.detach();
+ tool = null;
+ }
+
+ // Detach old annotation listener
+ if (ctx != null) {
+ ctx.getAnnotations().removeAnnotationListener(listener);
+ }
+
+ // Assign context
+ this.ctx = ctx;
+
+ if (ctx != null) {
+ // Attach new annotation listener
+ AnnotationManager annotations = ctx.getAnnotations();
+ annotations.addAnnotationListener(listener);
+
+ // Attach a new annotation tool
+ tool = new AnnotationTool(annotations, view);
+ tool.setLineWidth(lineWidth);
+ }
+
+ // Create the initial image
+ recreate();
+
+ // Fire event
+ fireContextChanged(old);
+ }
+
+
+ public int getLineWidth() {
+ if (tool != null) {
+ return tool.getLineWidth();
+ }
+ return 1;
+ }
+
+
+ public void setLineWidth(int width) {
+ if (tool != null) {
+ tool.setLineWidth(width);
+ }
+ }
+
+
+ public AnnotationType getAnnotationType() {
+ if (tool != null) {
+ return tool.getType();
+ }
+ return AnnotationType.Foreground;
+ }
+
+
+ public void setAnnotationType(AnnotationType type) {
+ if (tool != null) {
+ tool.setType(type);
+ }
+ }
+
+
+ public float getZoom() {
+ return view.getZoom();
+ }
+
+
+ public void setZoom(float zoom) {
+ view.setZoom(zoom);
+ }
+
+
+ public float getZoomStep() {
+ return view.getZoomStep();
+ }
+
+
+ public void setZoomStep(float zoomStep) {
+ view.setZoomStep(zoomStep);
+ }
+
+
+ public void zoomBestFit() {
+ view.zoomBestFit();
+ }
+
+
+ public void zoomIn() {
+ view.zoomIn();
+ }
+
+
+ public void zoomOriginal() {
+ view.zoomOriginal();
+ }
+
+
+ public void zoomOut() {
+ view.zoomOut();
+ }
+
+
+ public void undo() {
+ if (ctx != null) {
+ ctx.getAnnotations().undo();
+ }
+ }
+
+
+ public void redo() {
+ if (ctx != null) {
+ ctx.getAnnotations().redo();
+ }
+ }
+
+
+ public void clear() {
+ if (ctx != null) {
+ ctx.getAnnotations().clear();
+ }
+ }
+
+
+ public boolean canUndo() {
+ if (ctx != null) {
+ return ctx.getAnnotations().canUndo();
+ }
+ return false;
+ }
+
+
+ public boolean canRedo() {
+ if (ctx != null) {
+ return ctx.getAnnotations().canRedo();
+ }
+ return false;
+ }
+
+
+ public boolean canClear() {
+ if (ctx != null) {
+ return ctx.getAnnotations().count() > 0;
+ }
+ return false;
+ }
+
+
+ public boolean canZoomBestFit() {
+ return view.canZoomBestFit();
+ }
+
+
+ public boolean canZoomIn() {
+ return view.canZoomIn();
+ }
+
+
+ public boolean canZoomOriginal() {
+ return view.canZoomOriginal();
+ }
+
+
+ public boolean canZoomOut() {
+ return view.canZoomOut();
+ }
+
+
+ /**
+ * Redraw the entire canvas buffer.
+ */
+ public void repaint() {
+ if (ctx != null) {
+ painter.paint(ctx, view.getImage());
+ }
+ }
+
+
+ public boolean isAnnotatingForeground() {
+ return getAnnotationType() == AnnotationType.Foreground;
+ }
+
+
+ public boolean isAnnotatingBackground() {
+ return getAnnotationType() == AnnotationType.Background;
+ }
+
+
+ public void addAnnotationListener(AnnotationListener listener) {
+ if (ctx == null) {
+ throw new IllegalStateException();
+ }
+ ctx.getAnnotations().addAnnotationListener(listener);
+ }
+
+
+ public void removeAnnotationListener(AnnotationListener listener) {
+ if (ctx != null) {
+ ctx.getAnnotations().removeAnnotationListener(listener);
+ }
+ }
+
+
+ public void addZoomListener(ZoomListener listener) {
+ view.addZoomListener(listener);
+ }
+
+
+ public void removeZoomListener(ZoomListener listener) {
+ view.removeZoomListener(listener);
+ }
+
+
+ public void addContextChangeListener(ContextChangeListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void removeContextChangeListener(ContextChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ private void fireContextChanged(SegmentationContext old) {
+ ContextChangedEvent e = null;
+ for (ContextChangeListener l : listeners) {
+ if (e == null) {
+ e = new ContextChangedEvent(this, old, ctx);
+ }
+ l.contextChanged(e);
+ }
+ }
+
+
+ /**
+ * Re-construct the display image buffer, and dispose the old one if
+ * necessary. If the context is <code>null</code>, then set the display
+ * buffer to null and dispose of the old one.
+ */
+ private void recreate() {
+ if (ctx == null) {
+
+ // Set null image (disposing the old one)
+ view.setImage(null, true);
+
+ } else {
+
+ // Check if we can reuse what we have
+ boolean sameSize = false;
+ if (view.hasImage()) {
+ if (view.getImageBounds().equals(ctx.getBounds())) {
+
+ // Okay, we can reuse the buffer we have :-)
+ sameSize = true;
+ }
+ }
+
+ if (!sameSize) {
+
+ // Create initial image
+ ObservableImage buffer = new ObservableImage(
+ SwtUtils.createImage(ctx.getBounds())
+ );
+
+ // Set the image (disposing the old one)
+ view.setImage(buffer, true);
+ }
+
+ // Draw the image
+ repaint();
+ }
+ }
+
+
+ private final DisposeListener disposeListener = new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ if (cursor != null) {
+ cursor.dispose();
+ }
+ }
+ };
+
+
+ /**
+ * Changes the cursor to a cross-hair when mouse is over the image.
+ *
+ */
+ private final MouseMoveListener cursorChanger = new MouseMoveListener() {
+
+ public void mouseMove(MouseEvent e) {
+ Canvas canvas = view.getCanvas();
+ if (view.imageContains(new Point(e.x, e.y))) {
+ if (cursor == null) {
+ cursor = CursorFactory.createCrosshairCursor();
+ }
+ canvas.setCursor(cursor);
+ } else {
+ canvas.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
+ }
+ }
+ };
+
+
+ /**
+ * Listens for annotations and updates the buffer and view accordingly.
+ */
+ private final AnnotationListener listener = new AnnotationListener() {
+
+ public void annotationUndone(AnnotationEvent e) {
+ // Suspend notifications to prevent repainting the whole thing
+ view.getImage().setSuspendNotifications(true);
+
+ // Redraw all to buffer
+ repaint();
+
+ // Re-enable notifications
+ view.getImage().setSuspendNotifications(false);
+
+ // Repaint just the changed area
+ view.getImage().fireImageChanged(e.annotation.getBounds());
+ }
+
+
+ public void annotationsCleared(AnnotationEvent e) {
+ // Repaint everything
+ repaint();
+ }
+
+
+ public void annotationRedone(AnnotationEvent e) {
+ // Paint the new annotation
+ e.annotation.paint(view.getImage());
+ }
+
+
+ public void annotationPerformed(AnnotationEvent e) {
+ // Paint the new annotation
+ e.annotation.paint(view.getImage());
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import ie.dcu.swt.PopupComposite;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Pop up control for changing brush sizes.
+ *
+ * @author Kevin McGuinness
+ */
+public class BrushControl extends PopupComposite {
+ private static final int MAX_BRUSH_SIZE = 20;
+
+ private final Label label;
+ private final Scale scale;
+
+
+ public BrushControl(Shell parent, int style) {
+ super(parent, style);
+
+ // Create controls
+ label = new Label(this, SWT.NONE);
+ scale = new Scale(this, SWT.NONE);
+
+ configureControls();
+ configureListeners();
+ layoutControls();
+ }
+
+
+ public void setBrushSize(int size) {
+ scale.setSelection(size);
+ updateLabel();
+ }
+
+
+ public int getBrushSize() {
+ return scale.getSelection();
+ }
+
+
+ public void addSelectionListener(SelectionListener listener) {
+ scale.addSelectionListener(listener);
+ }
+
+
+ public void removeSelectionListener(SelectionListener listener) {
+ scale.removeSelectionListener(listener);
+ }
+
+
+ private void configureControls() {
+ scale.setMinimum(1);
+ scale.setMaximum(MAX_BRUSH_SIZE);
+ scale.setIncrement(1);
+ scale.setPageIncrement(2);
+ setBrushSize(1);
+ }
+
+
+ private void configureListeners() {
+ scale.addSelectionListener(new ScaleChangeListener());
+ }
+
+
+ private void layoutControls() {
+ setLayout(new GridLayout());
+
+ // Layout label
+ GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ label.setLayoutData(gd);
+
+ // Layout scale control
+ gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.horizontalAlignment = SWT.FILL;
+ gd.minimumWidth = 150;
+ scale.setLayoutData(gd);
+ }
+
+
+ private void updateLabel() {
+ int size = scale.getSelection();
+ label.setText(String.format("Brush Size: %dpx ", size));
+ }
+
+
+ private final class ScaleChangeListener
+ extends SelectionAdapter {
+
+ public void widgetSelected(SelectionEvent e) {
+ updateLabel();
+ }
+ };
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import java.beans.*;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Button for selecting colors.
+ *
+ * @author Kevin McGuinness
+ */
+public class ColorSelector {
+ public static final String DATA_KEY = "ColorEditor";
+ public static final String COLOR_PROPERTY = "color";
+
+ private final PropertyChangeSupport pcs;
+ private final Point extent;
+ private final Button button;
+ private final ControlListener listener;
+
+ private Image image;
+ private RGB rgb;
+
+
+ public ColorSelector(Composite parent) {
+
+ pcs = new PropertyChangeSupport(this);
+ button = new Button(parent, SWT.PUSH);
+ extent = computeImageSize(parent);
+ image = new Image(parent.getDisplay(), extent.x, extent.y);
+ listener = new ControlListener();
+
+ GC gc = new GC(image);
+ gc.setBackground(button.getBackground());
+ gc.fillRectangle(0, 0, extent.x, extent.y);
+ gc.dispose();
+
+
+ button.setData(DATA_KEY, this);
+ button.setImage(image);
+ button.addListener(SWT.Selection, listener);
+ button.addListener(SWT.Dispose, listener);
+ }
+
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
+
+
+ public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(name, listener);
+ }
+
+
+ public PropertyChangeListener[] getPropertyChangeListeners() {
+ return pcs.getPropertyChangeListeners();
+ }
+
+
+ public PropertyChangeListener[] getPropertyChangeListeners(String name) {
+ return pcs.getPropertyChangeListeners(name);
+ }
+
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(listener);
+ }
+
+
+ public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(name, listener);
+ }
+
+
+ public RGB getColor() {
+ return rgb;
+ }
+
+
+ public void setColor(RGB rgb) {
+ if (rgb != null && this.rgb != rgb) {
+ RGB old = rgb;
+ this.rgb = rgb;
+ updateImage();
+ pcs.firePropertyChange(COLOR_PROPERTY, old, rgb);
+ }
+ }
+
+
+ public Button getButton() {
+ return button;
+ }
+
+
+ public void addListener(int eventType, Listener listener) {
+ button.addListener(eventType, listener);
+ }
+
+
+ public void setLayoutData(Object layoutData) {
+ button.setLayoutData(layoutData);
+ }
+
+
+ public void setData(Object data) {
+ button.setData(data);
+ }
+
+
+ public Object getData() {
+ return button.getData();
+ }
+
+
+ protected void updateImage() {
+ Display display = button.getDisplay();
+
+ GC gc = new GC(image);
+ gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
+ gc.drawRectangle(0, 2, extent.x - 1, extent.y - 4);
+
+ Color color = new Color(display, rgb);
+ gc.setBackground(color);
+ gc.fillRectangle(1, 3, extent.x - 2, extent.y - 5);
+ gc.dispose();
+
+ button.setImage(image);
+ color.dispose();
+ }
+
+
+ protected Point computeImageSize(Composite parent) {
+ GC gc = new GC(parent);
+ Font f = JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT);
+ gc.setFont(f);
+ int height = gc.getFontMetrics().getHeight();
+ gc.dispose();
+ Point p = new Point(height * 3 - 6, height);
+ return p;
+ }
+
+
+ private void handleDispose(Event e) {
+ if (image != null) {
+ image.dispose();
+ image = null;
+ }
+ }
+
+
+ private void handleSelection(Event e) {
+ ColorDialog dialog = new ColorDialog(button.getShell());
+ dialog.setRGB(rgb);
+ RGB rgb = dialog.open();
+ setColor(rgb);
+ }
+
+
+ private final class ControlListener implements Listener {
+ public void handleEvent(Event e) {
+ switch (e.type) {
+ case SWT.Selection:
+ handleSelection(e);
+ break;
+ case SWT.Dispose:
+ handleDispose(e);
+ break;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import java.net.URL;
+
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+
+public class ImageMenuManager extends MenuManager {
+ private Image image;
+
+ public ImageMenuManager() {
+ super();
+ }
+
+ public ImageMenuManager(String text) {
+ super(text);
+ }
+
+ public ImageMenuManager(String text, URL imageURL) {
+ super(text);
+ setImageURL(imageURL);
+ }
+
+ public void setImageURL(URL url) {
+ ImageDescriptor descriptor = ImageDescriptor.createFromURL(url);
+ this.image = descriptor.createImage();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ if (image != null) {
+ image.dispose();
+ }
+ }
+
+ @Override
+ public void fill(Menu parent, int index) {
+ super.fill(parent, index);
+ MenuItem item = getMenuItem();
+ if (item != null) {
+ item.setImage(image);
+ }
+ }
+
+ protected MenuItem getMenuItem() {
+ Menu menu = getMenu();
+ return (menu != null) ? menu.getParentItem() : null;
+ }
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+
+import ie.dcu.apps.ist.event.*;
+
+import java.util.ArrayList;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * A timer user interface element. Displays a component used
+ * to implement a count-down.
+ *
+ * @author Kevin McGuinness
+ */
+public class SwtTimer implements TickerListener {
+
+ /**
+ * Timer is modeled as a state machine. These are the states.
+ */
+ public static enum State { Initial, Ready, Running, Paused };
+
+
+ /**
+ * List of listeners interested in changes to the object state.
+ */
+ private final ArrayList<StateListener> stateListeners;
+
+
+ /**
+ * List of listeners interested in timeouts.
+ */
+ private final ArrayList<TimeoutListener> timeoutListeners;
+
+
+ /**
+ * The drawing canvas where the numbers are drawn.
+ */
+ private final Canvas canvas;
+
+
+ /**
+ * The ticker posts tick events every second or so.
+ */
+ private Ticker ticker;
+
+
+ /**
+ * The current state of the timer.
+ */
+ private State state;
+
+
+ /**
+ * The font used to render the numbers.
+ */
+ private Font font;
+
+
+ /**
+ * Total time for the timer, in seconds.
+ */
+ private int time;
+
+
+ /**
+ * Remaining (displayed) time, in seconds.
+ */
+ private int remaining;
+
+
+ /**
+ * Create the timer. Style bits are passed to the underlying canvas.
+ *
+ * @param parent
+ * Parent container.
+ * @param style
+ * Style bits.
+ */
+ public SwtTimer(Composite parent, int style) {
+ stateListeners = new ArrayList<StateListener>(1);
+ timeoutListeners = new ArrayList<TimeoutListener>(1);
+ canvas = new Canvas(parent, style);
+ addListeners();
+ enter(State.Initial);
+ }
+
+
+ /**
+ * Add listeners to various components.
+ */
+ private void addListeners() {
+ // Listen for paint events
+ canvas.addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent e) {
+ paint(e.gc);
+ }
+ });
+
+ // Listen for dispose event
+ canvas.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ dispose();
+ }
+ });
+ }
+
+
+ /**
+ * Set the layout data for the component.
+ *
+ * @param data
+ * The layout data.
+ */
+ public void setLayoutData(Object data) {
+ canvas.setLayoutData(data);
+ }
+
+
+ /**
+ * Returns the layout data for the component.
+ *
+ * @return The layout data.
+ */
+ public Object getLayoutData() {
+ return canvas.getLayoutData();
+ }
+
+
+ /**
+ * Get the contained canvas.
+ *
+ * @return The contained canvas.
+ */
+ public Canvas getCanvas() {
+ return canvas;
+ }
+
+
+ /**
+ * Returns the shell associated with the canvas.
+ *
+ * @return The shell.
+ */
+ public Shell getShell() {
+ return canvas.getShell();
+ }
+
+
+ /**
+ * Get the state of the timer.
+ *
+ * @return The timer state.
+ */
+ public State getState() {
+ return state;
+ }
+
+
+ /**
+ * Add a state change listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void addStateListener(StateListener listener) {
+ stateListeners.add(listener);
+ }
+
+
+ /**
+ * Remove a state change listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void removeStateListener(StateListener listener) {
+ stateListeners.remove(listener);
+ }
+
+
+ /**
+ * Add a time-out listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void addTimeoutListener(TimeoutListener listener) {
+ timeoutListeners.add(listener);
+ }
+
+
+ /**
+ * Remove a time-out listener.
+ *
+ * @param listener
+ * The listener.
+ */
+ public void removeTimeoutListener(TimeoutListener listener) {
+ timeoutListeners.remove(listener);
+ }
+
+
+ /**
+ * Returns an estimate of the elapsed time, in seconds.
+ *
+ * @return The elapsed time.
+ */
+ public int getElapsed() {
+ return time - remaining;
+ }
+
+
+ /**
+ * Set the timeout for the timer, in seconds.
+ *
+ * @param seconds
+ * The number of seconds on the timer.
+ * @throws IllegalStateException
+ * If {@link #canSet()} is <code>false</code>.
+ */
+ public void set(int seconds) throws IllegalStateException {
+ switch (state) {
+ case Initial:
+ case Ready:
+ time = seconds;
+ remaining = time;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ enter(State.Ready);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if set can be called. Set cannot be called when
+ * the timer is running or paused.
+ *
+ * @return <code>true</code> if set can be called.
+ */
+ public boolean canSet() {
+ switch (state) {
+ case Initial:
+ case Ready:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Start the timer. A new thread will control the timer count-down, so this
+ * method returns once it has started.
+ *
+ * @throws IllegalStateException
+ * If {@link #canStart()} is <code>false</code>.
+ */
+ public void start() throws IllegalStateException {
+ switch (state) {
+ case Ready:
+ ticker = new Ticker(this, 1000);
+ ticker.start();
+ break;
+ case Paused:
+ ticker.resume();
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Running);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if start can be called. Start cannot be called
+ * when the timer is in it's initial or running state.
+ *
+ * @return <code>true</code> if start can be called.
+ */
+ public boolean canStart() {
+ switch (state) {
+ case Ready:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Pause the count-down. Temporarily suspends the count-down thread.
+ *
+ * @throws IllegalStateException
+ * If {@link #canPause()} is <code>false</code>.
+ */
+ public void pause() throws IllegalStateException {
+ switch (state) {
+ case Running:
+ ticker.pause();
+ break;
+ case Paused:
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Paused);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if pause can be called. Pause cannot be called
+ * when the timer is in it's initial or ready state.
+ *
+ * @return <code>true</code> if pause can be called.
+ */
+ public boolean canPause() {
+ switch (state) {
+ case Running:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Reset the timer. Causes the timer to stop and return to the ready
+ * state, reseting the time-out to the last one set.
+ *
+ * @throws IllegalStateException
+ * If {@link #canReset()} is <code>false</code>.
+ */
+ public void reset() throws IllegalStateException {
+ switch (state) {
+ case Ready:
+ break;
+ case Running:
+ case Paused:
+ ticker.stop();
+ ticker = null;
+ remaining = time;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ enter(State.Ready);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if reset can be called. Reset cannot be called
+ * when the timer is in it's initial state.
+ *
+ * @return <code>true</code> if reset can be called.
+ */
+ public boolean canReset() {
+ switch (state) {
+ case Ready:
+ case Running:
+ case Paused:
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Clear the timer. Returns the timer to its initial state, stopping
+ * it if necessary.
+ */
+ public void clear() {
+ switch (state) {
+ case Initial:
+ break;
+ case Ready:
+ time = 0;
+ remaining = 0;
+ break;
+ case Running:
+ case Paused:
+ ticker.stop();
+ ticker = null;
+ remaining = 0;
+ time = 0;
+ break;
+ }
+
+ enter(State.Initial);
+
+ repaint();
+ }
+
+
+ /**
+ * Returns <code>true</code> if clear can be called.
+ *
+ * @return Always returns <code>true</code>.
+ */
+ public boolean canClear() {
+ return true;
+ }
+
+
+ /**
+ * Tell the timer to repaint itself.
+ */
+ public void repaint() {
+ if (!canvas.isDisposed()) {
+ canvas.redraw();
+ }
+ }
+
+
+ /**
+ * Called at each ticker tick interval. Should not be invoked by clients.
+ */
+ public void tick(final TickerEvent evt) {
+ if (canvas.isDisposed()) {
+ // Widget is disposed, ignore timer events
+ return;
+ }
+
+ long elapsed = evt.getElapsed();
+ int remaining = time - (int) (elapsed / 1000);
+
+ if (remaining <= 0) {
+ // We're done
+ this.remaining = 0;
+
+ // Stop and nullify ticker
+ if (ticker != null) {
+ ticker.stopLater();
+ ticker = null;
+ }
+
+ // Enqueue the ui update the event dispatch thread
+ canvas.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ enter(State.Initial);
+
+ // repaint
+ repaint();
+
+ // fire timeout event
+ fireTimeoutEvent();
+ }
+ });
+
+ } else if (this.remaining != remaining) {
+
+ // Update remaining
+ this.remaining = remaining;
+
+ // Enqueue a repaint
+ canvas.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ // repaint
+ repaint();
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Enter the given state.
+ *
+ * @param state The state.
+ */
+ private void enter(State state) {
+ if (this.state != state) {
+ this.state = state;
+ fireStateChanged();
+ }
+ }
+
+
+ /**
+ * Fires a state changed event.
+ */
+ private void fireStateChanged() {
+ if (!stateListeners.isEmpty()) {
+ StateEvent evt = new StateEvent(this);
+ for (StateListener s : stateListeners) {
+ s.stateChanged(evt);
+ }
+ }
+ }
+
+
+ /**
+ * Sends a timeout event to listeners.
+ */
+ private void fireTimeoutEvent() {
+ if (!timeoutListeners.isEmpty()) {
+ TimeoutEvent evt = new TimeoutEvent(this);
+ for (TimeoutListener t : timeoutListeners) {
+ t.timeoutOccured(evt);
+ }
+ }
+ }
+
+
+ /**
+ * Paints the timer.
+ *
+ * @param gc
+ * The graphics context.
+ */
+ private void paint(GC gc) {
+ gc.setFont(getTimerFont());
+
+ int ss = remaining % 60;
+ int mm = (remaining % 3600) / 60;
+ int hh = remaining / 3600;
+
+ String timestr = formatTime(ss, mm, hh);
+
+ Point t = getStringDimensions(gc, timestr);
+ Point c = getCanvasDimension();
+
+ int x = c.x / 2 - t.x / 2;
+ int y = c.y / 2 - t.y / 2;
+
+ if (remaining < 20 && state != State.Initial) {
+ gc.setForeground(getWarningColor());
+ } else {
+ gc.setForeground(getTimerColor());
+ }
+
+ gc.drawString(timestr, x, y);
+ }
+
+
+ /**
+ * Tidies native resources. Called automatically.
+ */
+ private void dispose() {
+ if (font != null) {
+ font.dispose();
+ font = null;
+ }
+ }
+
+
+ /**
+ * Formats a time string.
+ */
+ private String formatTime(int ss, int mm, int hh) {
+ String timestr;
+ if (hh == 0) {
+ timestr = String.format("%02d:%02d", mm, ss);
+ } else {
+ timestr = String.format("%02d:%02d:%02d", hh, mm, ss);
+ }
+ return timestr;
+ }
+
+
+ /**
+ * Returns the dimensions of a given string as drawn with the current font on
+ * the given graphics context.
+ */
+ private static Point getStringDimensions(GC gc, String str) {
+ int x = 0;
+ for (int i = 0; i < str.length(); i++) {
+ x += gc.getAdvanceWidth(str.charAt(i));
+ }
+ int y = gc.getFontMetrics().getHeight();
+ return new Point(x,y);
+ }
+
+
+ /**
+ * Returns the dimensions of the canvas as a point.
+ *
+ * @return A point where x is the width and y is the height.
+ */
+ private Point getCanvasDimension() {
+ Rectangle bounds = canvas.getBounds();
+ return new Point(bounds.width, bounds.height);
+ }
+
+
+ private Color getWarningColor() {
+ return getSystemColor(SWT.COLOR_RED);
+ }
+
+
+ private Color getTimerColor() {
+ return getSystemColor(SWT.COLOR_DARK_BLUE);
+ }
+
+
+ private Font getTimerFont() {
+ if (font == null) {
+ font = new Font(canvas.getDisplay(), "Sans", 30, SWT.NONE);
+ }
+ return font;
+ }
+
+
+ private Color getSystemColor(int id) {
+ return canvas.getDisplay().getSystemColor(id);
+ }
+
+ public static void main(String[] args) {
+ Display display = new Display();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+ shell.setBounds(800, 100, 220, 200);
+ shell.setLayout(new FillLayout());
+
+ SwtTimer timer = new SwtTimer(shell, SWT.NONE);
+ timer.set(120);
+ timer.start();
+
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ }
+
+}
--- /dev/null
+package ie.dcu.apps.ist.widgets;
+
+import ie.dcu.apps.ist.event.*;
+
+
+public class Ticker implements Runnable {
+ private final TickerListener listener;
+
+ private Thread thread = null;
+ private boolean pause = false;
+ private boolean stop = false;
+ private long napTime = 100;
+ private long interval = 1000;
+ private long elapsed = 0;
+ private long startTime = 0;
+ private long pausedTime = 0;
+
+ // State variables
+ private boolean paused = false;
+ private boolean stopped = true;
+
+ public Ticker(TickerListener listener) {
+
+ // Check listener
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ this.listener = listener;
+ }
+
+
+ public Ticker(TickerListener listener, long interval) {
+
+ // Check listener
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ // Check interval
+ if (interval < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ // Assign
+ this.listener = listener;
+ this.interval = interval;
+ this.napTime = interval / 10;
+ }
+
+
+ public synchronized long elapsed() {
+ return elapsed;
+ }
+
+
+ public void start() {
+ if (thread != null) {
+ throw new IllegalStateException("Cannot restart threads");
+ }
+ thread = new Thread(this, "TimerThread");
+ thread.start();
+ }
+
+
+ public void run() {
+
+ synchronized (this) {
+ stopped = false;
+ }
+
+ // Set start time
+ startTime = System.currentTimeMillis();
+
+ // Loop
+ while (true) {
+
+ // Yawn! Z Z z z z Z Z Z z z z Z Z Z ...
+ sleep(napTime);
+
+ // Wait while thread is paused
+ synchronized (this) {
+ if (pause) {
+
+ // Remember when pausing started
+ long now = System.currentTimeMillis();
+
+ // Set paused flag to true and notify
+ paused = true;
+ notifyAll();
+
+ // Wait until woken up
+ while (paused) doWait();
+
+ // Add duration we've been paused for
+ pausedTime += System.currentTimeMillis() - now;
+
+ // Clear pause flag && notify
+ pause = false;
+ notifyAll();
+ }
+ }
+
+ // Check for stop flag
+ synchronized (this) {
+ if (stop) {
+ stopped = true;
+ notifyAll();
+ break;
+ }
+ }
+
+ // HI-HO-HI-HO ...
+
+ work();
+ }
+ }
+
+
+ private void work() {
+ boolean event = false;
+
+ synchronized (this) {
+ // Calculate total elapsed time in milliseconds
+ long now = System.currentTimeMillis();
+ long elapsed = now - (startTime + pausedTime);
+
+ // Calculate time elapsed since last event
+ long sinceLast = elapsed - this.elapsed;
+ if (interval <= sinceLast) {
+
+ // Compensate for any difference
+ long diff = sinceLast - interval;
+
+ // Its time for another event
+ this.elapsed = elapsed - diff;
+
+ // Fire event flag
+ event = true;
+ }
+ }
+
+ // Do event
+ if (event) {
+ synchronized (listener) {
+ listener.tick(new TickerEvent(this, elapsed));
+ }
+ }
+ }
+
+
+ public void pause() {
+ // Pause running thread
+ synchronized (this) {
+ pause = true;
+
+ // Wait for pause to occur
+ while (!paused) doWait();
+ }
+ }
+
+
+ public void resume() {
+ // Wake up paused threads
+ synchronized (this) {
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+ }
+ }
+
+
+ public boolean isPaused() {
+ synchronized (this) {
+ return paused;
+ }
+ }
+
+
+ public void stop() {
+
+ synchronized (this) {
+
+ // Resume paused threads
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+
+ // Stop
+ stop = true;
+
+ // Wait until stop block reached
+ while (!stopped) doWait();
+ }
+ }
+
+
+ public void stopLater() {
+ synchronized (this) {
+
+ // Resume paused threads
+ if (paused) {
+ paused = false;
+ notifyAll();
+ }
+
+ while (pause) doWait();
+
+ // Stop
+ stop = true;
+ }
+ }
+
+
+ public boolean isStopped() {
+ synchronized (this) {
+ return stopped;
+ }
+ }
+
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+
+ private void doWait() {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+}
+
+
--- /dev/null
+package ie.dcu.array;
+
+import java.lang.reflect.Array;
+
+/**
+ * Collection of static methods on arrays.
+ *
+ * @author Kevin McGuinness
+ */
+public class Arrays {
+
+ /**
+ * Clamp the values in the given byte matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * The passed array must be one of the following types:
+ * <ul>
+ * <li><code>byte[]</code></li>
+ * <li><code>short[]</code></li>
+ * <li><code>int[]</code></li>
+ * <li><code>long[]</code></li>
+ * <li><code>float[]</code></li>
+ * <li><code>double[]</code></li>
+ * </ul>
+ *
+ * @param array
+ * An array.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return
+ * The modified input matrix.
+ * @throws UnsupportedOperationException
+ * If the passed array is not one of the supported types.
+ */
+ public static Object clamp(Object array, Number min, Number max)
+ throws UnsupportedOperationException
+ {
+ if (array instanceof byte[]) {
+ return clamp((byte[]) array, min.byteValue(), max.byteValue());
+ } else if (array instanceof short[]) {
+ return clamp((short[]) array, min.shortValue(), max.shortValue());
+ } else if (array instanceof int[]) {
+ return clamp((int[]) array, min.intValue(), max.intValue());
+ } else if (array instanceof long[]) {
+ return clamp((long[]) array, min.longValue(), max.longValue());
+ } else if (array instanceof float[]) {
+ return clamp((float[]) array, min.longValue(), max.longValue());
+ } else if (array instanceof double[]) {
+ return clamp((double[]) array, min.longValue(), max.longValue());
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Clamp the values in the given byte matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of byte values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static byte[] clamp(byte[] values, byte min, byte max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Clamp the values in the given short matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of short values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static short[] clamp(short[] values, short min, short max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Clamp the values in the given int matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of int values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static int[] clamp(int[] values, int min, int max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Clamp the values in the given long matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of long values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static long[] clamp(long[] values, long min, long max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Clamp the values in the given float matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of float values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static float[] clamp(float[] values, float min, float max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Clamp the values in the given double matrix to the given range.
+ *
+ * The values array is modified (if necessary).
+ *
+ * @param values
+ * A matrix of double values.
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return The modified input matrix.
+ */
+ public static double[] clamp(double[] values, double min, double max) {
+ if (min > max) {
+ throw new IllegalArgumentException("min > max");
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] < min) {
+ values[i] = min;
+ }
+
+ if (values[i] > max) {
+ values[i] = max;
+ }
+ }
+
+ return values;
+ }
+
+ /**
+ * Filter the values in the given array.
+ *
+ * @param array
+ * An array.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T[] filter(T[] array, Filter filter) {
+ T[] buffer = (T[]) Array.newInstance(
+ array.getClass().getComponentType(), array.length);
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+
+ /**
+ * Filter the values in the given byte array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of bytes.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static byte[] filter(byte[] array, Filter.Byte filter) {
+ byte[] buffer = new byte[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given byte array.
+ *
+ * @param array
+ * An array of bytes.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static byte[] filter(byte[] array, Filter filter) {
+ byte[] buffer = new byte[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given short array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of shorts.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static short[] filter(short[] array, Filter.Short filter) {
+ short[] buffer = new short[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given short array.
+ *
+ * @param array
+ * An array of shorts.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static short[] filter(short[] array, Filter filter) {
+ short[] buffer = new short[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given int array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of ints.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static int[] filter(int[] array, Filter.Integer filter) {
+ int[] buffer = new int[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given int array.
+ *
+ * @param array
+ * An array of ints.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static int[] filter(int[] array, Filter filter) {
+ int[] buffer = new int[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given long array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of longs.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static long[] filter(long[] array, Filter.Long filter) {
+ long[] buffer = new long[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given long array.
+ *
+ * @param array
+ * An array of longs.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static long[] filter(long[] array, Filter filter) {
+ long[] buffer = new long[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given float array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of floats.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static float[] filter(float[] array, Filter.Float filter) {
+ float[] buffer = new float[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given float array.
+ *
+ * @param array
+ * An array of floats.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static float[] filter(float[] array, Filter filter) {
+ float[] buffer = new float[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given double array.
+ *
+ * This method is potentially more efficient than the generic Filter
+ * version, since it must box each value in the array.
+ *
+ * @param array
+ * An array of doubles.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static double[] filter(double[] array, Filter.Double filter) {
+ double[] buffer = new double[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Filter the values in the given double array.
+ *
+ * @param array
+ * An array of doubles.
+ * @param filter
+ * A filter.
+ * @return
+ * The filtered array.
+ */
+ public static double[] filter(double[] array, Filter filter) {
+ double[] buffer = new double[array.length];
+
+ int count = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (filter.retain(array[i])) {
+ buffer[count++] = array[i];
+ }
+ }
+
+ return subarray(buffer, 0, count);
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of bytes.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T[] subarray(T[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ T[] result = (T[]) Array.newInstance(
+ array.getClass().getComponentType(), count);
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of bytes.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static byte[] subarray(byte[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ byte[] result = new byte[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of shorts.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static short[] subarray(short[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ short[] result = new short[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of ints.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static int[] subarray(int[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ int[] result = new int[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of longs.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static long[] subarray(long[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ long[] result = new long[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of floats.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static float[] subarray(float[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ float[] result = new float[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Returns a sub-array of the given array.
+ *
+ * @param array
+ * The array of doubles.
+ * @param startPos
+ * The start position.
+ * @param count
+ * The number of elements.
+ * @return
+ * The sub-array.
+ * @throws IndexOutOfBoundsException
+ * If the operation would result in an index out of bounds
+ * error.
+ */
+ public static double[] subarray(double[] array, int startPos, int count) {
+ checkSubarray(array.length, startPos, count);
+ double[] result = new double[count];
+ System.arraycopy(array, 0, result, 0, count);
+ return result;
+ }
+
+ /**
+ * Check if the subarray operation will result in an
+ * {@link IndexOutOfBoundsException}
+ */
+ private static void checkSubarray(int length, int startPos, int count) {
+ if (startPos < 0) {
+ throw new IndexOutOfBoundsException("startPos < 0");
+ }
+
+ if (count < 0) {
+ throw new IndexOutOfBoundsException("count < 0");
+ }
+
+ if (startPos + count > length) {
+ throw new IndexOutOfBoundsException("startPos + count > array.length");
+ }
+ }
+
+ /**
+ * Perform a reduction on the given array of bytes.
+ *
+ * @param array
+ * An array.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static <T, U> U reduce(T[] array, U initialValue,
+ Reduction<T,U> operation)
+ {
+ U accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of bytes.
+ *
+ * @param array
+ * An array of bytes.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static long reduce(byte[] array, long initialValue,
+ Reduction.Long operation)
+ {
+ long accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of shorts.
+ *
+ * @param array
+ * An array of shorts.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static long reduce(short[] array, long initialValue,
+ Reduction.Long operation)
+ {
+ long accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of ints.
+ *
+ * @param array
+ * An array of ints.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static long reduce(int[] array, long initialValue,
+ Reduction.Long operation)
+ {
+ long accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of longs.
+ *
+ * @param array
+ * An array of longs.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static long reduce(long[] array, long initialValue,
+ Reduction.Long operation)
+ {
+ long accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of floats.
+ *
+ * @param array
+ * An array of floats.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static double reduce(float[] array, double initialValue,
+ Reduction.Double operation)
+ {
+ double accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Perform a reduction on the given array of doubles.
+ *
+ * @param array
+ * An array of doubles.
+ * @param initialValue
+ * The initial value for the reduction.
+ * @param operation
+ * The reduction operation.
+ * @return
+ * The result of the reduction.
+ */
+ public static double reduce(double[] array, double initialValue,
+ Reduction.Double operation)
+ {
+ double accumulator = initialValue;
+ for (int i = 0; i < array.length; i++) {
+ accumulator = operation.reduce(accumulator, array[i], i);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Finds the sum of all elements in the given array.
+ *
+ * The passed array must be one of the following types:
+ * <ul>
+ * <li><code>byte[]</code></li>
+ * <li><code>short[]</code></li>
+ * <li><code>int[]</code></li>
+ * <li><code>long[]</code></li>
+ * <li><code>float[]</code></li>
+ * <li><code>double[]</code></li>
+ * </ul>
+ *
+ * @param array
+ * An array.
+ * @return
+ * The sum of all elements in the given array as a Number.
+ * @throws UnsupportedOperationException
+ * If the passed array is not one of the supported types.
+ */
+ public static Number sum(Object array)
+ throws UnsupportedOperationException
+ {
+ if (array instanceof byte[]) {
+ return sum((byte[]) array);
+ } else if (array instanceof short[]) {
+ return sum((short[]) array);
+ } else if (array instanceof int[]) {
+ return sum((int[]) array);
+ } else if (array instanceof long[]) {
+ return sum((long[]) array);
+ } else if (array instanceof float[]) {
+ return sum((float[]) array);
+ } else if (array instanceof double[]) {
+ return sum((double[]) array);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Computes the sum of all the elements in the given byte array.
+ *
+ * @param array
+ * An array of bytes.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static long sum(byte[] array) {
+ long sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Computes the sum of all the elements in the given short array.
+ *
+ * @param array
+ * An array of shorts.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static long sum(short[] array) {
+ long sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Computes the sum of all the elements in the given int array.
+ *
+ * @param array
+ * An array of ints.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static long sum(int[] array) {
+ long sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Computes the sum of all the elements in the given long array.
+ *
+ * @param array
+ * An array of longs.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static long sum(long[] array) {
+ long sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Computes the sum of all the elements in the given float array.
+ *
+ * @param array
+ * An array of floats.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static double sum(float[] array) {
+ double sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Computes the sum of all the elements in the given double array.
+ *
+ * @param array
+ * An array of doubles.
+ * @return
+ * The sum of all elements in the array.
+ */
+ public static double sum(double[] array) {
+ double sumValue = 0;
+ for (int i = 0; i < array.length; i++) {
+ sumValue += array[i];
+ }
+ return sumValue;
+ }
+
+ /**
+ * Finds the minimum number in the given array.
+ *
+ * The passed array must be one of the following types:
+ * <ul>
+ * <li><code>byte[]</code></li>
+ * <li><code>short[]</code></li>
+ * <li><code>int[]</code></li>
+ * <li><code>long[]</code></li>
+ * <li><code>float[]</code></li>
+ * <li><code>double[]</code></li>
+ * </ul>
+ *
+ * @param array
+ * An array.
+ * @return The smallest number, or <code>null</code> if the length of the
+ * array is zero.
+ * @throws UnsupportedOperationException
+ * If the passed array is not one of the supported types.
+ */
+ public static Number min(Object array)
+ throws UnsupportedOperationException
+ {
+ if (array instanceof byte[]) {
+ return min((byte[]) array);
+ } else if (array instanceof short[]) {
+ return min((short[]) array);
+ } else if (array instanceof int[]) {
+ return min((int[]) array);
+ } else if (array instanceof long[]) {
+ return min((long[]) array);
+ } else if (array instanceof float[]) {
+ return min((float[]) array);
+ } else if (array instanceof double[]) {
+ return min((double[]) array);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Finds the maximum number in the given array.
+ *
+ * The passed array must be one of the following types:
+ * <ul>
+ * <li><code>byte[]</code></li>
+ * <li><code>short[]</code></li>
+ * <li><code>int[]</code></li>
+ * <li><code>long[]</code></li>
+ * <li><code>float[]</code></li>
+ * <li><code>double[]</code></li>
+ * </ul>
+ *
+ * @param array
+ * An array.
+ * @return The largest number, or <code>null</code> if the length of the
+ * array is zero.
+ * @throws UnsupportedOperationException
+ * If the passed array is not one of the supported types.
+ */
+ public static Number max(Object array)
+ throws UnsupportedOperationException
+ {
+ if (array instanceof byte[]) {
+ return max((byte[]) array);
+ } else if (array instanceof short[]) {
+ return max((short[]) array);
+ } else if (array instanceof int[]) {
+ return max((int[]) array);
+ } else if (array instanceof long[]) {
+ return max((long[]) array);
+ } else if (array instanceof float[]) {
+ return max((float[]) array);
+ } else if (array instanceof double[]) {
+ return max((double[]) array);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Returns the smallest element in the given byte array.
+ *
+ * @param array
+ * An array of bytes.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Byte min(byte[] array) {
+ if (array.length > 0) {
+ byte minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given byte array.
+ *
+ * @param array
+ * An array of bytes.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Byte max(byte[] array) {
+ if (array.length > 0) {
+ byte maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the smallest element in the given short array.
+ *
+ * @param array
+ * An array of shorts.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Short min(short[] array) {
+ if (array.length > 0) {
+ short minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given short array.
+ *
+ * @param array
+ * An array of shorts.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Short max(short[] array) {
+ if (array.length > 0) {
+ short maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the smallest element in the given int array.
+ *
+ * @param array
+ * An array of ints.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Integer min(int[] array) {
+ if (array.length > 0) {
+ int minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given int array.
+ *
+ * @param array
+ * An array of ints.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Integer max(int[] array) {
+ if (array.length > 0) {
+ int maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the smallest element in the given long array.
+ *
+ * @param array
+ * An array of longs.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Long min(long[] array) {
+ if (array.length > 0) {
+ long minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given long array.
+ *
+ * @param array
+ * An array of longs.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Long max(long[] array) {
+ if (array.length > 0) {
+ long maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the smallest element in the given float array.
+ *
+ * @param array
+ * An array of floats.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Float min(float[] array) {
+ if (array.length > 0) {
+ float minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given float array.
+ *
+ * @param array
+ * An array of floats.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Float max(float[] array) {
+ if (array.length > 0) {
+ float maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the smallest element in the given double array.
+ *
+ * @param array
+ * An array of doubles.
+ * @return The smallest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Double min(double[] array) {
+ if (array.length > 0) {
+ double minimum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] < minimum) {
+ minimum = array[i];
+ }
+ }
+ return minimum;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the largest element in the given double array.
+ *
+ * @param array
+ * An array of doubles.
+ * @return The largest element, or <code>null</code> if the length of the
+ * array is zero.
+ */
+ public static Double max(double[] array) {
+ if (array.length > 0) {
+ double maximum = array[0];
+ for (int i = 1; i < array.length; i++) {
+ if (array[i] > maximum) {
+ maximum = array[i];
+ }
+ }
+ return maximum;
+ }
+ return null;
+ }
+
+ /**
+ * Copies an array from the specified source array, beginning at the
+ * specified position, to the specified position of the destination array.
+ *
+ * This method uses {@link System#arraycopy(Object, int, Object, int, int)}
+ * if the source and destination array are of the same type, otherwise
+ * it sees if there is a compatible static copy method in this class
+ * that can perform the conversion copy. If one cannot be found, an
+ * {@link ArrayStoreException} is thrown.
+ *
+ * If the positions or lengths specified are out of range then an
+ * {@link IndexOutOfBoundsException} is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws ArrayStoreException
+ * if an element in the source array could not be stored into the
+ * destination array because of a type mismatch, and there is no
+ * primitive copy conversion routine to handle the conversion.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(Object src, Object dst) {
+ copy(src, 0, dst, 0, Array.getLength(dst));
+ }
+
+ /**
+ * Copies an array from the specified source array, beginning at the
+ * specified position, to the specified position of the destination array.
+ *
+ * @see #copy(Object, int, Object, int, int)
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws ArrayStoreException
+ * if an element in the source array could not be stored into the
+ * destination array because of a type mismatch, and there is no
+ * primitive copy conversion routine to handle the conversion.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(Object src, int srcPos,
+ Object dst, int dstPos, int length)
+ {
+ if (dst.getClass().equals(src.getClass())) {
+
+ // Use the fast system copy
+ System.arraycopy(src, srcPos, dst, dstPos, length);
+
+ } else {
+ // Try for arrays of different primitive types
+
+ if (src instanceof byte[] && dst instanceof short[]) {
+
+ // Copy from an array of bytes to array of shorts
+ copy((byte[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof int[]) {
+
+ // Copy from an array of bytes to array of ints
+ copy((byte[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof long[]) {
+
+ // Copy from an array of bytes to array of longs
+ copy((byte[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof float[]) {
+
+ // Copy from an array of bytes to array of floats
+ copy((byte[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof double[]) {
+
+ // Copy from an array of bytes to array of doubles
+ copy((byte[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof byte[]) {
+
+ // Copy from an array of shorts to array of bytes
+ copy((short[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof int[]) {
+
+ // Copy from an array of shorts to array of ints
+ copy((short[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof long[]) {
+
+ // Copy from an array of shorts to array of longs
+ copy((short[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof float[]) {
+
+ // Copy from an array of shorts to array of floats
+ copy((short[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof double[]) {
+
+ // Copy from an array of shorts to array of doubles
+ copy((short[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof byte[]) {
+
+ // Copy from an array of ints to array of bytes
+ copy((int[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof short[]) {
+
+ // Copy from an array of ints to array of shorts
+ copy((int[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof long[]) {
+
+ // Copy from an array of ints to array of longs
+ copy((int[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof float[]) {
+
+ // Copy from an array of ints to array of floats
+ copy((int[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof double[]) {
+
+ // Copy from an array of ints to array of doubles
+ copy((int[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof byte[]) {
+
+ // Copy from an array of longs to array of bytes
+ copy((long[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof short[]) {
+
+ // Copy from an array of longs to array of shorts
+ copy((long[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof int[]) {
+
+ // Copy from an array of longs to array of ints
+ copy((long[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof float[]) {
+
+ // Copy from an array of longs to array of floats
+ copy((long[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof double[]) {
+
+ // Copy from an array of longs to array of doubles
+ copy((long[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof byte[]) {
+
+ // Copy from an array of floats to array of bytes
+ copy((float[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof short[]) {
+
+ // Copy from an array of floats to array of shorts
+ copy((float[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof int[]) {
+
+ // Copy from an array of floats to array of ints
+ copy((float[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof long[]) {
+
+ // Copy from an array of floats to array of longs
+ copy((float[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof double[]) {
+
+ // Copy from an array of floats to array of doubles
+ copy((float[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof byte[]) {
+
+ // Copy from an array of doubles to array of bytes
+ copy((double[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof short[]) {
+
+ // Copy from an array of doubles to array of shorts
+ copy((double[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof int[]) {
+
+ // Copy from an array of doubles to array of ints
+ copy((double[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof long[]) {
+
+ // Copy from an array of doubles to array of longs
+ copy((double[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof float[]) {
+
+ // Copy from an array of doubles to array of floats
+ copy((double[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else {
+
+ throw new ArrayStoreException();
+ }
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, short[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of bytes to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of bytes to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, long[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of bytes to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, float[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of bytes to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of bytes to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(byte[] src, double[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of shorts to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of shorts to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, byte[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of shorts to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of shorts to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of shorts to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of shorts to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, long[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of shorts to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of shorts to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, float[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of shorts to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of shorts to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(short[] src, double[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of ints to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of ints to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, byte[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of ints to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of ints to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, short[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of ints to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of ints to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, long[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of ints to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of ints to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, float[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of ints to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of ints to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(int[] src, double[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of longs to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of longs to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, byte[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of longs to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of longs to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, short[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of longs to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of longs to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of longs to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of longs to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, float[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of longs to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of longs to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(long[] src, double[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of floats to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of floats to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, byte[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of floats to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of floats to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, short[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of floats to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of floats to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of floats to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of floats to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, long[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of floats to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of floats to array of doubles.
+ *
+ * The values will be cast to double.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(float[] src, double[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of doubles to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of doubles to array of bytes.
+ *
+ * The values will be cast to byte.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, byte[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of doubles to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of doubles to array of shorts.
+ *
+ * The values will be cast to short.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, short[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of doubles to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of doubles to array of ints.
+ *
+ * The values will be cast to int.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of doubles to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of doubles to array of longs.
+ *
+ * The values will be cast to long.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, long[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Copy from an array of doubles to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * If the positions or lengths specified are out of range then an
+ * IndexOutOfBoundsException is thrown and the destination is not
+ * modified.
+ *
+ * @param src
+ * the source array.
+ * @param srcPos
+ * starting position in the source array.
+ * @param dst
+ * the destination array.
+ * @param dstPos
+ * starting position in the destination data.
+ * @param length
+ * the number of array elements to be copied.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ /**
+ * Copy from an array of doubles to array of floats.
+ *
+ * The values will be cast to float.
+ *
+ * @param src
+ * the source array.
+ * @param dst
+ * the destination array.
+ * @throws IndexOutOfBoundsException
+ * if copying would cause access of data outside array bounds.
+ * @throws NullPointerException
+ * if either <code>src</code> or <code>dest</code> is
+ * <code>null</code>.
+ */
+ public static void copy(double[] src, float[] dst) {
+ copy(src, 0, dst, 0, dst.length);
+ }
+
+ /**
+ * Throws an {@link IndexOutOfBoundsException} if the arguments
+ * would result in an array copy that would go out of bounds.
+ */
+ private static void checkCopy(int srcLen, int srcPos,
+ int dstLen, int dstPos, int length)
+ {
+ if (srcPos < 0) {
+ throw new IndexOutOfBoundsException("srcPos < 0");
+ }
+
+ if (dstPos < 0) {
+ throw new IndexOutOfBoundsException("dstPos < 0");
+ }
+
+ if (length < 0) {
+ throw new IndexOutOfBoundsException("length < 0");
+ }
+
+ if (srcPos + length > srcLen) {
+ throw new IndexOutOfBoundsException("srcPos + length > src.length");
+ }
+
+ if (dstPos + length > dstLen) {
+ throw new IndexOutOfBoundsException("dstPos + length > dst.length");
+ }
+ }
+}
--- /dev/null
+package ie.dcu.array;
+
+/**
+ * Interfaces for filters
+ *
+ * @author Kevin McGuinness
+ */
+public interface Filter {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param object
+ * An object.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(Object object);
+
+
+ /**
+ * An byte filter.
+ */
+ public interface Byte {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An byte value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(byte value);
+ }
+
+ /**
+ * An short filter.
+ */
+ public interface Short {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An short value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(short value);
+ }
+
+ /**
+ * An int filter.
+ */
+ public interface Integer {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An int value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(int value);
+ }
+
+ /**
+ * An long filter.
+ */
+ public interface Long {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An long value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(long value);
+ }
+
+ /**
+ * An float filter.
+ */
+ public interface Float {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An float value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(float value);
+ }
+
+ /**
+ * An double filter.
+ */
+ public interface Double {
+
+ /**
+ * Returns <code>true</code> if the given value should be retained.
+ *
+ * @param value
+ * An double value.
+ * @return
+ * <code>true</code> if the value should be retained.
+ */
+ public boolean retain(double value);
+ }
+}
--- /dev/null
+package ie.dcu.array;
+
+/**
+ * A reduce operation.
+ *
+ * @author Kevin McGuinness
+ */
+public interface Reduction<T, U> {
+
+ /**
+ * Perform a reduction.
+ *
+ * @param accumulator
+ * The accumulated value so far.
+ * @param value
+ * The next value in the array.
+ * @param index
+ * The index of the value in the array.
+ * @return
+ * The result of the reduction.
+ */
+ public U reduce(U accumulator, T value, int index);
+
+
+ /**
+ * A long width reduction.
+ */
+ public interface Long {
+
+ /**
+ * Perform a reduction.
+ *
+ * @param accumulator
+ * The accumulated value so far.
+ * @param value
+ * The next value in the array.
+ * @param index
+ * The index of the value in the array.
+ * @return
+ * The result of the reduction.
+ */
+ public long reduce(long accumulator, long value, int index);
+ }
+
+ /**
+ * A double precision reduction.
+ */
+ public interface Double {
+
+ /**
+ * Perform a reduction.
+ *
+ * @param accumulator
+ * The accumulated value so far.
+ * @param value
+ * The next value in the array.
+ * @param index
+ * The index of the value in the array.
+ * @return
+ * The result of the reduction.
+ */
+ public long reduce(double accumulator, double value, int index);
+ }
+}
--- /dev/null
+package ie.dcu.auto;
+
+import ie.dcu.auto.automator.*;
+import ie.dcu.eval.*;
+import ie.dcu.matrix.ByteMatrix;
+import ie.dcu.segment.*;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Container class for data needed for the automation.
+ *
+ * @author Kevin McGuinness
+ */
+public class AutomationData {
+
+ public static interface Listener {
+ public void segmentationContextChanged(AutomationData data);
+ public void segmentationContextUpdated(AutomationData automationData);
+ public void groundTruthMaskChanged(AutomationData data);
+ public void foregroundErrorChanged(AutomationData data);
+ public void backgroundErrorChanged(AutomationData data);
+ public void segmenterChanged(AutomationData data);
+ public void evaluationMeasuresChanged(AutomationData data);
+ public void evaluationResultsAdded(AutomationData data);
+ public void evaluationResultsCleared(AutomationData data);
+ public void automatorStateChanged(AutomationData data);
+ public void logUpdated(AutomationData data, String message);
+
+ }
+
+ private SegmentationContext segmentationContext;
+ private SegmentationMask groundTruthMask;
+ private ByteMatrix foregroundError;
+ private ByteMatrix backgroundError;
+ private EvaluationResults evaluationResults;
+ private List<Evaluator> evaluators;
+ private Segmenter segmenter;
+ private Automator automator;
+ private List<Listener> listeners;
+
+ public AutomationData() {
+ evaluators = new LinkedList<Evaluator>();
+ listeners = new LinkedList<Listener>();
+ evaluationResults = new EvaluationResults();
+ automator = new DeterministicAutomator(this);
+ }
+
+ public void setAutomator(Automator automator) {
+ if (automator == null) {
+ throw new IllegalArgumentException();
+ }
+
+ // Reset old automator
+ this.automator.reset();
+
+ // Set new automator
+ this.automator = automator;
+
+ // Reset new automator
+ this.automator.reset();
+ }
+
+ public void dispose() {
+ listeners.clear();
+ evaluators.clear();
+ setSegmentationContext(null);
+ }
+
+ public void addDefaultEvaluators() {
+ addEvaluator(new BestEvaluator());
+ }
+
+ public void addEvaluator(Evaluator e) {
+ evaluators.add(e);
+ updateEvaluationMeasures();
+ }
+
+ public void removeEvaluator(Evaluator e) {
+ evaluators.remove(e);
+ updateEvaluationMeasures();
+ }
+
+ public List<Evaluator> getEvaluators() {
+ return Collections.unmodifiableList(evaluators);
+ }
+
+ public SegmentationContext getSegmentationContext() {
+ return segmentationContext;
+ }
+
+ public void setSegmentationContext(SegmentationContext context) {
+ if (segmentationContext != context) {
+
+ // Tidy up
+ if (segmentationContext != null) {
+
+ if (segmenter != null) {
+ segmenter.finish(segmentationContext);
+ }
+
+ if (!segmentationContext.isDisposed()) {
+ segmentationContext.dispose();
+ }
+
+ segmentationContext = null;
+ }
+
+ // Set
+ this.segmentationContext = context;
+ fireSegmentationContextChanged();
+ }
+ }
+
+ public void setSegmenter(Segmenter segmenter) {
+ if (this.segmenter != segmenter) {
+
+ // Tidy up
+ if (this.segmenter != null) {
+ if (segmentationContext != null) {
+ this.segmenter.finish(segmentationContext);
+ }
+ this.segmenter = null;
+ }
+
+ this.segmenter = segmenter;
+ this.automator.reset();
+ fireSegmenterChanged();
+ }
+ }
+
+ public Segmenter getSegmenter() {
+ return segmenter;
+ }
+
+ public SegmentationMask getGroundTruthMask() {
+ return groundTruthMask;
+ }
+
+ public void setGroundTruthMask(SegmentationMask mask) {
+ if (this.groundTruthMask != mask) {
+ this.groundTruthMask = mask;
+ fireGroundTruthMaskChanged();
+ }
+ }
+
+ public ByteMatrix getForegroundError() {
+ return foregroundError;
+ }
+
+ public void setForegroundError(ByteMatrix error) {
+ if (this.foregroundError != error) {
+ this.foregroundError = error;
+ fireForegroundErrorChanged();
+ }
+ }
+
+ public ByteMatrix getBackgroundError() {
+ return backgroundError;
+ }
+
+ public void setBackgroundError(ByteMatrix error) {
+ if (this.backgroundError != error) {
+ this.backgroundError = error;
+ fireBackgroundErrorChanged();
+ }
+ }
+
+ public void clearEvaluationResults() {
+ if (evaluationResults.getResultCount() > 0) {
+ evaluationResults.clearResults();
+ fireEvaluationResultsCleared();
+ }
+ }
+
+ private void updateEvaluationMeasures() {
+ List<String> headings = new ArrayList<String>();
+ for (Evaluator e : evaluators) {
+ String[] measures = e.getMeasures();
+ for (String m : measures) {
+ headings.add(m);
+ }
+ }
+
+ String[] measures = headings.toArray(new String[headings.size()]);
+ setEvaluationMeasures(measures);
+ }
+
+ public void evaluate() {
+ SegmentationMask mask = segmentationContext.getMask();
+
+ double[] values = new double[evaluationResults.getMeasureCount()];
+ for (Evaluator e : evaluators) {
+ e.run(mask, groundTruthMask);
+
+ String[] measures = e.getMeasures();
+ for (String m : measures) {
+ int idx = evaluationResults.indexOfMeasure(m);
+ values[idx] = e.getMeasure(m);
+ }
+ }
+
+ addEvaluationResult(values);
+ }
+
+ private void addEvaluationResult(double[] result) {
+ evaluationResults.addResults(result);
+ fireEvaluationResultAdded();
+ }
+
+ private void setEvaluationMeasures(String[] measures) {
+ clearEvaluationResults();
+ evaluationResults.setMeasures(measures);
+ fireEvaluationMeasuresChanged();
+ }
+
+ public EvaluationResults getEvaluationResults() {
+ return evaluationResults;
+ }
+
+ public Automator getAutomator() {
+ return automator;
+ }
+
+ public void load(AutomationInput input) throws IOException {
+ SegmentationContext ctx = SegmentationContext.create(input.imageFile);
+ SegmentationMask mask = SegmentationMask.read(input.groundTruthFile);
+
+ if (ctx.getBounds().equals(mask.getBounds())) {
+
+ clearEvaluationResults();
+ setSegmentationContext(ctx);
+ setGroundTruthMask(mask);
+ automator.reset();
+
+
+ } else {
+ ctx.dispose();
+ throw new IOException("Images are not the same size");
+ }
+ }
+
+ public void updateSegmentationError() {
+
+ SegmentationMask mask = segmentationContext.getMask();
+
+ int rows = groundTruthMask.height;
+ int cols = groundTruthMask.width;
+
+ if (foregroundError == null ||
+ foregroundError.cols != cols ||
+ foregroundError.rows != rows)
+ {
+ foregroundError = new ByteMatrix(rows, cols);
+ }
+
+ if (backgroundError == null ||
+ backgroundError.cols != cols ||
+ backgroundError.rows != rows)
+ {
+ backgroundError = new ByteMatrix(rows, cols);
+ }
+
+ foregroundError.fill(0);
+ backgroundError.fill(0);
+
+ for (int i = 0; i < rows; i++) {
+ int k = i * cols;
+
+ for (int j = 0; j < cols; j++, k++) {
+ int v = groundTruthMask.values[k];
+ if (v != mask.values[k]) {
+ switch (v) {
+ case SegmentationMask.FOREGROUND:
+ foregroundError.values[k] = Constants.ERROR_VALUE;
+ break;
+ case SegmentationMask.BACKGROUND:
+ backgroundError.values[k] = Constants.ERROR_VALUE;
+ break;
+ }
+
+ }
+ }
+ }
+
+ fireForegroundErrorChanged();
+ fireBackgroundErrorChanged();
+ }
+
+ public void logActivity(String message, Object ... args) {
+ String log = String.format(message, args) + "\n";
+ fireLogUpdated(log);
+ }
+
+ private void fireLogUpdated(String message) {
+ for (Listener listener : listeners) {
+ listener.logUpdated(this, message);
+ }
+ }
+
+ private void fireSegmenterChanged() {
+ for (Listener listener : listeners) {
+ listener.segmenterChanged(this);
+ }
+ }
+
+ private void fireForegroundErrorChanged() {
+ for (Listener listener : listeners) {
+ listener.foregroundErrorChanged(this);
+ }
+ }
+
+ private void fireBackgroundErrorChanged() {
+ for (Listener listener : listeners) {
+ listener.backgroundErrorChanged(this);
+ }
+ }
+
+ private void fireSegmentationContextChanged() {
+ for (Listener listener : listeners) {
+ listener.segmentationContextChanged(this);
+ }
+ }
+
+ public void fireSegmentationContextUpdated() {
+ for (Listener listener : listeners) {
+ listener.segmentationContextUpdated(this);
+ }
+ }
+
+ private void fireGroundTruthMaskChanged() {
+ for (Listener listener : listeners) {
+ listener.groundTruthMaskChanged(this);
+ }
+ }
+
+ private void fireEvaluationMeasuresChanged() {
+ for (Listener listener : listeners) {
+ listener.evaluationMeasuresChanged(this);
+ }
+ }
+
+ private void fireEvaluationResultAdded() {
+ for (Listener listener : listeners) {
+ listener.evaluationResultsAdded(this);
+ }
+ }
+
+ private void fireEvaluationResultsCleared() {
+ for (Listener listener : listeners) {
+ listener.evaluationResultsCleared(this);
+ }
+ }
+
+ public void fireAutomatorStateChanged() {
+ for (Listener listener : listeners) {
+ listener.automatorStateChanged(this);
+ }
+ }
+
+ public void addListener(Listener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ listeners.remove(listener);
+ }
+}
--- /dev/null
+package ie.dcu.auto;
+
+/**
+ * Convenience class for listeners who are only interested in some events
+ *
+ * @author Kevin McGuinness
+ */
+public class AutomationDataAdapter implements AutomationData.Listener {
+
+ public void automatorStateChanged(AutomationData data) {
+ }
+
+ public void backgroundErrorChanged(AutomationData data) {
+ }
+
+ public void evaluationMeasuresChanged(AutomationData data) {
+ }
+
+ public void evaluationResultsAdded(AutomationData data) {
+ }
+
+ public void evaluationResultsCleared(AutomationData data) {
+ }
+
+ public void foregroundErrorChanged(AutomationData data) {
+ }
+
+ public void groundTruthMaskChanged(AutomationData data) {
+ }
+
+ public void logUpdated(AutomationData data, String message) {
+ }
+
+ public void segmentationContextChanged(AutomationData data) {
+ }
+
+ public void segmentationContextUpdated(AutomationData automationData) {
+ }
+
+ public void segmenterChanged(AutomationData data) {
+ }
+}
--- /dev/null
+package ie.dcu.auto;
+
+import java.io.File;
+
+/**
+ * Immutable class encapsulating an input for the automation.
+ *
+ * @author Kevin McGuinness
+ */
+public class AutomationInput {
+
+ /**
+ * The input image file.
+ */
+ public final File imageFile;
+
+ /**
+ * The input ground-truth mask file.
+ */
+ public final File groundTruthFile;
+
+ /**
+ * Create an automation input with the given image and ground
+ * truth file.
+ *
+ * @param imageFile
+ * The image file (cannot be <code>null</code>).
+ * @param groundTruthFile
+ * The ground-truth mask file (cannot be <code>null</code>).
+ */
+ public AutomationInput(File imageFile, File groundTruthFile) {
+
+ if (imageFile == null) {
+ throw new IllegalArgumentException("imageFile == null");
+ }
+
+ if (groundTruthFile == null) {
+ throw new IllegalArgumentException("groundTruthFile == null");
+ }
+
+ this.imageFile = imageFile;
+ this.groundTruthFile = groundTruthFile;
+ }
+
+ /**
+ * Returns true if both the image and ground truth files exist.
+ */
+ public boolean filesExist() {
+ return imageFile.isFile() && groundTruthFile.isFile();
+ }
+
+ /**
+ * Returns the image file.
+ */
+ public File getImageFile() {
+ return imageFile;
+ }
+
+ /**
+ * Returns the ground-truth file.
+ */
+ public File getGroundTruthFile() {
+ return groundTruthFile;
+ }
+
+ /**
+ * Returns a deep copy of the object.
+ */
+ @Override
+ protected AutomationInput clone() {
+ return new AutomationInput(imageFile, groundTruthFile);
+ }
+
+ /**
+ * Compare for equality.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AutomationInput) {
+ AutomationInput x = (AutomationInput) obj;
+ return x.imageFile.equals(imageFile) &&
+ x.groundTruthFile.equals(groundTruthFile);
+ }
+ return false;
+ }
+
+ /**
+ * Hash code override.
+ */
+ @Override
+ public int hashCode() {
+ return imageFile.hashCode() + groundTruthFile.hashCode();
+ }
+
+ /**
+ * Returns a string representation
+ */
+ @Override
+ public String toString() {
+ return imageFile.getName() + "," + groundTruthFile.getName();
+ }
+}
--- /dev/null
+package ie.dcu.auto;
+
+public interface Constants {
+ final byte ERROR_VALUE = (byte) 0xff;
+}
--- /dev/null
+package ie.dcu.auto;
+
+import java.util.*;
+
+public class EvaluationResults {
+
+ private static class Item {
+ final double[] values;
+
+ public Item(double[] values) {
+ this.values = values.clone();
+ }
+ };
+
+ private String[] measures;
+ private final List<Item> results;
+
+ public EvaluationResults() {
+ this.measures = new String[0];
+ this.results = new ArrayList<Item>();
+ }
+
+ void setMeasures(String[] measures) {
+ if (this.measures != measures) {
+ clearResults();
+ this.measures = measures.clone();
+ }
+ }
+
+ public String[] getMeasures() {
+ return measures;
+ }
+
+ public int getMeasureCount() {
+ return measures.length;
+ }
+
+ public int indexOfMeasure(String measure) {
+ for (int i = 0; i < measures.length; i++) {
+ if (measures[i].equals(measure)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ void clearResults() {
+ results.clear();
+ }
+
+ void addResults(double[] results) {
+ if (results.length != measures.length) {
+ throw new IllegalArgumentException();
+ }
+
+ this.results.add(new Item(results));
+ }
+
+ public double[] getResults(int idx) {
+ return results.get(idx).values;
+ }
+
+ public int getResultCount() {
+ return results.size();
+ }
+}
--- /dev/null
+package ie.dcu.auto;
+
+import ie.dcu.matrix.*;
+
+import java.util.*;
+
+public class MaximaLocator {
+
+ private final IntMatrix matrix;
+ private int maximum;
+
+ public MaximaLocator(IntMatrix matrix) {
+ this.matrix = matrix;
+ this.maximum = Integer.MIN_VALUE;
+ }
+
+ public int getMaximum() {
+ return maximum;
+ }
+
+ public List<Index2D> findAll(int min) {
+ maximum = matrix.maxValue();
+
+ if (maximum < min) {
+ return Collections.emptyList();
+ }
+
+ List<Index2D> indices = new ArrayList<Index2D>();
+
+ for (int i = 0; i < matrix.rows; i++) {
+ int k = i * matrix.cols;
+
+ for (int j = 0; j < matrix.cols; j++, k++) {
+ if (matrix.values[k] == maximum) {
+ indices.add(new Index2D(i, j));
+ }
+ }
+ }
+
+ return indices;
+ }
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+import ie.dcu.auto.AutomationData;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.AnnotationManager;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Handles the grunt work of the automator (maintaining state, sending
+ * notifications, and so forth).
+ *
+ * Subclasses just need to implement {@link #doReset()}, {@link #doStart()}, and
+ * {@link #doStep()}.
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractAutomator implements Automator {
+ private final AutomationData data;
+ private boolean done;
+ private int maxSteps;
+ private int step;
+
+ public AbstractAutomator(AutomationData data) {
+ this.data = data;
+ this.done = false;
+ this.maxSteps = DEFAULT_MAX_STEPS;
+ this.step = -1;
+ }
+
+ public void setMaxSteps(int maxSteps) {
+ if (maxSteps < 0) {
+ throw new IllegalArgumentException();
+ }
+ this.maxSteps = maxSteps;
+ }
+
+ public int getMaxSteps() {
+ return maxSteps;
+ }
+
+ public int getStep() {
+ return step;
+ }
+
+ public boolean canReset() {
+ return hasSegmentationContext();
+ }
+
+ public boolean canStart() {
+ return step == 0 && hasSegmentationContext();
+ }
+
+ public boolean canStep() {
+ return step > 0 && !done && hasSegmentationContext() && step < maxSteps;
+ }
+
+ public void reset() {
+ if (canReset()) {
+ doReset();
+ getSegmenter().finish(getSegmentationContext());
+ getAnnotations().clear();
+ getSegmentationContext().getMask().clear();
+ getSegmenter().init(getSegmentationContext());
+ step = 0;
+ done = false;
+ data.clearEvaluationResults();
+ data.updateSegmentationError();
+ data.fireAutomatorStateChanged();
+ data.fireSegmentationContextUpdated();
+ }
+ }
+
+ public void start() {
+ if (canStart()) {
+ doStart();
+
+ // Update segmentation
+ getSegmenter().update(getSegmentationContext());
+
+ // Evaluate
+ data.evaluate();
+
+ // Set step
+ step = 1;
+
+ // Notify
+ data.updateSegmentationError();
+ data.fireAutomatorStateChanged();
+ data.fireSegmentationContextUpdated();
+ }
+ }
+
+ public void step() {
+ if (canStep()) {
+ doStep();
+
+ // Update segmentation
+ getSegmenter().update(getSegmentationContext());
+
+ // Evaluate
+ data.evaluate();
+
+ // Increment step
+ step++;
+
+ if (done || step == maxSteps) {
+ getSegmenter().finish(getSegmentationContext());
+ }
+
+ // Notify
+ data.updateSegmentationError();
+ data.fireAutomatorStateChanged();
+ data.fireSegmentationContextUpdated();
+ }
+ }
+
+ private boolean hasSegmentationContext() {
+ return data.getSegmentationContext() != null;
+ }
+
+ private Segmenter getSegmenter() {
+ return data.getSegmenter();
+ }
+
+ private SegmentationContext getSegmentationContext() {
+ return data.getSegmentationContext();
+ }
+
+ protected AnnotationManager getAnnotations() {
+ return data.getSegmentationContext().getAnnotations();
+ }
+
+ protected ByteMatrix getForegroundError() {
+ return data.getForegroundError();
+ }
+
+ protected ByteMatrix getBackgroundError() {
+ return data.getBackgroundError();
+ }
+
+ protected void log(String format, Object... args) {
+ data.logActivity(String.format(format, args));
+ }
+
+ protected void setDone(boolean done) {
+ this.done = done;
+ }
+
+ protected static Point getSwtPointForIndex(Index2D index) {
+ return new Point(index.j, index.i);
+ }
+
+ protected abstract void doReset();
+
+ protected abstract void doStart();
+
+ protected abstract void doStep();
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+/**
+ * Automates the evaluation of interactive segmentation algorithms
+ *
+ * @author Kevin McGuinness
+ */
+public interface Automator {
+
+ /**
+ * The maximum brush size that an automator may use.
+ */
+ public static final int MAX_BRUSH_SIZE = 20;
+
+ /**
+ * The default maximum number of steps to use.
+ */
+ public static final int DEFAULT_MAX_STEPS = 200;
+
+ /**
+ * Returns the current step.
+ */
+ public int getStep();
+
+ /**
+ * Returns the max number of steps the automator will take.
+ */
+ public int getMaxSteps();
+
+ /**
+ * Set the max number of steps the automator will take.
+ */
+ public void setMaxSteps(int maxSteps);
+
+ /**
+ * Returns true if the automator can currently be reset.
+ */
+ public boolean canReset();
+
+ /**
+ * Reset the automator.
+ */
+ public void reset();
+
+ /**
+ * Returns true if the automator can currently be started.
+ */
+ public boolean canStart();
+
+ /**
+ * Start the automator.
+ */
+ public void start();
+
+ /**
+ * Returns true if a step in the automation can be taken.
+ */
+ public boolean canStep();
+
+ /**
+ * Take the next step in the automation procedure.
+ */
+ public void step();
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.auto.automator;
+
+import ie.dcu.auto.AutomationData;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * Creates automator instances.
+ *
+ * @author Kevin McGuinness
+ */
+public class AutomatorFactory {
+
+ public static final String STRATEGY1 = "Strategy 1 (Deterministic)";
+ public static final String STRATEGY2 = "Strategy 2 (Probabilistic)";
+ public static final String STRATEGY3 = "Strategy 3 (Probabilistic)";
+ public static final String STRATEGY4 = "Strategy 4 (Probabilistic)";
+
+ private final Map<String, Class<? extends Automator>> automators;
+ private static AutomatorFactory instance;
+
+ private AutomatorFactory() {
+ automators = new HashMap<String, Class<? extends Automator>>();
+ automators.put(STRATEGY1, DeterministicAutomator.class);
+ automators.put(STRATEGY2, NonDeterministicAutomator1.class);
+ automators.put(STRATEGY3, NonDeterministicAutomator2.class);
+ automators.put(STRATEGY4, NonDeterministicAutomator3.class);
+ }
+
+ public Automator createAutomator(String strategy, AutomationData data) {
+
+ try {
+ Class<? extends Automator> clazz = automators.get(strategy);
+ if (clazz != null) {
+ Constructor<? extends Automator> ctor =
+ clazz.getConstructor(AutomationData.class);
+ return ctor.newInstance(data);
+ } else {
+ throw new IllegalArgumentException(
+ "no such strategy: " + strategy);
+ }
+
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean hasAutomator(String strategy) {
+ return automators.containsKey(strategy);
+ }
+
+ public Set<String> getAvailableStrategies() {
+ return Collections.unmodifiableSet(automators.keySet());
+ }
+
+ public boolean isNonDeterministic(String strategy) {
+ if (hasAutomator(strategy)) {
+ Class<? extends Automator> clazz = automators.get(strategy);
+ try {
+ Method method = clazz.getMethod("isNonDeterministic");
+ return (Boolean) method.invoke(null);
+
+ } catch (SecurityException e) {
+ return false;
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ public static AutomatorFactory getInstance() {
+ if (instance == null) {
+ instance = new AutomatorFactory();
+ }
+ return instance;
+ }
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+import ie.dcu.auto.*;
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.annotate.*;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Automates the evaluation of interactive segmentation algorithms using
+ * a simple deterministic method.
+ *
+ * @author Kevin McGuinness
+ */
+public class DeterministicAutomator extends AbstractAutomator implements Automator {
+ private final Set<Index2D> seeds;
+
+ public DeterministicAutomator(AutomationData data) {
+ super(data);
+ this.seeds = new HashSet<Index2D>();
+ }
+
+ protected void doReset() {
+ seeds.clear();
+ }
+
+ protected void doStart() {
+
+ // Get error
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ // DT
+ IntMatrix fgDT = distanceTransform(fgError);
+ IntMatrix bgDT = distanceTransform(bgError);
+
+ // Maxima
+ MaximaLocator fgLocator = new MaximaLocator(fgDT);
+ List<Index2D> fgMaxima = fgLocator.findAll(2);
+
+ MaximaLocator bgLocator = new MaximaLocator(bgDT);
+ List<Index2D> bgMaxima = bgLocator.findAll(2);
+
+ // Add seeds
+ if (fgMaxima.size() > 0 && bgMaxima.size() > 0) {
+ addForegroundSeeds(fgMaxima, fgLocator.getMaximum());
+ addBackgroundSeeds(bgMaxima, bgLocator.getMaximum());
+ } else if (fgMaxima.size() > 0) {
+ addForegroundSeeds(fgMaxima, fgLocator.getMaximum());
+ } else if (bgMaxima.size() > 0) {
+ addBackgroundSeeds(bgMaxima, bgLocator.getMaximum());
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ protected void doStep() {
+
+ // Get error
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ // DT
+ IntMatrix fgDT = distanceTransform(fgError);
+ IntMatrix bgDT = distanceTransform(bgError);
+
+ // Maxima
+ MaximaLocator fgLocator = new MaximaLocator(fgDT);
+ List<Index2D> fgMaxima = fgLocator.findAll(2);
+
+ MaximaLocator bgLocator = new MaximaLocator(bgDT);
+ List<Index2D> bgMaxima = bgLocator.findAll(2);
+
+ log("Found %d potential foreground seeds", fgMaxima.size());
+ log("Found %d potential background seeds", bgMaxima.size());
+
+ // Remove previously selected seeds
+ removeSelectedSeeds(fgMaxima);
+ removeSelectedSeeds(bgMaxima);
+
+ // Add seeds
+ if (fgMaxima.size() > 0 && bgMaxima.size() > 0) {
+
+ if (fgLocator.getMaximum() >= bgLocator.getMaximum()) {
+ addForegroundSeeds(fgMaxima, fgLocator.getMaximum());
+ } else {
+ addBackgroundSeeds(bgMaxima, bgLocator.getMaximum());
+ }
+
+ } else if (fgMaxima.size() > 0) {
+ addForegroundSeeds(fgMaxima, fgLocator.getMaximum());
+
+ } else if (bgMaxima.size() > 0) {
+ addBackgroundSeeds(bgMaxima, bgLocator.getMaximum());
+
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ private void removeSelectedSeeds(List<Index2D> seeds) {
+ Iterator<Index2D> iterator = seeds.iterator();
+
+ while (iterator.hasNext()) {
+ Index2D seed = iterator.next();
+ if (this.seeds.contains(seed)) {
+ iterator.remove();
+ }
+ }
+ }
+
+ private void addBackgroundSeeds(List<Index2D> seeds, int maximum) {
+ double distance = Math.sqrt(maximum);
+
+ log("Adding %d background seeds (distance: %f)", seeds.size(), distance);
+
+ int size = (int) Math.min(distance/2, MAX_BRUSH_SIZE);
+
+ AnnotationManager annotations = getAnnotations();
+ for (Index2D seed : seeds) {
+ this.seeds.add(seed);
+ Point pt = getSwtPointForIndex(seed);
+ annotations.add(new Annotation(AnnotationType.Background, size, pt));
+ }
+ }
+
+ private void addForegroundSeeds(List<Index2D> seeds, int maximum) {
+ double distance = Math.sqrt(maximum);
+
+ log("Adding %d foreground seeds (distance: %f)", seeds.size(), distance);
+
+ int size = (int) Math.min(distance/2, MAX_BRUSH_SIZE);
+
+ AnnotationManager annotations = getAnnotations();
+ for (Index2D seed : seeds) {
+ this.seeds.add(seed);
+ Point pt = getSwtPointForIndex(seed);
+ annotations.add(new Annotation(AnnotationType.Foreground, size, pt));
+ }
+ }
+
+ private static IntMatrix distanceTransform(ByteMatrix error) {
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(error, Constants.ERROR_VALUE);
+ return dt.computeSquareTransform();
+ }
+
+ public static boolean isNonDeterministic() {
+ return false;
+ }
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+import ie.dcu.auto.*;
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.stats.InversionMethod;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Automates the evaluation of interactive segmentation algorithms using
+ * a simple non-deterministic method.
+ *
+ * @author Kevin McGuinness
+ */
+public class NonDeterministicAutomator1
+ extends AbstractAutomator implements Automator
+{
+ private final Set<Index2D> seeds;
+
+ public NonDeterministicAutomator1(AutomationData data) {
+ super(data);
+ this.seeds = new HashSet<Index2D>();
+ }
+
+ protected void doReset() {
+ seeds.clear();
+ }
+
+ protected void doStart() {
+
+ // Distance transform
+ DoubleMatrix fgDT = distanceTransform(getForegroundError());
+ DoubleMatrix bgDT = distanceTransform(getBackgroundError());
+
+ // Initialize foreground probability field
+ Index2D fgIndex = null;
+ if (nonzero(fgDT)) {
+ InversionMethod fgProb = new InversionMethod(fgDT.values);
+ fgIndex = fgDT.indexOf(fgProb.random());
+ }
+
+ // Initialize background probability field
+ Index2D bgIndex = null;
+ if (nonzero(bgDT)) {
+ InversionMethod bgProb = new InversionMethod(bgDT.values);
+ bgIndex = bgDT.indexOf(bgProb.random());
+ }
+
+ // Add seeds
+ if (fgIndex != null && bgIndex != null) {
+ addSeed(fgDT, fgIndex, AnnotationType.Foreground);
+ addSeed(bgDT, bgIndex, AnnotationType.Background);
+ } else if (fgIndex != null) {
+ addSeed(fgDT, fgIndex, AnnotationType.Foreground);
+ } else if (bgIndex != null) {
+ addSeed(bgDT, bgIndex, AnnotationType.Background);
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ protected void doStep() {
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ DoubleMatrix field = getProbabilityField(fgError, bgError);
+ if (field != null) {
+ InversionMethod rv = new InversionMethod(field.values);
+ Index2D seed = field.indexOf(rv.random());
+ AnnotationType type =
+ (fgError.byteAt(seed) == Constants.ERROR_VALUE) ?
+ AnnotationType.Foreground : AnnotationType.Background;
+ addSeed(field, seed, type);
+
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ private DoubleMatrix getProbabilityField(ByteMatrix fgError, ByteMatrix bgError) {
+
+ // Distance transforms
+ DoubleMatrix fgDT = distanceTransform(fgError);
+ DoubleMatrix bgDT = distanceTransform(bgError);
+
+ // Sum transforms
+ DoubleMatrix field = fgDT;
+ for (int i = 0; i < field.size; i++) {
+ field.values[i] += bgDT.values[i];
+ }
+
+ // Set probability of selected seeds to zero
+ for (Index2D seed : seeds) {
+ field.setDoubleAt(seed, 0.0);
+ }
+
+ if (!isStoppingCriteriaSatisfied(field)) {
+ return field;
+ }
+
+ return null;
+ }
+
+ private void addSeed(DoubleMatrix dt, Index2D seed, AnnotationType type) {
+ double distance = dt.doubleAt(seed);
+ int size = (int) Math.min(distance/2, MAX_BRUSH_SIZE);
+ size = Math.max(size, 1);
+ Point pt = getSwtPointForIndex(seed);
+ getAnnotations().add(new Annotation(type, size, pt));
+ seeds.add(seed);
+ }
+
+ private static DoubleMatrix distanceTransform(ByteMatrix error) {
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(error, Constants.ERROR_VALUE);
+ return dt.computeTransform();
+ }
+
+ private static boolean isStoppingCriteriaSatisfied(DoubleMatrix dt) {
+ return dt.maxValue() < 2.0;
+ }
+
+ private static boolean nonzero(DoubleMatrix matrix) {
+ for (int i = 0; i < matrix.size; i++) {
+ if (matrix.values[i] != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isNonDeterministic() {
+ return true;
+ }
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+import static ie.dcu.auto.Constants.ERROR_VALUE;
+import ie.dcu.auto.*;
+import ie.dcu.graph.dijkstra.IntGraph;
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.stats.InversionMethod;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Implementation of the more complex non-deterministic strategy for interactive
+ * segmentation evaluation.
+ *
+ * @author Kevin McGuinness
+ *
+ */
+public class NonDeterministicAutomator2
+ extends AbstractAutomator implements Automator
+{
+ private final Set<Index2D> seeds;
+
+ public NonDeterministicAutomator2(AutomationData data) {
+ super(data);
+ this.seeds = new HashSet<Index2D>();
+ }
+
+ @Override
+ protected void doReset() {
+ seeds.clear();
+ }
+
+ @Override
+ protected void doStart() {
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ // Distance transform
+ DoubleMatrix fgDT = distanceTransform(fgError);
+ DoubleMatrix bgDT = distanceTransform(bgError);
+
+ // Initialize foreground probability field
+ List<Integer> fgPath = createPath(fgError, fgDT);
+ List<Integer> bgPath = createPath(bgError, bgDT);
+
+ // Add seeds
+ if (fgPath != null && bgPath != null) {
+ addSeedPath(fgDT, fgPath, AnnotationType.Foreground);
+ addSeedPath(bgDT, bgPath, AnnotationType.Background);
+ } else if (fgPath != null) {
+ addSeedPath(fgDT, fgPath, AnnotationType.Foreground);
+ } else if (bgPath != null) {
+ addSeedPath(bgDT, bgPath, AnnotationType.Background);
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ @Override
+ protected void doStep() {
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ DoubleMatrix fgDT = distanceTransform(fgError);
+ DoubleMatrix bgDT = distanceTransform(bgError);
+
+ DoubleMatrix field = getProbabilityField(fgDT, bgDT);
+
+ if (field != null) {
+ InversionMethod rv = new InversionMethod(field.values);
+ int seed = rv.random();
+ List<Integer> path = null;
+ AnnotationType type;
+
+ if (fgError.values[seed] == ERROR_VALUE) {
+ type = AnnotationType.Foreground;
+ path = createPath(fgError, fgDT, seed);
+
+ } else {
+ type = AnnotationType.Background;
+ path = createPath(bgError, bgDT, seed);
+ }
+
+ if (path != null) {
+ addSeedPath(field, path, type);
+ } else {
+ addSeed(field, seed, type);
+ }
+
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ private void addSeed(DoubleMatrix field, int seed, AnnotationType type) {
+ log("Adding single seed");
+
+ Index2D index = field.indexOf(seed);
+ double distance = field.doubleAt(seed);
+ int size = (int) Math.min(distance/2, MAX_BRUSH_SIZE);
+ size = Math.max(size, 1);
+ Point pt = getSwtPointForIndex(index);
+ getAnnotations().add(new Annotation(type, size, pt));
+ seeds.add(index);
+ }
+
+ private void addSeedPath(
+ DoubleMatrix field, List<Integer> path, AnnotationType type) {
+
+ log("Adding seed path");
+
+ int size = MAX_BRUSH_SIZE;
+ for (int k : path) {
+ double distance = field.doubleAt(k);
+ size = (int) Math.min(size, distance/2);
+ }
+
+ size = Math.max(size, 1);
+
+ for (int k : path) {
+ Index2D index = field.indexOf(k);
+ Point pt = getSwtPointForIndex(index);
+ getAnnotations().add(new Annotation(type, size, pt));
+ seeds.add(index);
+ }
+ }
+
+ private List<Integer> createPath(ByteMatrix error, DoubleMatrix dt) {
+ if (nonzero(dt)) {
+
+ // Select random variable
+ InversionMethod field = new InversionMethod(dt.values);
+ int source = field.random();
+
+ // Create path
+ return createPath(error, dt, source);
+ }
+
+ return null;
+ }
+
+ private List<Integer> createPath(ByteMatrix error, DoubleMatrix dt, int source) {
+
+ // Build graph
+ IntGraph graph = buildGraph(error);
+
+ // The graph may not contain the source pixel if it has no neighbors
+ if (graph.hasNode(source)) {
+
+ // Ok, it does
+ graph.findPaths(source);
+
+ // Mark inaccessible pixels with probability zero
+ for (int i = 0; i < dt.size; i++) {
+ if (!graph.hasNode(i)) {
+ dt.values[i] = 0.0;
+ }
+ }
+
+ if (nonzero(dt)) {
+
+ // Select random variable
+ InversionMethod field = new InversionMethod(dt.values);
+ int target = field.random();
+
+ // Get shortest path
+ return graph.getPathFrom(target);
+ }
+ }
+
+ return null;
+ }
+
+ private DoubleMatrix getProbabilityField(
+ DoubleMatrix fgDT, DoubleMatrix bgDT) {
+
+ // Sum transforms
+ DoubleMatrix field = fgDT.clone();
+ for (int i = 0; i < field.size; i++) {
+ field.values[i] += bgDT.values[i];
+ }
+
+ // Set probability of selected seeds to zero
+ for (Index2D seed : seeds) {
+ field.setDoubleAt(seed, 0.0);
+ }
+
+ if (!isStoppingCriteriaSatisfied(field)) {
+ return field;
+ }
+
+ return null;
+ }
+
+ private static IntGraph buildGraph(ByteMatrix matrix) {
+
+ final double SQRT_2 = Math.sqrt(2.0);
+ final byte[] values = matrix.values;
+ final int rows = matrix.rows;
+ final int cols = matrix.cols;
+ final IntGraph graph = new IntGraph(matrix.size);
+
+ for (int i = 0; i < rows; i++) {
+ int k = i * cols;
+
+ for (int j = 0; j < cols; j++, k++) {
+ if (values[k] != ERROR_VALUE) {
+ continue;
+ }
+
+ // Locations neighboring pixels
+ final int north = k - cols;
+ final int south = k + cols;
+ final int west = k - 1;
+ final int east = k + 1;
+ final int northWest = north - 1;
+ final int northEast = north + 1;
+ final int southWest = south - 1;
+ final int southEast = south + 1;
+
+ final boolean hasNorth = i > 0;
+ final boolean hasSouth = i + 1 < rows;
+ final boolean hasWest = j > 0;
+ final boolean hasEast = j + 1 < cols;
+
+ if (hasNorth) {
+ if (hasWest && values[northWest] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, northWest, SQRT_2);
+ }
+
+ if (values[north] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, north, 1.0);
+ }
+
+ if (hasEast && values[northEast] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, northEast, SQRT_2);
+ }
+ }
+
+ if (hasWest && values[west] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, west, 1.0);
+ }
+
+ if (hasEast && values[east] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, east, 1.0);
+ }
+
+ if (hasSouth) {
+ if (hasWest && values[southWest] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, southWest, SQRT_2);
+ }
+
+ if (values[south] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, south, 1.0);
+ }
+
+ if (hasEast && values[southEast] == ERROR_VALUE) {
+ graph.addDirectedEdge(k, southEast, SQRT_2);
+ }
+ }
+ }
+ }
+
+ return graph;
+ }
+
+ private static DoubleMatrix distanceTransform(ByteMatrix error) {
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(error, Constants.ERROR_VALUE);
+ return dt.computeTransform();
+ }
+
+ private static boolean isStoppingCriteriaSatisfied(DoubleMatrix dt) {
+ return dt.maxValue() < 2.0;
+ }
+
+ private static boolean nonzero(DoubleMatrix matrix) {
+ for (int i = 0; i < matrix.size; i++) {
+ if (matrix.values[i] != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isNonDeterministic() {
+ return true;
+ }
+}
--- /dev/null
+package ie.dcu.auto.automator;
+
+import static ie.dcu.auto.Constants.ERROR_VALUE;
+import ie.dcu.auto.*;
+import ie.dcu.graph.dijkstra.IntGraph;
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.annotate.*;
+import ie.dcu.stats.InversionMethod;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Implementation of the more complex non-deterministic strategy for interactive
+ * segmentation evaluation. This automator weights the pixels so that the paths
+ * found tend to stay closer to the center of the shape, rather than using the
+ * absolute shortest path
+ *
+ * @author Kevin McGuinness
+ *
+ */
+public class NonDeterministicAutomator3
+ extends AbstractAutomator implements Automator
+{
+ private final Set<Index2D> seeds;
+
+ public NonDeterministicAutomator3(AutomationData data) {
+ super(data);
+ this.seeds = new HashSet<Index2D>();
+ }
+
+ @Override
+ protected void doReset() {
+ seeds.clear();
+ }
+
+ @Override
+ protected void doStart() {
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ // Distance transform
+ DoubleMatrix fgDT = distanceTransform(fgError);
+ DoubleMatrix bgDT = distanceTransform(bgError);
+
+ // Initialize foreground probability field
+ List<Integer> fgPath = createPath(fgError, fgDT);
+ List<Integer> bgPath = createPath(bgError, bgDT);
+
+ // Add seeds
+ if (fgPath != null && bgPath != null) {
+ addSeedPath(fgDT, fgPath, AnnotationType.Foreground);
+ addSeedPath(bgDT, bgPath, AnnotationType.Background);
+ } else if (fgPath != null) {
+ addSeedPath(fgDT, fgPath, AnnotationType.Foreground);
+ } else if (bgPath != null) {
+ addSeedPath(bgDT, bgPath, AnnotationType.Background);
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ @Override
+ protected void doStep() {
+ ByteMatrix fgError = getForegroundError();
+ ByteMatrix bgError = getBackgroundError();
+
+ DoubleMatrix fgDT = distanceTransform(fgError);
+ DoubleMatrix bgDT = distanceTransform(bgError);
+
+ DoubleMatrix field = getProbabilityField(fgDT, bgDT);
+
+ if (field != null) {
+ InversionMethod rv = new InversionMethod(field.values);
+ int seed = rv.random();
+ List<Integer> path = null;
+ AnnotationType type;
+
+ if (fgError.values[seed] == ERROR_VALUE) {
+ type = AnnotationType.Foreground;
+ path = createPath(fgError, fgDT, seed);
+
+ } else {
+ type = AnnotationType.Background;
+ path = createPath(bgError, bgDT, seed);
+ }
+
+ if (path != null) {
+ addSeedPath(field, path, type);
+ } else {
+ addSeed(field, seed, type);
+ }
+
+ } else {
+ log("Automation complete");
+ setDone(true);
+ }
+ }
+
+ private void addSeed(DoubleMatrix field, int seed, AnnotationType type) {
+ log("Adding single seed");
+
+ Index2D index = field.indexOf(seed);
+ double distance = field.doubleAt(seed);
+ int size = (int) Math.min(distance/2, MAX_BRUSH_SIZE);
+ size = Math.max(size, 1);
+ Point pt = getSwtPointForIndex(index);
+ getAnnotations().add(new Annotation(type, size, pt));
+ seeds.add(index);
+ }
+
+ private void addSeedPath(
+ DoubleMatrix field, List<Integer> path, AnnotationType type) {
+
+ log("Adding seed path");
+
+ int size = MAX_BRUSH_SIZE;
+ for (int k : path) {
+ double distance = field.doubleAt(k);
+ size = (int) Math.min(size, distance/2);
+ }
+
+ size = Math.max(size, 1);
+
+ for (int k : path) {
+ Index2D index = field.indexOf(k);
+ Point pt = getSwtPointForIndex(index);
+ getAnnotations().add(new Annotation(type, size, pt));
+ seeds.add(index);
+ }
+ }
+
+ private List<Integer> createPath(ByteMatrix error, DoubleMatrix dt) {
+ if (nonzero(dt)) {
+
+ // Select random variable
+ InversionMethod field = new InversionMethod(dt.values);
+ int source = field.random();
+
+ // Create path
+ return createPath(error, dt, source);
+ }
+
+ return null;
+ }
+
+ private List<Integer> createPath(ByteMatrix error, DoubleMatrix dt, int source) {
+
+ // Build graph
+ IntGraph graph = buildGraph(error, dt);
+
+ // The graph may not contain the source pixel if it has no neighbors
+ if (graph.hasNode(source)) {
+
+ // Ok, it does
+ graph.findPaths(source);
+
+ // Mark inaccessible pixels with probability zero
+ for (int i = 0; i < dt.size; i++) {
+ if (!graph.hasNode(i)) {
+ dt.values[i] = 0.0;
+ }
+ }
+
+ if (nonzero(dt)) {
+
+ // Select random variable
+ InversionMethod field = new InversionMethod(dt.values);
+ int target = field.random();
+
+ // Get shortest path
+ return graph.getPathFrom(target);
+ }
+ }
+
+ return null;
+ }
+
+ private DoubleMatrix getProbabilityField(
+ DoubleMatrix fgDT, DoubleMatrix bgDT) {
+
+ // Sum transforms
+ DoubleMatrix field = fgDT.clone();
+ for (int i = 0; i < field.size; i++) {
+ field.values[i] += bgDT.values[i];
+ }
+
+ // Set probability of selected seeds to zero
+ for (Index2D seed : seeds) {
+ field.setDoubleAt(seed, 0.0);
+ }
+
+ if (!isStoppingCriteriaSatisfied(field)) {
+ return field;
+ }
+
+ return null;
+ }
+
+ private static IntGraph buildGraph(ByteMatrix matrix, DoubleMatrix dt) {
+
+ final double dtmax = dt.maxValue();
+ final double SQRT_2 = Math.sqrt(2.0);
+ final byte[] values = matrix.values;
+ final int rows = matrix.rows;
+ final int cols = matrix.cols;
+ final IntGraph graph = new IntGraph(matrix.size);
+
+ for (int i = 0; i < rows; i++) {
+ int k = i * cols;
+
+ for (int j = 0; j < cols; j++, k++) {
+ if (values[k] != ERROR_VALUE) {
+ continue;
+ }
+
+ // Locations neighboring pixels
+ final int north = k - cols;
+ final int south = k + cols;
+ final int west = k - 1;
+ final int east = k + 1;
+ final int northWest = north - 1;
+ final int northEast = north + 1;
+ final int southWest = south - 1;
+ final int southEast = south + 1;
+
+ final boolean hasNorth = i > 0;
+ final boolean hasSouth = i + 1 < rows;
+ final boolean hasWest = j > 0;
+ final boolean hasEast = j + 1 < cols;
+
+ double w;
+
+ if (hasNorth) {
+ if (hasWest && values[northWest] == ERROR_VALUE) {
+ w = weight(dt.values[northWest], dtmax, SQRT_2);
+ graph.addDirectedEdge(k, northWest, w);
+ }
+
+ if (values[north] == ERROR_VALUE) {
+ w = weight(dt.values[north], dtmax, 1);
+ graph.addDirectedEdge(k, north, w);
+ }
+
+ if (hasEast && values[northEast] == ERROR_VALUE) {
+ w = weight(dt.values[northEast], dtmax, SQRT_2);
+ graph.addDirectedEdge(k, northEast, w);
+ }
+ }
+
+ if (hasWest && values[west] == ERROR_VALUE) {
+ w = weight(dt.values[west], dtmax, 1);
+ graph.addDirectedEdge(k, west, w);
+ }
+
+ if (hasEast && values[east] == ERROR_VALUE) {
+ w = weight(dt.values[east], dtmax, 1);
+ graph.addDirectedEdge(k, east, w);
+ }
+
+ if (hasSouth) {
+ if (hasWest && values[southWest] == ERROR_VALUE) {
+ w = weight(dt.values[southWest], dtmax, SQRT_2);
+ graph.addDirectedEdge(k, southWest, w);
+ }
+
+ if (values[south] == ERROR_VALUE) {
+ w = weight(dt.values[south], dtmax, 1);
+ graph.addDirectedEdge(k, south, w);
+ }
+
+ if (hasEast && values[southEast] == ERROR_VALUE) {
+ w = weight(dt.values[southEast], dtmax, SQRT_2);
+ graph.addDirectedEdge(k, southEast, w);
+ }
+ }
+ }
+ }
+
+ return graph;
+ }
+
+ private static double weight(double n, double m, double d) {
+ return d*Math.exp(-n/m);
+ }
+
+ private static DoubleMatrix distanceTransform(ByteMatrix error) {
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(error, Constants.ERROR_VALUE);
+ return dt.computeTransform();
+ }
+
+ private static boolean isStoppingCriteriaSatisfied(DoubleMatrix dt) {
+ return dt.maxValue() < 2.0;
+ }
+
+ private static boolean nonzero(DoubleMatrix matrix) {
+ for (int i = 0; i < matrix.size; i++) {
+ if (matrix.values[i] != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isNonDeterministic() {
+ return true;
+ }
+}
--- /dev/null
+package ie.dcu.eval;
+
+import ie.dcu.matrix.ByteMatrix;
+
+import java.util.*;
+
+/**
+ * Abstract class to make implementing evaluators easier. Subclasses should set
+ * the values of name, description and vendor in the constructor, and add all
+ * the measures that will be computed. The subclasses implementation of the run
+ * method should call {@link #setResult(String, double)} for all the measures computed.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractEvaluator implements Evaluator {
+
+ /**
+ * Map will contain the results of the evaluation.
+ */
+ private final Map<String, Double> results;
+
+
+ /**
+ * Name of the evaluator, should be set by subclass on construction.
+ */
+ protected String name;
+
+
+ /**
+ * Description of the evaluator, should be set by subclass on construction.
+ */
+ protected String description;
+
+
+ /**
+ * Vendor of the evaluator, should be set by subclass on construction.
+ */
+ protected String vendor;
+
+
+ /**
+ * Cached names of the measures;
+ */
+ private transient String[] measures;
+
+
+ /**
+ * Constructor.
+ */
+ public AbstractEvaluator() {
+ // Use a tree-map as we expect only a small number of measures
+ results = new TreeMap<String, Double>();
+ }
+
+
+ /**
+ * Default implementation returns the name field.
+ */
+ public String getName() {
+ return name;
+ }
+
+
+ /**
+ * Default implementation returns the description field.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+
+ /**
+ * Default implementation returns the vendor field.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+
+
+ public String[] getMeasures() {
+ if (measures == null) {
+ Set<String> keySet = results.keySet();
+ measures = keySet.toArray(new String[keySet.size()]);
+ }
+ return measures;
+ }
+
+
+ public double getMeasure(String name)
+ throws IllegalStateException, IllegalArgumentException {
+
+ Double measure = results.get(name);
+
+ if (measure == null) {
+
+ if (results.containsKey(name)) {
+
+ // Measure not computed by run method
+ throw new IllegalStateException();
+ }
+
+ // Unknown measure name
+ throw new IllegalArgumentException(name + " is not a valid measure");
+ }
+
+ return measure;
+ }
+
+
+ /**
+ * Add a measure that the evaluator will compute. Subclasses should add all
+ * their measures using this method in their constructor.
+ *
+ * @param name
+ * The name of the measure.
+ */
+ protected void addMeasure(String name) {
+ // Add measure with null value
+ results.put(name, null);
+
+ // Invalidate cache
+ measures = null;
+ }
+
+
+ /**
+ * Set the value of a measure resulting from running the evaluator. This
+ * method should be called at the end of run to set the results of the
+ * evaluation.
+ *
+ * @param name
+ * The name of the measure.
+ * @param value
+ * The computed value.
+ * @throws IllegalArgumentException
+ * If the name of the measure has not been added with
+ * {@link #addMeasure(String)}
+ */
+ protected void setResult(String name, double value)
+ throws IllegalArgumentException {
+
+ // Ensure we're a real measure
+ if (!results.containsKey(name)) {
+ throw new IllegalArgumentException();
+ }
+
+ results.put(name, value);
+ }
+
+
+ /**
+ * Checks if the two masks are non-null and the have the same dimensions. If
+ * they do not, an IllegalArgumentException is thrown. Method is provided to
+ * let subclasses easily validate parameters to run().
+ *
+ * @param a
+ * The first segmentation mask
+ * @param b
+ * The second segmentation mask
+ * @throws IllegalArgumentException
+ * If either mask is <code>null</code> or they have different
+ * dimensions.
+ */
+ protected void check(ByteMatrix a, ByteMatrix b)
+ throws IllegalArgumentException
+ {
+ if (a == null) {
+ throw new IllegalArgumentException("null mask");
+ }
+
+ if (b == null) {
+ throw new IllegalArgumentException("null mask");
+ }
+
+ if (!a.sizeEquals(b)) {
+ throw new IllegalArgumentException("unequal dimensions");
+ }
+
+ }
+}
--- /dev/null
+package ie.dcu.eval;
+
+import ie.dcu.matrix.ByteMatrix;
+
+/**
+ * Extracts the most useful measures from the other two evaluators
+ *
+ * @author Kevin McGuinness
+ */
+public class BestEvaluator extends AbstractEvaluator {
+
+ /**
+ * Object Jaccard index.
+ */
+ public static final String OBJECT_ACCURACY = "Object";
+
+ /**
+ * Fuzzy boundary accuracy measure.
+ */
+ public static final String BOUNDARY_ACCURACY = "Boundary";
+
+ /**
+ * Measures computed by the confusion matrix evaluator
+ */
+ private final ConfusionMatrixEvaluator cmEvaluator;
+
+ /**
+ * Measures computed by the boundary accuracy evaluator
+ */
+ private final BoundaryAccuracyEvaluator baEvaluator;
+
+ /**
+ * Constructor.
+ */
+ public BestEvaluator() {
+
+ // Create evaluators
+ cmEvaluator = new ConfusionMatrixEvaluator();
+ baEvaluator = new BoundaryAccuracyEvaluator();
+
+ // Setup evaluator
+ name = "Boundary and Object Accuracy Evaluator";
+ description = "Computes object jaccard index and fuzzy boundary index";
+ vendor = "Kevin McGuinness";
+
+ // Add measures
+ addMeasure(OBJECT_ACCURACY);
+ addMeasure(BOUNDARY_ACCURACY);
+ }
+
+ /**
+ * Set the sigma parameter.
+ *
+ * The sigma parameter controls the tolerance of the fuzzy Jaccard
+ * measure.
+ *
+ * @param sigma A value greater than zero.
+ */
+ public void setSigma(double sigma) {
+ baEvaluator.setSigma(sigma);
+ }
+
+ /**
+ * Get the sigma parameter.
+ */
+ public double getSigma() {
+ return baEvaluator.getSigma();
+ }
+
+ /**
+ * Run the evaluator.
+ */
+ public void run(ByteMatrix mask, ByteMatrix gt)
+ throws IllegalArgumentException
+ {
+ cmEvaluator.run(mask, gt);
+ baEvaluator.run(mask, gt);
+
+ setResult(OBJECT_ACCURACY, cmEvaluator.getMeasure(
+ ConfusionMatrixEvaluator.JACCARD));
+ setResult(BOUNDARY_ACCURACY, baEvaluator.getMeasure(
+ BoundaryAccuracyEvaluator.FUZZY_ACCURACY));
+ }
+}
--- /dev/null
+package ie.dcu.eval;
+
+import static java.lang.Math.sqrt;
+
+
+/**
+ * Computes a variety of statistical measures and indices to evaluate binary
+ * classifiers based on a confusion matrix computed against a reference.
+ *
+ * @author Kevin McGuinness
+ */
+public class BinaryClassifierEvaluation {
+ public final double tp;
+ public final double fp;
+ public final double tn;
+ public final double fn;
+ public final double total;
+
+
+ public BinaryClassifierEvaluation(int[][] c) {
+ this(c[0][0], c[0][1], c[1][0], c[1][1]);
+ }
+
+
+ public BinaryClassifierEvaluation(long[][] c) {
+ this(c[0][0], c[0][1], c[1][0], c[1][1]);
+ }
+
+
+ public BinaryClassifierEvaluation(long tp, long fn, long fp, long tn) {
+ check(this.tp = tp);
+ check(this.fp = fp);
+ check(this.tn = tn);
+ check(this.fn = fn);
+ this.total = tp + fp + tn + fn;
+ }
+
+
+ public final double getTotal() {
+ return total;
+ }
+
+
+ public final double getPositiveReference() {
+ return tp + fn;
+ }
+
+
+ public final double getNegativeReference() {
+ return tn + fp;
+ }
+
+
+ public final double getPositiveResponse() {
+ return tp + fp;
+ }
+
+
+ public final double getNegativeResponse() {
+ return tn + fn;
+ }
+
+
+ public final double getCorrectResponse() {
+ return tp + tn;
+ }
+
+
+ public final double getIncorrectResponse() {
+ return fp + fn;
+ }
+
+
+ public final double getReferenceLikelihood() {
+ return (tp + fn) / total;
+ }
+
+
+ public final double getResponseLikelihood() {
+ return (tp + fp) / total;
+ }
+
+
+ public final double getAccuracy() {
+ return (tp + tn) / total;
+ }
+
+
+ public final double getRecall() {
+ return tp / (tp + fn);
+ }
+
+ public final double getPrecision() {
+ return tp / (tp + fp);
+ }
+
+
+ public final double getRejectionRecall() {
+ return tn / (tn + fp);
+ }
+
+
+ public final double getRejectionPrecision() {
+ return tn / (tn + fn);
+ }
+
+
+ public final double getFMeasure() {
+ return getFMeasure(1.0);
+ }
+
+
+ public final double getFMeasure(double beta) {
+ double b = beta * beta, p = getPrecision(), r = getRecall();
+ return (1.0 + b) * p * r / (r + (b * p));
+ }
+
+
+ public final double getRandIndex() {
+ // Same as accuracy in the binary case
+ return getAccuracy();
+ }
+
+
+ public final double getJaccardIndex() {
+ // Jaccard similarity coefficient J
+ return tp / (tp + fn + fp);
+ }
+
+
+ public final double getJaccardDistance() {
+ // Jaccard distance J'
+ return (fp + fn) / (fp + fn + tp);
+ }
+
+
+ public final double getFowlkesMallowsIndex() {
+ // Fowlkes-Mallows 83 - A method for comparing 2 hierarchical clusterings
+ double tk = sq(tp) + sq(fn) + sq(fp) + sq(tn) - total;
+ double pk = sq(tp + fp) + sq(fn + tn) - total;
+ double qk = sq(tp + fn) + sq(fp + tn) - total;
+ return tk / sqrt(pk*qk);
+ }
+
+
+ public final double getChiSquared() {
+ double d = tp * tn - fp * fn;
+ return total * d * d / ((tp + fn) * (fp + tn) * (tp + fp) * (fn + tn));
+ }
+
+
+ public final double getPhiSquared() {
+ return getChiSquared() / total;
+ }
+
+
+ public final double getYulesQ() {
+ return (tp * tn - fp * fn) / (tp * tn + fp * fn);
+ }
+
+
+ public final double getYulesY() {
+ return (sqrt(tp * tn) - sqrt(fp * fn)) / (sqrt(tp * tn) + sqrt(fp * fn));
+ }
+
+
+ public final double getAccuracyDeviation() {
+ double a = getAccuracy();
+ return sqrt(a * (1.0 - a) / total);
+ }
+
+
+ public final double getRandomAccuracy() {
+ double ref = (tp + fn) / total;
+ double res = (tp + fp) / total;
+ return ref * res + (1.0 - ref) * (1.0 - res);
+ }
+
+
+ public final double getRandomAccuracyUnbiased() {
+ double avg = tp / total + (fn + fp) / (2.0 * total);
+ return avg * avg + (1.0 - avg) * (1.0 - avg);
+ }
+
+
+ public final double getKappa() {
+ return kappa(getAccuracy(), getRandomAccuracy());
+ }
+
+
+ public final double getKappaUnbiased() {
+ return kappa(getAccuracy(), getRandomAccuracyUnbiased());
+ }
+
+
+ public final double getKappaNoPrevalence() {
+ return 2.0 * getAccuracy() - 1.0;
+ }
+
+
+ private static double kappa(double observed, double expected) {
+ return (observed - expected) / (1 - expected);
+ }
+
+
+ private static double sq(double x) {
+ return x*x;
+ }
+
+
+ private static void check(double v) {
+ if (v < 0) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.eval;
+
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+
+/**
+ * An evaluator that computes internal object boundary accuracy using
+ * the Jaccard and the fuzzy Jaccard indices.
+ *
+ * @author Kevin McGuinness
+ */
+public class BoundaryAccuracyEvaluator extends AbstractEvaluator {
+
+ /**
+ * Name of the fuzzy accuracy measure.
+ */
+ public static final String FUZZY_ACCURACY = "FBA";
+
+ /**
+ * Name of the binary accuracy measure.
+ */
+ public static final String BINARY_ACCURACY = "BBA";
+
+ /**
+ * Default value of the sigma parameter.
+ */
+ public static final double DEFAULT_SIGMA = 3.5;
+
+ /**
+ * Sigma parameter.
+ */
+ private double sigma;
+
+ /**
+ * Constructor
+ */
+ public BoundaryAccuracyEvaluator() {
+
+ // Setup evaluator
+ name = "Boundary Accuracy Evaluator";
+ description = "Compute internal object boundary accuracy " +
+ "using the Jaccard and the fuzzy Jaccard measures";
+ vendor = "Kevin McGuinness";
+
+ // Add measures
+ addMeasure(FUZZY_ACCURACY);
+ addMeasure(BINARY_ACCURACY);
+
+ // Set default sigma value
+ sigma = DEFAULT_SIGMA;
+ }
+
+ /**
+ * Get the sigma parameter.
+ */
+ public double getSigma() {
+ return sigma;
+ }
+
+ /**
+ * Set the sigma parameter.
+ *
+ * The sigma parameter controls the tolerance of the fuzzy Jaccard
+ * measure.
+ *
+ * @param sigma A value greater than zero.
+ */
+ public void setSigma(double sigma) {
+ if (sigma <= 0) {
+ throw new IllegalArgumentException("sigma must be > 0");
+ }
+ this.sigma = sigma;
+ }
+
+ /**
+ * Run evaluator.
+ */
+ public void run(ByteMatrix a, ByteMatrix b)
+ throws IllegalArgumentException
+ {
+ // Check arguments
+ check(a, b);
+
+ // Find boundaries
+ ByteMatrix boundaryA = findInternalBoundary(a);
+ ByteMatrix boundaryB = findInternalBoundary(b);
+
+ // Compute accuracy
+ double fba = fuzzyBoundaryAccuracy(boundaryA, boundaryB);
+ double bba = binaryBoundaryAccuracy(boundaryA, boundaryB);
+
+ // Set result
+ setResult(FUZZY_ACCURACY, fba);
+ setResult(BINARY_ACCURACY, bba);
+ }
+
+ /**
+ * Compute fuzzy boundary accuracy between given boundary masks.
+ */
+ private double fuzzyBoundaryAccuracy(
+ ByteMatrix boundaryA, ByteMatrix boundaryB)
+ {
+ // Compute distance transforms
+ double[] dtA = distanceTransform(boundaryA).values;
+ double[] dtB = distanceTransform(boundaryB).values;
+
+ // Compute Gaussian
+ applyGaussian(dtA);
+ applyGaussian(dtB);
+
+ // Compute fuzzy Jaccard
+ return fuzzyJaccard(dtA, dtB);
+ }
+
+ /**
+ * Compute binary boundary accuracy between given boundary masks.
+ */
+ private double binaryBoundaryAccuracy(
+ ByteMatrix boundaryA, ByteMatrix boundaryB)
+ {
+ // Operate directly on the masks
+ return binaryJaccard(boundaryA.values, boundaryB.values);
+ }
+
+ /**
+ * Fuzzy jaccard computation on fuzzy masks
+ */
+ private double fuzzyJaccard(double[] a, double[] b) {
+ assert (a.length == b.length);
+
+ double num = 0;
+ double den = 0;
+
+ for (int i = 0; i < a.length; i++) {
+ num += Math.min(a[i], b[i]);
+ den += Math.max(a[i], b[i]);
+ }
+
+ if (den != 0.0) {
+ num /= den;
+ return num;
+ }
+
+ return 0.0;
+ }
+
+ /**
+ * Binary Jaccard computation on binary masks.
+ */
+ private double binaryJaccard(byte[] a, byte[] b) {
+ assert (a.length == b.length);
+
+ double union_v = 0;
+ double intersection = 0;
+
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] == FG || b[i] == FG) {
+ union_v++;
+ }
+ if (a[i] == FG && b[i] == FG) {
+ intersection++;
+ }
+ }
+
+ if (union_v != 0) {
+ return intersection / union_v;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Apply gaussian kernel to mask.
+ */
+ private void applyGaussian(double[] values) {
+ double denominator = 2.0 * sigma * sigma;
+ for (int i = 0; i < values.length; i++) {
+ double x = values[i];
+ values[i] = Math.exp(-(x*x) / denominator);
+ }
+ }
+
+ /**
+ * Compute Euclidean distance transform of mask.
+ */
+ private DoubleMatrix distanceTransform(ByteMatrix m) {
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(m, BG);
+ return dt.computeTransform();
+ }
+
+ /**
+ * Determine the internal boundary of the foreground object.
+ */
+ private static ByteMatrix findInternalBoundary(ByteMatrix m) {
+
+ ByteMatrix result = new ByteMatrix(m.rows, m.cols);
+
+ for (int i = 0; i < m.rows; i++) {
+ int k = i * m.cols;
+
+ for (int j = 0; j < m.cols; j++, k++) {
+ if (m.values[k] == FG) {
+
+ // border pixels
+ if (i == 0 || j == 0 ||
+ i == m.rows - 1 || j == m.cols - 1)
+ {
+ result.values[k] = FG;
+ continue;
+ }
+
+ // pixels with neighbors in background
+
+ if (m.values[k-m.cols-1] == BG || // (i-1, j-1)
+ m.values[k-m.cols] == BG || // (i , j-1)
+ m.values[k-m.cols+1] == BG || // (i+1, j-1)
+ m.values[k-1] == BG || // (i-1, j )
+ m.values[k+1] == BG || // (i+1, j )
+ m.values[k+m.cols-1] == BG || // (i-1, j+1)
+ m.values[k+m.cols] == BG || // (i , j+1)
+ m.values[k+m.cols+1] == BG) // (i+1, j+1)
+ {
+ result.values[k] = FG;
+ } else {
+ result.values[k] = BG;
+ }
+ } else {
+ result.values[k] = BG;
+ }
+ }
+ }
+
+ return result;
+ }
+
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.eval;
+
+import ie.dcu.matrix.ByteMatrix;
+
+/**
+ * An evaluator that computes several standard statistical measure using a
+ * confusion matrix.
+ *
+ * @author Kevin McGuinness
+ */
+public class ConfusionMatrixEvaluator extends AbstractEvaluator {
+ public static final String ACCURACY = "Accuracy";
+ public static final String PRECISION = "Precision";
+ public static final String RECALL = "Recall";
+ public static final String F_MEASURE = "F-Measure";
+ public static final String JACCARD = "Jaccard";
+
+ public ConfusionMatrixEvaluator() {
+ name = "Confusion Matrix Evaluator";
+ description = "Computes standard measures using a confusion matrix";
+ vendor = "Kevin McGuinness";
+
+ // Add measures
+ addMeasure(ACCURACY);
+ addMeasure(PRECISION);
+ addMeasure(RECALL);
+ addMeasure(F_MEASURE);
+ addMeasure(JACCARD);
+ }
+
+
+ public void run(ByteMatrix a, ByteMatrix b) {
+
+ // Check preconditions
+ check(a, b);
+
+ // Confusion matrix (confused? :-P)
+ int[][] c = new int[2][2];
+
+ // Zero matrix
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ c[i][j] = 0;
+ }
+ }
+
+ // Compute confusion
+ for (int i = 0; i < a.size; i++) {
+
+ // Indices into confusion matrix
+ int idx1 = a.values[i] == FG ? 0 : 1;
+ int idx2 = b.values[i] == FG ? 0 : 1;
+
+ // Increment confusion
+ c[idx1][idx2]++;
+ }
+
+ // Compute measures and set results
+ BinaryClassifierEvaluation ev =
+ new BinaryClassifierEvaluation(c);
+
+ // Set results
+ setResult(ACCURACY, ev.getAccuracy());
+ setResult(PRECISION, ev.getPrecision());
+ setResult(RECALL, ev.getRecall());
+ setResult(F_MEASURE, ev.getFMeasure());
+ setResult(JACCARD, ev.getJaccardIndex());
+ }
+}
--- /dev/null
+/**
+ *
+ */
+package ie.dcu.eval;
+
+import ie.dcu.matrix.ByteMatrix;
+import ie.dcu.segment.SegmentationMask;
+
+/**
+ * Interface for a class that is capable of evaluating a segmentation mask
+ * against a ground truth.
+ *
+ * @author Kevin McGuinness
+ */
+public interface Evaluator {
+
+ /**
+ * Value for the foreground pixels.
+ */
+ public static final byte FG = SegmentationMask.FOREGROUND;
+
+ /**
+ * Value for the background pixels.
+ */
+ public static final byte BG = SegmentationMask.BACKGROUND;
+
+ /**
+ * Returns the name of the evaluator.
+ *
+ * @return The evaluator name. (never <code>null</code>).
+ */
+ public String getName();
+
+
+ /**
+ * Returns a short description of the evaluator.
+ *
+ * @return A description (never <code>null</code>).
+ */
+ public String getDescription();
+
+
+ /**
+ * Returns the name of the author or vendor.
+ *
+ * @return The vendor.
+ */
+ public String getVendor();
+
+
+ /**
+ * Run the evaluator on the given mask and ground truth.
+ *
+ * @param mask
+ * The segmentation mask to evaluate.
+ * @param gt
+ * The ground truth to evaluate against.
+ * @throws IllegalArgumentException
+ * If the mask or ground truth are <code>null</code>, if they do
+ * not have the same dimensions, or if they contain invalid pixel
+ * values.
+ *
+ */
+ public void run(ByteMatrix mask, ByteMatrix gt)
+ throws IllegalArgumentException;
+
+
+ /**
+ * Returns the names of the measures generated by this evaluator.
+ *
+ * @return The names of the generated measures.
+ */
+ public String[] getMeasures();
+
+
+ /**
+ * Get the computed value of the measure with the given name.
+ *
+ * @param name
+ * The measure name.
+ * @return The value of the computed measure.
+ *
+ * @throws IllegalStateException
+ * If the run method was not previously called successfully.
+ * @throws IllegalArgumentException
+ * If the name is not in the array returned by {@link #getMeasures()}.
+ */
+ public double getMeasure(String name)
+ throws IllegalStateException, IllegalArgumentException;
+}
--- /dev/null
+package ie.dcu.graph.dijkstra;
+
+import java.util.*;
+
+/**
+ * Implementation of Dijkstra's shortest path algorithm.
+ *
+ * @author Kevin McGuinness
+ *
+ * @param <T>
+ * The node type for the graph.
+ */
+public class Graph<T> {
+
+ /**
+ * Map of nodes to node objects.
+ */
+ private final Map<T, Node<T>> nodes;
+
+ /**
+ * The source node from which all paths are found.
+ */
+ private T source;
+
+ /**
+ * Flag to indicate whether or not the algorithm has been run.
+ */
+ private boolean done;
+
+ /**
+ * Construct empty graph.
+ */
+ public Graph() {
+ nodes = new HashMap<T, Node<T>>();
+ done = false;
+ }
+
+ /**
+ * Construct empty graph with the given initial capacity of nodes.
+ */
+ public Graph(int initialCapacity) {
+ nodes = new HashMap<T, Node<T>>(initialCapacity);
+ done = false;
+ }
+
+ /**
+ * Add a directed edge between the given source and target objects.
+ */
+ public void addDirectedEdge(T source, T target, double weight) {
+ checkWeight(weight);
+
+ // Get source node
+ Node<T> srcNode = nodes.get(source);
+ if (srcNode == null) {
+ srcNode = new Node<T>(source);
+ nodes.put(source, srcNode);
+ }
+
+ // Get target node
+ Node<T> dstNode = nodes.get(target);
+ if (dstNode == null) {
+ dstNode = new Node<T>(target);
+ nodes.put(target, dstNode);
+ }
+
+ // Add edge
+ srcNode.addDirectedEdge(dstNode, weight);
+ done = false;
+ }
+
+ /**
+ * Add an undirected edge between the given nodes
+ */
+ public void addUndirectedEdge(T node1, T node2, double weight) {
+ checkWeight(weight);
+
+ // Get source node
+ Node<T> srcNode = nodes.get(node1);
+ if (srcNode == null) {
+ srcNode = new Node<T>(node1);
+ nodes.put(node1, srcNode);
+ }
+
+ // Get target node
+ Node<T> dstNode = nodes.get(node2);
+ if (dstNode == null) {
+ dstNode = new Node<T>(node2);
+ nodes.put(node2, dstNode);
+ }
+
+ // Add edge
+ srcNode.addDirectedEdge(dstNode, weight);
+ dstNode.addDirectedEdge(srcNode, weight);
+ done = false;
+ }
+
+ /**
+ * Returns the (unmodifiable) set of nodes in the graph.
+ */
+ public Set<T> nodes() {
+ return Collections.unmodifiableSet(nodes.keySet());
+ }
+
+ /**
+ * Returns true if the given object is a node in the graph.
+ */
+ public boolean hasNode(T node) {
+ return nodes.containsKey(node);
+ }
+
+ /**
+ * Returns the edge weight for the directed edge between src and dst, or
+ * positive infinity if there is no such edge.
+ */
+ public double getEdgeWeight(T src, T dst) {
+ Node<T> n1 = nodes.get(src);
+ if (n1 != null) {
+ for (Edge<T> edge : n1.edges) {
+ if (edge.target.equals(dst)) {
+ return edge.weight;
+ }
+ }
+ }
+ return Double.POSITIVE_INFINITY;
+ }
+
+ /**
+ * Run Dijkstra's method for the given source node.
+ */
+ public void findPaths(T source) {
+
+ if (!hasNode(source)) {
+ throw new IllegalArgumentException("src node not in graph");
+ }
+
+ // Check if we are re-running for a new source node
+ if (this.source != source) {
+ this.source = source;
+ this.done = false;
+ }
+
+ if (!done) {
+
+ // Initialize nodes
+ for (Node<T> node : nodes.values()) {
+ node.visited = false;
+ node.distance = Double.POSITIVE_INFINITY;
+ node.previous = null;
+ }
+
+ // Create node queue
+ PriorityQueue<Node<T>> queue =
+ new PriorityQueue<Node<T>>();
+
+ // Set distance to zero and add first node to the queue
+ Node<T> node = nodes.get(source);
+ node.distance = 0.0;
+ queue.add(node);
+
+ // Run Dijkstra's method
+ while (!queue.isEmpty()) {
+ node = queue.remove();
+
+ if (node.visited) {
+ continue;
+ }
+
+ node.visited = true;
+
+ for (Edge<T> edge : node.edges) {
+ Node<T> target = edge.target;
+
+ double distance = node.distance + edge.weight;
+ if (distance < target.distance) {
+ target.previous = node;
+ target.distance = distance;
+ }
+
+ if (!target.visited) {
+ queue.add(target);
+ }
+ }
+ }
+
+ // Done
+ done = true;
+ }
+ }
+
+ /**
+ * Returns the source node or null if the {@link #findPaths(Object)}
+ * method has not yet been called.
+ */
+ public T getSource() {
+ return source;
+ }
+
+ /**
+ * Returns the shortest path from the source to the object.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(Object)} function has not been called.
+ */
+ public List<T> getPathTo(T object) {
+ List<T> path = getPathFrom(object);
+ Collections.reverse(path);
+ return path;
+ }
+
+ /**
+ * Returns the shortest path from the object to the source.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(Object)} function has not been called.
+ */
+ public List<T> getPathFrom(T object) {
+ ensureDone();
+ Node<T> node = nodes.get(object);
+ List<T> list = new LinkedList<T>();
+ while (node != null) {
+ list.add(node.object);
+ node = node.previous;
+ }
+ return list;
+ }
+
+ /**
+ * Get the distance to the object. Returns infinity if the object is
+ * not in the graph.
+ */
+ public double getDistanceTo(T object) {
+ ensureDone();
+ Node<T> node = nodes.get(object);
+ return node != null ? node.distance : Double.POSITIVE_INFINITY;
+ }
+
+ /**
+ * Get the distance from the source node to the farthest node in graph.
+ * Returns zero if there are no nodes.
+ */
+ public double getDistanceOfLongestPath() {
+ double max = 0.0;
+ for (Node<T> n : nodes.values()) {
+ if (n.distance > max) {
+ max = n.distance;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Returns the node that is farthest away from the source node.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(Object)} function has not been called.
+ */
+ public T getFarthestNode() {
+ ensureDone();
+
+ T node = null;
+ double max = 0.0;
+ for (Node<T> n : nodes.values()) {
+ if (n.distance > max) {
+ max = n.distance;
+ node = n.object;
+ }
+ }
+
+ return node;
+ }
+
+ /**
+ * Throws an exception if the done flag is not set.
+ */
+ private final void ensureDone() {
+ if (!done) {
+ throw new IllegalStateException("findPaths never called");
+ }
+ }
+
+ /**
+ * Check the specified edge weight is valid.
+ */
+ private static void checkWeight(double weight) {
+ if (weight < 0) {
+ throw new IllegalArgumentException("negative weights not allowed");
+ }
+
+ if (weight == Double.POSITIVE_INFINITY) {
+ throw new IllegalArgumentException("infinite weights not allowed");
+ }
+ }
+
+ /**
+ * Graph node containing an object of type T.
+ */
+ private static class Node<T> implements Comparable<Node<?>> {
+
+ /**
+ * The object the node contains.
+ */
+ public final T object;
+
+ /**
+ * List of edges to adjacent nodes.
+ */
+ public final List<Edge<T>> edges;
+
+ /**
+ * Flag to indicate the node has been visited (dijkstras algorithm).
+ */
+ public boolean visited;
+
+ /**
+ * The minimum distance so far to this node (dijkstras algorithm)
+ */
+ public double distance;
+
+ /**
+ * The previous node in the shortest path.
+ */
+ public Node<T> previous;
+
+ /**
+ * Construct the node for an object.
+ *
+ * @param object An object (non-null)
+ */
+ public Node(T object) {
+ assert (object != null);
+ this.object = object;
+ this.edges = new ArrayList<Edge<T>>(8);
+ this.visited = false;
+ this.distance = Double.POSITIVE_INFINITY;
+ this.previous = null;
+ }
+
+ /**
+ * Add a directed edge between this node and an adjacent one.
+ */
+ public final void addDirectedEdge(Node<T> target, double weight) {
+ edges.add(new Edge<T>(target, weight));
+ }
+
+ /**
+ * Nodes are sorted based on distance.
+ */
+ public int compareTo(Node<?> n) {
+ return distance < n.distance ? -1 : (distance == n.distance ? 0 : 1);
+ }
+ }
+
+ /**
+ * A weighted directed edge.
+ */
+ private static class Edge<T> {
+
+ /**
+ * Target node
+ */
+ public final Node<T> target;
+
+ /**
+ * Edge weight
+ */
+ public final double weight;
+
+ /**
+ * Edge constructor
+ */
+ public Edge(Node<T> target, double weight) {
+ assert (target != null);
+ this.target = target;
+ this.weight = weight;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.graph.dijkstra;
+
+import java.util.*;
+
+/**
+ * Implementation of Dijkstra's shortest path algorithm, optimized
+ * for nodes that have integer indices in a fixed integer range.
+ *
+ * @author Kevin McGuinness
+ */
+public class IntGraph {
+
+ /**
+ * Map of node indices to node objects.
+ */
+ private final Node[] nodes;
+
+ /**
+ * The source node from which all paths are found.
+ */
+ private int source;
+
+ /**
+ * Flag to indicate whether or not the algorithm has been run.
+ */
+ private boolean done;
+
+ /**
+ * Number of nodes inserted into the graph.
+ */
+ private int nodeCount;
+
+ /**
+ * Construct empty graph which can hold nodes up to the given node index.
+ */
+ public IntGraph(int maxNodeIndex) {
+ nodes = new Node[maxNodeIndex];
+ nodeCount = 0;
+ done = false;
+ }
+
+ /**
+ * Reset (clear) the graph.
+ */
+ public void clear() {
+ Arrays.fill(nodes, null);
+ nodeCount = 0;
+ done = false;
+ }
+
+ /**
+ * Add a directed edge between the given source and target objects.
+ */
+ public void addDirectedEdge(int source, int target, double weight) {
+ checkWeight(weight);
+
+ // Get source node
+ Node srcNode = nodes[source];
+ if (srcNode == null) {
+ srcNode = nodes[source] = new Node(source);
+ nodeCount++;
+ }
+
+ // Get target node
+ Node dstNode = nodes[target];
+ if (dstNode == null) {
+ dstNode = nodes[target] = new Node(target);
+ nodeCount++;
+ }
+
+ // Add edge
+ srcNode.addDirectedEdge(dstNode, weight);
+ done = false;
+ }
+
+ /**
+ * Add an undirected edge between the given nodes
+ */
+ public void addUndirectedEdge(int node1, int node2, double weight) {
+ checkWeight(weight);
+
+ // Get source node
+ Node srcNode = nodes[node1];
+ if (srcNode == null) {
+ srcNode = nodes[node1] = new Node(node1);
+ nodeCount++;
+ }
+
+ // Get target node
+ Node dstNode = nodes[node2];
+ if (dstNode == null) {
+ dstNode = nodes[node2] = new Node(node2);
+ nodeCount++;
+ }
+
+ // Add edge
+ srcNode.addDirectedEdge(dstNode, weight);
+ dstNode.addDirectedEdge(srcNode, weight);
+ done = false;
+ }
+
+ /**
+ * Returns the set of nodes in the graph.
+ */
+ public Set<Integer> nodes() {
+ Set<Integer> nodeSet = new HashSet<Integer>();
+ for (int i = 0; i < nodes.length; i++) {
+ if (nodes[i] != null) {
+ nodeSet.add(i);
+ }
+ }
+ return nodeSet;
+ }
+
+ /**
+ * Returns true if the given object is a node in the graph.
+ */
+ public boolean hasNode(int node) {
+ return nodes[node] != null;
+ }
+
+ /**
+ * Returns the edge weight for the directed edge between src and dst, or
+ * positive infinity if there is no such edge.
+ */
+ public double getEdgeWeight(int src, int dst) {
+ Node n1 = nodes[src];
+ if (n1 != null) {
+ for (Edge edge : n1.edges) {
+ if (edge.target.equals(dst)) {
+ return edge.weight;
+ }
+ }
+ }
+ return Double.POSITIVE_INFINITY;
+ }
+
+ /**
+ * Run Dijkstra's method for the given source node.
+ */
+ public void findPaths(int source) {
+
+ if (!hasNode(source)) {
+ throw new IllegalArgumentException("src node not in graph: " + source);
+ }
+
+ // Check if we are re-running for a new source node
+ if (this.source != source) {
+ this.source = source;
+ this.done = false;
+ }
+
+ if (!done) {
+
+ // Initialize nodes
+ for (Node node : nodes) {
+ if (node != null) {
+ node.visited = false;
+ node.distance = Double.POSITIVE_INFINITY;
+ node.previous = null;
+ }
+ }
+
+ // Create node queue
+ PriorityQueue<Node> queue =
+ new PriorityQueue<Node>();
+
+ // Set distance to zero and add first node to the queue
+ Node node = nodes[source];
+ node.distance = 0.0;
+ queue.add(node);
+
+ // Run Dijkstra's method
+ while (!queue.isEmpty()) {
+ node = queue.remove();
+
+ if (node.visited) {
+ continue;
+ }
+
+ node.visited = true;
+
+ for (Edge edge : node.edges) {
+ Node target = edge.target;
+
+ if (target.visited) {
+ continue;
+ }
+
+ double distance = node.distance + edge.weight;
+ if (distance < target.distance) {
+ target.previous = node;
+ target.distance = distance;
+ }
+
+ queue.add(target);
+ }
+ }
+
+ done = true;
+ }
+ }
+
+ /**
+ * Returns the number of nodes in the graph.
+ */
+ public int getNodeCount() {
+ return nodeCount;
+ }
+
+ /**
+ * Returns the source node or null if the {@link #findPaths(int)}
+ * method has not yet been called.
+ */
+ public int getSource() {
+ return source;
+ }
+
+ /**
+ * Returns the shortest path from the source to the object.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(int)} function has not been called.
+ */
+ public List<Integer> getPathTo(int object) {
+ List<Integer> path = getPathFrom(object);
+ Collections.reverse(path);
+ return path;
+ }
+
+ /**
+ * Returns the shortest path from the object to the source.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(int)} function has not been called.
+ */
+ public List<Integer> getPathFrom(int object) {
+ ensureDone();
+ Node node = nodes[object];
+ List<Integer> list = new LinkedList<Integer>();
+ while (node != null) {
+ list.add(node.object);
+ node = node.previous;
+ }
+ return list;
+ }
+
+ /**
+ * Get the distance to the object. Returns infinity if the object is
+ * not in the graph.
+ */
+ public double getDistanceTo(int object) {
+ ensureDone();
+ Node node = nodes[object];
+ return node != null ? node.distance : Double.POSITIVE_INFINITY;
+ }
+
+ /**
+ * Get the distance from the source node to the farthest node in graph.
+ * Returns zero if there are no nodes.
+ */
+ public double getDistanceOfLongestPath() {
+ double max = 0.0;
+ for (Node n : nodes) {
+ if (n != null && n.distance > max) {
+ max = n.distance;
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Returns the node that is farthest away from the source node.
+ *
+ * @throws IllegalStateException
+ * if the {@link #findPaths(int)} function has not been called.
+ */
+ public int getFarthestNode() {
+ ensureDone();
+
+ int node = -1;
+ double max = 0.0;
+ for (Node n : nodes) {
+ if (n != null && n.distance > max) {
+ max = n.distance;
+ node = n.object;
+ }
+ }
+
+ return node;
+ }
+
+ /**
+ * Throws an exception if the done flag is not set.
+ */
+ private final void ensureDone() {
+ if (!done) {
+ throw new IllegalStateException("findPaths never called");
+ }
+ }
+
+ /**
+ * Check the specified edge weight is valid.
+ */
+ private static void checkWeight(double weight) {
+ if (weight < 0) {
+ throw new IllegalArgumentException("negative weights not allowed");
+ }
+
+ if (weight == Double.POSITIVE_INFINITY) {
+ throw new IllegalArgumentException("infinite weights not allowed");
+ }
+ }
+
+ /**
+ * Graph node containing an object of type T.
+ */
+ private static class Node implements Comparable<Node> {
+
+ /**
+ * The object the node contains.
+ */
+ public final int object;
+
+ /**
+ * List of edges to adjacent nodes.
+ */
+ public final List<Edge> edges;
+
+ /**
+ * Flag to indicate the node has been visited (dijkstras algorithm).
+ */
+ public boolean visited;
+
+ /**
+ * The minimum distance so far to this node (dijkstras algorithm)
+ */
+ public double distance;
+
+ /**
+ * The previous node in the shortest path.
+ */
+ public Node previous;
+
+ /**
+ * Construct the node for an object.
+ *
+ * @param object An object (non-null)
+ */
+ public Node(int object) {
+ this.object = object;
+ this.edges = new ArrayList<Edge>(8);
+ this.visited = false;
+ this.distance = Double.POSITIVE_INFINITY;
+ this.previous = null;
+ }
+
+ /**
+ * Add a directed edge between this node and an adjacent one.
+ */
+ public final void addDirectedEdge(Node target, double weight) {
+ edges.add(new Edge(target, weight));
+ }
+
+ /**
+ * Nodes are sorted based on distance.
+ */
+ public int compareTo(Node n) {
+ return distance < n.distance ? -1 : (distance == n.distance ? 0 : 1);
+ }
+ }
+
+ /**
+ * A weighted directed edge.
+ */
+ private static class Edge {
+
+ /**
+ * Target node
+ */
+ public final Node target;
+
+ /**
+ * Edge weight
+ */
+ public final double weight;
+
+ /**
+ * Edge constructor
+ */
+ public Edge(Node target, double weight) {
+ assert (target != null);
+ this.target = target;
+ this.weight = weight;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.image;
+
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+/**
+ * Abstract implementation of an {@link ImageOp}. Subclasses should implement
+ * the {@link #processImage(MatrixProvider)} method.
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractImageOp implements ImageOp {
+
+ private MatrixProvider inputProvider;
+ private Matrix outputMatrix;
+
+ /**
+ * Default constructor (does nothing).
+ */
+ public AbstractImageOp() {
+ }
+
+ /**
+ * Returns <code>true</code> if the output matrix is available.
+ */
+ public boolean isProcessingComplete() {
+ return outputMatrix != null;
+ }
+
+ /**
+ * Returns <code>true</code> if the input {@link MatrixProvider} has been
+ * set.
+ */
+ public boolean isInputProviderSet() {
+ return inputProvider != null;
+ }
+
+ /**
+ * Set the input provider. Clears the output {@link Matrix}.
+ *
+ * @see ImageOp#setInputProvider(MatrixProvider)
+ */
+ public void setInputProvider(MatrixProvider provider) {
+ this.inputProvider = provider;
+ this.outputMatrix = null;
+ }
+
+ /**
+ * Returns the input {@link MatrixProvider}.
+ *
+ * @return The input {@link MatrixProvider} or <code>null</code> if it has
+ * not been set.
+ */
+ public MatrixProvider getInputProvider() {
+ return inputProvider;
+ }
+
+ /**
+ * Returns the output {@link Matrix}.
+ *
+ * @return The output {@link Matrix} or <code>null</code> if it has not been
+ * set.
+ */
+ protected Matrix getOutputMatrix() {
+ if (!isInputProviderSet()) {
+ throw new IllegalStateException("input provider not set");
+ }
+
+ if (!isProcessingComplete()) {
+ outputMatrix = processImage(getInputProvider());
+ }
+
+ return outputMatrix;
+ }
+
+ public Matrix getMatrix(boolean alwaysCopy) {
+ return getMatrix(null, false);
+ }
+
+ public Matrix getMatrix(Type type, boolean alwaysCopy) {
+ return getOutputMatrix().getMatrix(type, alwaysCopy);
+ }
+
+ /**
+ * Process the image and return the results.
+ *
+ * @param imageProvider
+ * A class that can be used to obtain the input image.
+ * @return The result of the image operation.
+ */
+ protected abstract Matrix processImage(MatrixProvider imageProvider);
+
+}
--- /dev/null
+package ie.dcu.image;
+
+import ie.dcu.matrix.*;
+
+import java.awt.Polygon;
+import java.util.*;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Trace contours of objects in images.
+ *
+ * TODO: This class uses SWT points and AWT polygons...
+ *
+ * @author Kevin McGuinness
+ */
+public class ContourTracer {
+
+ private final IntMatrix labels;
+ private final int nobjects;
+
+ // Inversion table
+ private static final int[] INVERT = { 4,5,6,7,0,1,2,3 };
+
+ // Neighbor count
+ private static final int NEIGHBORS = 8;
+
+ public ContourTracer(ByteMatrix mask, byte foregroundValue) {
+ ObjectLabeler mapper = new ObjectLabeler();
+ mapper.setForegroundValue(foregroundValue);
+ nobjects = mapper.label(mask);
+ labels = mapper.getLabelMap();
+ }
+
+ public List<Polygon> trace() {
+ List<Polygon> polygons = new ArrayList<Polygon>();
+ if (labels != null) {
+ doTrace(polygons);
+ }
+ return polygons;
+ }
+
+ private void doTrace(List<Polygon> polygons) {
+ for (int i = 1; i <= nobjects; i++) {
+ Polygon polygon = trace(i);
+ if (polygon != null) {
+ polygons.add(polygon);
+ }
+ //break;
+ }
+ }
+
+
+ private Polygon trace(int object) {
+ Point e1 = findFirstObjectPixel(object);
+
+ if (e1 != null) {
+ // First external border pixel is to the left
+ e1 = new Point(e1.x-1, e1.y);
+
+ List<Point> pts = new ArrayList<Point>();
+ pts.add(e1);
+
+ // Find the second pixel
+ Candidate c = findFirstCandidate(e1, object);
+
+ if (c != null) {
+
+ Candidate next = new Candidate(c.pt, c.direction);
+
+ do {
+ next = findNext(next.pt, next.direction, object);
+ pts.add(next.pt);
+ } while (!next.pt.equals(e1));
+ }
+
+ return toPolygon(pts);
+ }
+
+ return null;
+ }
+
+ private final Candidate findNext(Point pc, int dpc, int object) {
+ Candidate next = null;
+
+ int dcp = INVERT[dpc];
+ for (int r = 0; r < 7; r++) {
+ int de = (dcp + r) % NEIGHBORS;
+ int di = (dcp + r + 1) % NEIGHBORS;
+ Point pe = neighbour(pc, de);
+ Point pi = neighbour(pc, di);
+ if (isBackground(pe, object) && isObject(pi, object)) {
+ next = new Candidate(pe, de);
+ }
+ }
+
+ return next;
+ }
+
+ private final Point findFirstObjectPixel(int object) {
+ for (int y = 0; y < labels.rows; y++) {
+ for (int x = 0; x < labels.cols; x++) {
+ int label = labels.intAt(y, x);
+ if (label == object) {
+ return new Point(x, y);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ static final class Candidate {
+ final Point pt;
+ final int direction;
+
+ Candidate(Point pt, int direction) {
+ this.pt = new Point(pt.x, pt.y);
+ this.direction = direction;
+ }
+ }
+
+ private final Candidate findFirstCandidate(Point p0, int object) {
+
+ // Find the next pixel
+ for (int i = 4; i < NEIGHBORS; i++) {
+ Point p1 = neighbour(p0, i);
+ Point p2 = neighbour(p0, (i+1) % NEIGHBORS);
+ if (isBackground(p1, object) && isObject(p2, object)) {
+ return new Candidate(p1, i);
+ }
+ }
+ return null;
+ }
+
+
+ private final boolean isBackground(Point pt, int object) {
+ return !labels.hasIndex(pt.y, pt.x) || labels.intAt(pt.y, pt.x) != object;
+ }
+
+ private final boolean isObject(Point pt, int label) {
+ return labels.hasIndex(pt.y, pt.x) && labels.intAt(pt.y, pt.x) == label;
+ }
+
+ private final Point neighbour(Point pt, int n) {
+ Point np = new Point(pt.x, pt.y);
+ switch (n) {
+ case 0:
+ np.x++;
+ break;
+ case 1:
+ np.x++;
+ np.y--;
+ break;
+ case 2:
+ np.y--;
+ break;
+ case 3:
+ np.x--;
+ np.y--;
+ break;
+ case 4:
+ np.x--;
+ break;
+ case 5:
+ np.x--;
+ np.y++;
+ break;
+ case 6:
+ np.y++;
+ break;
+ case 7:
+ np.x++;
+ np.y++;
+ default:
+ assert (false);
+ }
+ return np;
+ }
+
+
+ private static Polygon toPolygon(List<Point> pts) {
+ Polygon poly = new Polygon();
+ poly.npoints = pts.size();
+ poly.xpoints = new int[pts.size()];
+ poly.ypoints = new int[pts.size()];
+
+ int i = 0;
+ for (Point p : pts) {
+ poly.xpoints[i] = p.x;
+ poly.ypoints[i] = p.y;
+ i++;
+ }
+ return poly;
+ }
+}
+
--- /dev/null
+package ie.dcu.image;
+
+import ie.dcu.matrix.MatrixProvider;
+
+/**
+ * Interface for classes that implement operations on images.
+ *
+ * The following is an example of how it can be used:
+ * <pre>
+ * ByteMatrix input = ...
+ * ImageOp op = createImageOp();
+ * op.setInputProvider(input);
+ * ByteMatrix matrix = (ByteMatrix) op.getMatrix(Matrix.Type.Byte, false);
+ * </pre>
+ *
+ * The above assumes that <code>createImageOp()</code> creates and returns
+ * an instance of <code>ImageOp</code>.
+ *
+ * @author Kevin McGuinness
+ */
+public interface ImageOp extends MatrixProvider {
+
+ /**
+ * Set the class that will provide the input for this image operation.
+ *
+ * @param provider
+ * A {@link MatrixProvider} instance.
+ */
+ public void setInputProvider(MatrixProvider provider);
+}
--- /dev/null
+package ie.dcu.image;
+
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+import java.util.Stack;
+
+/**
+ * Class to perform connected component analysis on a binary object mask and
+ * create a labeled map of connected components.
+ *
+ * Connected component analysis is performed using the 8-neighborhood rule.
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public class ObjectLabeler implements MatrixProvider {
+
+ /**
+ * Value of the background label in the label maps produced.
+ */
+ public static final int BACKGROUND_LABEL = 0;
+
+ /**
+ * Offsets of the row indices for each of the 8-neighbors
+ */
+ private static final int[] Ni = { -1, -1, -1, 0, 0, 1, 1, 1 };
+
+ /**
+ * Offsets of the column values for each of the 8-neighbors
+ */
+ private static final int[] Nj = { -1, 0, 1, -1, 1, -1, 0, 1 };
+
+ /**
+ * The label matrix.
+ */
+ private IntMatrix labels;
+
+ /**
+ * The number of objects found in the last labeling.
+ */
+ private int objectCount;
+
+ /**
+ * The value used to identify foreground pixels in the object mask.
+ */
+ private byte foregroundValue;
+
+ /**
+ * Construct an object labeler with a default foreground label value of 1.
+ */
+ public ObjectLabeler() {
+ this((byte) 1);
+ }
+
+ /**
+ * Construct an object labeler that identifies foreground pixels using the
+ * given value.
+ *
+ * @param foregroundValue
+ * The value used to identify foreground pixels in the mask.
+ */
+ public ObjectLabeler(byte foregroundValue) {
+ this.objectCount = 0;
+ this.foregroundValue = foregroundValue;
+ }
+
+ /**
+ * Get the foreground value used to identify foreground pixels in the mask.
+ */
+ public byte getForegroundValue() {
+ return foregroundValue;
+ }
+
+ /**
+ * Set the foreground value used to identify foreground pixels in the mask.
+ */
+ public void setForegroundValue(byte value) {
+ foregroundValue = value;
+ }
+
+ /**
+ * Return the number of objects found in the last labeling.
+ */
+ public int getObjectCount() {
+ return objectCount;
+ }
+
+ /**
+ * Returns the label map computed in the last labeling. This will be null
+ * if no labeling has been performed with this labeler.
+ */
+ public IntMatrix getLabelMap() {
+ return labels;
+ }
+
+ /**
+ * Compute the connected component labeling of the given binary mask.
+ *
+ * @param mask
+ * A binary matrix (non null).
+ * @return The number of objects found.
+ */
+ public int label(ByteMatrix mask) {
+ labels = new IntMatrix(mask.rows, mask.cols);
+
+ // Fill the object with the background label
+ labels.fill(BACKGROUND_LABEL);
+
+ // Current label
+ int label = 0;
+
+ // Labeling loop
+ for (int i = 0, k = 0; i < mask.rows; i++) {
+ for (int j = 0; j < mask.cols; j++, k++) {
+
+ if (mask.values[k] == foregroundValue &&
+ labels.values[k] == BACKGROUND_LABEL)
+ {
+ label++;
+
+ // Unlabeled foreground pixel
+ doLabel(mask, i, j, label);
+ }
+ }
+ }
+
+ // Set and return label count
+ return (objectCount = label);
+ }
+
+ /**
+ * Internal routine for label propagation.
+ */
+ private void doLabel(ByteMatrix mask, int i, int j, int label) {
+
+ // Stack of indices to process
+ Stack<Index2D> stack = new Stack<Index2D>();
+ stack.add(new Index2D(i, j));
+
+ // Processing loop
+ while (!stack.isEmpty()) {
+
+ Index2D idx = stack.pop();
+
+ if (labels.intAt(idx) != BACKGROUND_LABEL) {
+ // Already labeled
+ continue;
+ }
+
+ labels.setIntAt(idx, label);
+
+ // iterate over neighbors
+ for (int k = 0; k < Ni.length; k++) {
+ i = idx.i + Ni[k];
+ j = idx.j + Nj[k];
+
+ // Check in range
+ if (labels.hasIndex(i, j)) {
+
+ int offset = labels.offsetOf(i, j);
+
+ if (labels.values[offset] == BACKGROUND_LABEL &&
+ mask.values[offset] == foregroundValue)
+ {
+ stack.push(new Index2D(i, j));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Implementation of matrix provider.
+ */
+ public Matrix getMatrix(boolean alwaysCopy) {
+ return getMatrix(null, alwaysCopy);
+ }
+
+ /**
+ * Implementation of matrix provider.
+ */
+ public Matrix getMatrix(Type type, boolean alwaysCopy) {
+ if (labels == null) {
+ throw new IllegalStateException();
+ }
+
+ return labels.getMatrix(type, alwaysCopy);
+ }
+
+ /**
+ * Implementation of matrix provider.
+ */
+ public Type getDefaultMatrixType() {
+ return labels.getDefaultMatrixType();
+ }
+}
--- /dev/null
+package ie.dcu.image.binary;
+
+import ie.dcu.image.AbstractImageOp;
+import ie.dcu.matrix.*;
+
+/**
+ * Abstract implementation of the {@link BinaryImageOp} interface.
+ *
+ * Subclasses should implement either the {@link #processImage(ByteMatrix)} or
+ * the {@link #processImage(MatrixProvider)} method.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractBinaryImageOp extends AbstractImageOp
+ implements BinaryImageOp
+{
+ /**
+ * The value that should be interpreted by the implementor as a background
+ * pixel.
+ */
+ protected byte backgroundValue;
+
+ /**
+ * The value that should be interpreted by the implementor as a foreground
+ * pixel.
+ */
+ protected byte foregroundValue;
+
+ /**
+ * Flag indicating that the default implementation of
+ * {@link #processImage(MatrixProvider)} should request a copy of the input
+ * {@link Matrix}.
+ */
+ private boolean requestInputCopy;
+
+ /**
+ * Default constructor. Sets the {@link #foregroundValue} and
+ * {@link #backgroundValue} values to
+ * {@link BinaryImageOp#DEFAULT_FOREGROUND_VALUE} and
+ * {@link BinaryImageOp#DEFAULT_BACKGROUND_VALUE}
+ */
+ public AbstractBinaryImageOp() {
+ backgroundValue = DEFAULT_BACKGROUND_VALUE;
+ foregroundValue = DEFAULT_FOREGROUND_VALUE;
+ requestInputCopy = true;
+ }
+
+ /**
+ * Set a flag indicating whether the default implementation of
+ * {@link #processImage(MatrixProvider)} should pass a copy of the input to
+ * matrix to {@link #processImage(ByteMatrix)}.
+ *
+ * @param requestInputCopy
+ * <code>true</code> to have {@link #processImage(ByteMatrix)}
+ * request a copy of the input matrix, <code>false</code> otherwise.
+ */
+ protected void setRequestInputCopy(boolean requestInputCopy) {
+ this.requestInputCopy = requestInputCopy;
+ }
+
+ public void setBackgroundValue(byte backgroundValue) {
+ this.backgroundValue = backgroundValue;
+ }
+
+ /**
+ * Returns the value that should be interpreted by the implementor as
+ * a background pixel.
+ */
+ public byte getBackgroundValue() {
+ return backgroundValue;
+ }
+
+ public void setForegroundValue(byte foregroundValue) {
+ this.foregroundValue = foregroundValue;
+ }
+
+ /**
+ * Returns the value that should be interpreted by the implementor as
+ * a foreground pixel.
+ */
+ public byte getForegroundValue() {
+ return foregroundValue;
+ }
+
+ protected final Matrix processImage(MatrixProvider inputProvider) {
+ Matrix matrix = inputProvider.getMatrix(Matrix.Type.Byte, requestInputCopy);
+ return processImage((ByteMatrix) matrix);
+ }
+
+ /**
+ * Process the image and return the results.
+ *
+ * @param input
+ * The input byte matrix.
+ * @return The result of the image operation.
+ */
+ protected abstract Matrix processImage(ByteMatrix input);
+}
--- /dev/null
+package ie.dcu.image.binary;
+
+import ie.dcu.image.ImageOp;
+
+/**
+ * A specialization of {@link ImageOp} for binary images.
+ *
+ * @author Kevin McGuinness
+ */
+public interface BinaryImageOp extends ImageOp {
+
+ /**
+ * The default byte value that the image operation should consider to
+ * foreground.
+ */
+ public static final byte DEFAULT_BACKGROUND_VALUE = 0;
+
+ /**
+ * The default byte value that the image operation should consider to
+ * background.
+ */
+ public static final byte DEFAULT_FOREGROUND_VALUE = 1;
+
+ /**
+ * Set the value that the image operation should consider to be foreground.
+ *
+ * @param foregroundValue
+ * A byte value.
+ */
+ public void setForegroundValue(byte foregroundValue);
+
+ /**
+ * Set the value that the image operation should consider to be background.
+ *
+ * @param backgroundValue
+ * A byte value.
+ */
+ public void setBackgroundValue(byte backgroundValue);
+}
--- /dev/null
+package ie.dcu.image.binary;
+
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+/**
+ * Distance transform operation. This class is a {@link BinaryImageOp} wrapper
+ * for the {@link DistanceTransform} object.
+ *
+ * @author Kevin McGuinness
+ */
+public class DistanceTransformOp extends AbstractBinaryImageOp {
+ private boolean computeSquareTransform;
+
+ public DistanceTransformOp() {
+ setRequestInputCopy(false);
+ computeSquareTransform = false;
+ }
+
+ @Override
+ protected Matrix processImage(ByteMatrix input) {
+ DistanceTransform transform = new DistanceTransform();
+ transform.init(input, foregroundValue);
+ if (computeSquareTransform) {
+ return transform.computeSquareTransform();
+ }
+ return transform.computeTransform();
+ }
+
+ public Type getDefaultMatrixType() {
+ return Matrix.Type.Double;
+ }
+}
--- /dev/null
+package ie.dcu.image.binary;
+
+import ie.dcu.image.dt.DistanceTransform;
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+/**
+ * Implementation of the Medial Axis Transform (MAT).
+ *
+ * Computes the MAT by finding the Laplace of the square distance transform.
+ *
+ * @author Kevin McGuinness
+ */
+public class MedialAxisTransformOp extends AbstractBinaryImageOp {
+ private boolean simpleLaplace = false;
+
+ @Override
+ protected Matrix processImage(ByteMatrix input) {
+ DistanceTransform transform = new DistanceTransform();
+ transform.init(input, foregroundValue);
+ IntMatrix dt = transform.computeSquareTransform();
+ return laplace(dt).clamp(0, Integer.MAX_VALUE);
+ }
+
+
+ private IntMatrix laplace(IntMatrix m) {
+ return simpleLaplace ? laplace4(m) : laplace8(m);
+ }
+
+ private IntMatrix laplace4(IntMatrix m) {
+
+ IntMatrix result = new IntMatrix(m.rows, m.cols);
+
+ int n0, n1, n2, n3;
+ for (int i = 0; i < m.rows; i++) {
+
+ n0 = (i != 0) ? 1 : 0;
+ n3 = (i != m.rows - 1) ? 1 : 0;
+
+ for (int j = 0; j < m.cols; j++) {
+
+ n1 = (j != 0) ? 1 : 0;
+ n2 = (j != m.cols-1) ? 1 : 0;
+
+ int value = (n0 + n1 + n2 + n3) * m.intAt(i, j);
+ value -= (n0 != 0) ? m.intAt(i-1, j) : 0;
+ value -= (n1 != 0) ? m.intAt(i, j-1) : 0;
+ value -= (n2 != 0) ? m.intAt(i, j+1) : 0;
+ value -= (n3 != 0) ? m.intAt(i+1, j) : 0;
+ result.setIntAt(i, j, value);
+ }
+ }
+
+ return result;
+ }
+
+ private IntMatrix laplace8(IntMatrix m) {
+ IntMatrix result = new IntMatrix(m.rows, m.cols);
+
+ int a = m.rows - 1;
+ int b = m.cols - 1;
+
+ int n0, n1, n2, n3, n4, n5, n6, n7;
+ for (int i = 0; i < m.rows; i++) {
+
+ for (int j = 0; j < m.cols; j++) {
+
+ n0 = (i != 0 && j != 0) ? 1 : 0;
+ n1 = (i != 0) ? 1 : 0;
+ n2 = (i != 0 && j != b) ? 1 : 0;
+ n3 = (j != 0) ? 1 : 0;
+ n4 = (j != b) ? 1 : 0;
+ n5 = (i != a && j != 0) ? 1 : 0;
+ n6 = (i != a) ? 1 : 0;
+ n7 = (i != a && j != b) ? 1 : 0;
+
+ int w = (n0 + n1 + n2 + n3 + n4 + n5 + n6 + n7);
+ int v = w * m.intAt(i, j);
+
+ v -= (n0 != 0) ? m.intAt(i-1, j-1) : 0;
+ v -= (n1 != 0) ? m.intAt(i-1, j ) : 0;
+ v -= (n2 != 0) ? m.intAt(i-1, j+1) : 0;
+ v -= (n3 != 0) ? m.intAt(i , j-1) : 0;
+ v -= (n4 != 0) ? m.intAt(i , j+1) : 0;
+ v -= (n5 != 0) ? m.intAt(i+1, j-1) : 0;
+ v -= (n6 != 0) ? m.intAt(i+1, j ) : 0;
+ v -= (n7 != 0) ? m.intAt(i+1, j+1) : 0;
+
+ result.setIntAt(i, j, v);
+ }
+ }
+ return result;
+ }
+
+ public Type getDefaultMatrixType() {
+ return Matrix.Type.Int;
+ }
+}
--- /dev/null
+package ie.dcu.image.binary;
+
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+/**
+ * Base class for binary morphological operations.
+ *
+ * Implementations of different morphological operations are given as the static
+ * inner classes.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class MorphOp extends AbstractBinaryImageOp {
+
+ /**
+ * The structuring element for the morph.
+ */
+ protected ByteMatrix structuringElement;
+
+ /**
+ * Get the structuring element.
+ *
+ * @return the structuring element (may be null).
+ */
+ public ByteMatrix getStructuringElement() {
+ return structuringElement;
+ }
+
+ /**
+ * Set the structuring element.
+ *
+ * @param se
+ * A structuring element (can be null).
+ */
+ public void setStructuringElement(ByteMatrix se) {
+ this.structuringElement = se;
+ }
+
+ /**
+ * Perform a basic morph-op on the given matrix.
+ *
+ * The operation will be either an erode or dilate depending
+ * on the values of the a and b parameters.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @param a
+ * Either the foregroundValue or the backgroundValue
+ * @param b
+ * Either the foregroundValue or the backgroundValue
+ * @return
+ * The modified input matrix.
+ */
+ protected static ByteMatrix morph(
+ final ByteMatrix matrix,
+ final byte a,
+ final byte b
+ ) {
+
+ final byte[] im = matrix.values;
+
+ // Right pixel
+ for (int i = 0; i < matrix.rows; i++) {
+ final int off = i * matrix.cols;
+
+ for (int j = 0; j < matrix.cols - 1; j++) {
+ final int idx = off + j;
+
+ if (im[idx] == b && im[idx+1] == a) {
+ im[idx] = a;
+ }
+ }
+ }
+
+ // Left pixel
+ for (int i = 0; i < matrix.rows; i++) {
+ final int off = i * matrix.cols;
+
+ for (int j = matrix.cols - 1; j > 0; j--) {
+ final int idx = off + j;
+
+ if (im[idx] == b && im[idx-1] == a) {
+ im[idx] = a;
+ }
+ }
+ }
+
+ // Below pixel
+ for (int i = 0; i < matrix.rows - 1; i++) {
+ final int off = i * matrix.cols;
+
+ for (int j = 0; j < matrix.cols; j++) {
+ final int idx = off + j;
+
+ if (im[idx] == b && im[idx + matrix.cols] == a) {
+ im[idx] = a;
+ }
+ }
+ }
+
+ // Above pixel
+ for (int i = matrix.rows - 1; i > 0; i--) {
+ final int off = i * matrix.cols;
+
+ for (int j = 0; j < matrix.cols; j++) {
+ final int idx = off + j;
+
+ if (im[idx] == b && im[idx - matrix.cols] == a) {
+ im[idx] = a;
+ }
+ }
+ }
+
+ return matrix;
+ }
+
+ /**
+ * Perform a binary morphological operation with a structuring element.
+ *
+ * @param matrix
+ * The input matrix to morph (not modified).
+ * @param se
+ * The structuring element.
+ * @param a
+ * Either the foreground or background value.
+ * @param b
+ * Either the foreground or background value.
+ * @return A matrix containing the result of the morph.
+ */
+ protected static ByteMatrix morph(
+ final ByteMatrix matrix,
+ final ByteMatrix se,
+ final byte a,
+ final byte b)
+ {
+ final ByteMatrix result =
+ new ByteMatrix(matrix.rows, matrix.cols);
+ result.fill(a);
+
+ for (int i = 0, k = 0; i < matrix.rows; i++) {
+ for (int j = 0; j < matrix.cols; j++, k++) {
+
+ if (matrix.values[k] == b) {
+ byte value = b;
+
+ int im_i0 = i - (se.rows >> 1);
+ int im_j0 = j - (se.cols >> 1);
+ int se_i0 = im_i0 < 0 ? -im_i0 : 0;
+ int se_j0 = im_j0 < 0 ? -im_j0 : 0;
+
+ if (im_i0 < 0) {
+ im_i0 = 0;
+ }
+
+ if (im_j0 < 0) {
+ im_j0 = 0;
+ }
+
+ // Iterate over structuring element
+ outer:
+ for (int se_i = se_i0, im_i = im_i0;
+ se_i < se.rows && im_i < matrix.rows;
+ se_i++, im_i++)
+ {
+ int off1 = se_i * se.cols;
+ int off2 = im_i * matrix.cols;
+
+ for (int se_j = se_j0, im_j = im_j0;
+ se_j < se.cols && im_j < matrix.cols;
+ se_j++, im_j++)
+ {
+ if (se.values[off1 + se_j] != 0 &&
+ matrix.values[off2 + im_j] == a)
+ {
+ value = a;
+ break outer;
+ }
+ }
+ }
+
+ // Set value
+ result.values[k] = value;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Applies the morphological dilate operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix dilate(ByteMatrix matrix) {
+ return morph(matrix, foregroundValue, backgroundValue);
+ }
+
+ /**
+ * Perform a morphological dilation using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix dilate(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return dilate(matrix.clone());
+ }
+
+ return morph(matrix, se, foregroundValue, backgroundValue);
+ }
+
+
+ /**
+ * Applies the morphological erode operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix erode(ByteMatrix matrix) {
+ return morph(matrix, backgroundValue, foregroundValue);
+ }
+
+ /**
+ * Perform a morphological erosion using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix erode(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return erode(matrix.clone());
+ }
+
+ return morph(matrix, se, backgroundValue, foregroundValue);
+ }
+
+
+ /**
+ * Applies the morphological open operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix open(ByteMatrix matrix) {
+ return dilate(erode(matrix));
+ }
+
+ /**
+ * Perform a morphological open using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix open(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return open(matrix.clone());
+ }
+ return dilate(erode(matrix, se), se);
+ }
+
+ /**
+ * Applies the morphological open operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix close(ByteMatrix matrix) {
+ return erode(dilate(matrix));
+ }
+
+ /**
+ * Perform a morphological close using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix close(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return close(matrix.clone());
+ }
+ return erode(dilate(matrix, se), se);
+ }
+
+ /**
+ * Applies the morphological smooth operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix smooth(ByteMatrix matrix) {
+ return close(open(matrix));
+ }
+
+ /**
+ * Perform a morphological smooth using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix smooth(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return smooth(matrix.clone());
+ }
+ return close(open(matrix, se), se);
+ }
+
+ /**
+ * Applies the morphological edge operator.
+ *
+ * @param matrix
+ * The matrix to operate on (will be modified).
+ * @return the input matrix
+ */
+ protected ByteMatrix edge(ByteMatrix matrix) {
+
+ // Dilate
+ dilate(matrix);
+
+ // Erode to temporary
+ ByteMatrix erosion = erode(matrix.clone());
+
+ // Remove eroded mask
+ for (int i = 0; i < matrix.size; i++) {
+ if (erosion.values[i] == foregroundValue) {
+ matrix.values[i] = backgroundValue;
+ }
+ }
+
+ return matrix;
+ }
+
+ /**
+ * Perform a morphological edge using the given structuring element
+ *
+ * @param matrix
+ * The matrix to operate on (will not be modified).
+ * @param se
+ * The structuring element
+ * @return A matrix containing the result of the morph.
+ */
+ protected ByteMatrix edge(ByteMatrix matrix, ByteMatrix se) {
+ if (se == null) {
+ return edge(matrix.clone());
+ }
+
+ // Dilate
+ dilate(matrix, se);
+
+ // Erode to temporary
+ ByteMatrix erosion = erode(matrix.clone(), se);
+
+ // Remove eroded mask
+ for (int i = 0; i < matrix.size; i++) {
+ if (erosion.values[i] == foregroundValue) {
+ matrix.values[i] = backgroundValue;
+ }
+ }
+
+ return matrix;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ie.dcu.cdvp.matrix.MatrixProvider#getDefaultMatrixType()
+ */
+ public Type getDefaultMatrixType() {
+ return Matrix.Type.Byte;
+ }
+
+ /**
+ * Binary morphological erosion operation.
+ */
+ public static class Erode extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return erode(input);
+ }
+ return erode(input, structuringElement);
+ }
+ }
+
+ /**
+ * Binary morphological dilation operation.
+ */
+ public static class Dilate extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return dilate(input);
+ }
+ return dilate(input, structuringElement);
+ }
+ }
+
+ /**
+ * Binary morphological open operation.
+ */
+ public static class Open extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return open(input);
+ }
+ return open(input, structuringElement);
+ }
+ }
+
+ /**
+ * Binary morphological close operation.
+ */
+ public static class Close extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return close(input);
+ }
+ return close(input, structuringElement);
+ }
+ }
+
+ /**
+ * Binary morphological smooth operation.
+ */
+ public static class Smooth extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return smooth(input);
+ }
+ return smooth(input, structuringElement);
+ }
+ }
+
+ /**
+ * Binary morphological edge operation.
+ */
+ public static class Edge extends MorphOp {
+
+ @Override
+ protected ByteMatrix processImage(ByteMatrix input) {
+ if (structuringElement == null) {
+ return edge(input);
+ }
+ return edge(input, structuringElement);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.image.colormap;
+
+import java.io.Serializable;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Base class for color maps.
+ *
+ * @author Kevin McGuinness
+ */
+public class ColorMap implements Serializable {
+
+ /**
+ * Serialization UID
+ */
+ private static final long serialVersionUID = 3514876556837016224L;
+
+ /**
+ * Color look up table. N x 3 components representing the
+ * RGB values.
+ */
+ protected final float[][] lut;
+
+ /**
+ * Create a color map of the given size.
+ */
+ protected ColorMap(int size) {
+ this.lut = new float[size][3];
+ }
+
+ /**
+ * Create a color map from the given look up table.
+ */
+ public ColorMap(float[][] lut) {
+ if (lut == null) {
+ throw new NullPointerException("lut == null");
+ }
+
+ for (int i = 0; i < lut.length; i++) {
+ if (lut[i].length != 3) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ this.lut = lut;
+ }
+
+ public int size() {
+ return lut.length;
+ }
+
+ public final float red(int index) {
+ return lut[index][0];
+ }
+
+ public final float green(int index) {
+ return lut[index][1];
+ }
+
+ public final float blue(int index) {
+ return lut[index][2];
+ }
+
+ public final RGB rgb(int index) {
+ int r = (int) (lut[index][0] * 255);
+ int g = (int) (lut[index][1] * 255);
+ int b = (int) (lut[index][2] * 255);
+ return new RGB(r, g, b);
+ }
+
+ public PaletteData createPaletteData() {
+ RGB[] palette = new RGB[lut.length];
+ for (int i = 0; i < lut.length; i++) {
+ int r = (int) (lut[i][0] * 255);
+ int g = (int) (lut[i][1] * 255);
+ int b = (int) (lut[i][2] * 255);
+ palette[i] = new RGB(r, g, b);
+ }
+ return new PaletteData(palette);
+ }
+
+}
--- /dev/null
+package ie.dcu.image.colormap;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A set of predefined color maps.
+ *
+ * @author Kevin McGuinness
+ */
+public final class ColorMaps {
+
+ /**
+ * Specification for the "binary" color map.
+ */
+ private static final float[][][] SPEC_BINARY = {
+ { // R
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // G
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // B
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "grey" color map.
+ */
+ private static final float[][][] SPEC_GREY = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } }
+ };
+
+ /**
+ * Specification for the "autumn" color map.
+ */
+ private static final float[][][] SPEC_AUTUMN = {
+ { // R
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "bone" color map.
+ */
+ private static final float[][][] SPEC_BONE = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.746032f, 0.652778f, 0.652778f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.365079f, 0.319444f, 0.319444f },
+ { 0.746032f, 0.777778f, 0.777778f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.365079f, 0.444444f, 0.444444f },
+ { 1.000000f, 1.000000f, 1.000000f } }
+ };
+
+ /**
+ * Specification for the "cool" color map.
+ */
+ private static final float[][][] SPEC_COOL = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // B
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } }
+ };
+
+ /**
+ * Specification for the "copper" color map.
+ */
+ private static final float[][][] SPEC_COPPER = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.809524f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.781200f, 0.781200f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.497500f, 0.497500f } }
+ };
+
+ /**
+ * Specification for the "flag" color map.
+ */
+ private static final float[][][] SPEC_FLAG = {
+ { // R
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 0.015873f, 1.000000f, 1.000000f },
+ { 0.031746f, 0.000000f, 0.000000f },
+ { 0.047619f, 0.000000f, 0.000000f },
+ { 0.063492f, 1.000000f, 1.000000f },
+ { 0.079365f, 1.000000f, 1.000000f },
+ { 0.095238f, 0.000000f, 0.000000f },
+ { 0.111111f, 0.000000f, 0.000000f },
+ { 0.126984f, 1.000000f, 1.000000f },
+ { 0.142857f, 1.000000f, 1.000000f },
+ { 0.158730f, 0.000000f, 0.000000f },
+ { 0.174603f, 0.000000f, 0.000000f },
+ { 0.190476f, 1.000000f, 1.000000f },
+ { 0.206349f, 1.000000f, 1.000000f },
+ { 0.222222f, 0.000000f, 0.000000f },
+ { 0.238095f, 0.000000f, 0.000000f },
+ { 0.253968f, 1.000000f, 1.000000f },
+ { 0.269841f, 1.000000f, 1.000000f },
+ { 0.285714f, 0.000000f, 0.000000f },
+ { 0.301587f, 0.000000f, 0.000000f },
+ { 0.317460f, 1.000000f, 1.000000f },
+ { 0.333333f, 1.000000f, 1.000000f },
+ { 0.349206f, 0.000000f, 0.000000f },
+ { 0.365079f, 0.000000f, 0.000000f },
+ { 0.380952f, 1.000000f, 1.000000f },
+ { 0.396825f, 1.000000f, 1.000000f },
+ { 0.412698f, 0.000000f, 0.000000f },
+ { 0.428571f, 0.000000f, 0.000000f },
+ { 0.444444f, 1.000000f, 1.000000f },
+ { 0.460317f, 1.000000f, 1.000000f },
+ { 0.476190f, 0.000000f, 0.000000f },
+ { 0.492063f, 0.000000f, 0.000000f },
+ { 0.507937f, 1.000000f, 1.000000f },
+ { 0.523810f, 1.000000f, 1.000000f },
+ { 0.539683f, 0.000000f, 0.000000f },
+ { 0.555556f, 0.000000f, 0.000000f },
+ { 0.571429f, 1.000000f, 1.000000f },
+ { 0.587302f, 1.000000f, 1.000000f },
+ { 0.603175f, 0.000000f, 0.000000f },
+ { 0.619048f, 0.000000f, 0.000000f },
+ { 0.634921f, 1.000000f, 1.000000f },
+ { 0.650794f, 1.000000f, 1.000000f },
+ { 0.666667f, 0.000000f, 0.000000f },
+ { 0.682540f, 0.000000f, 0.000000f },
+ { 0.698413f, 1.000000f, 1.000000f },
+ { 0.714286f, 1.000000f, 1.000000f },
+ { 0.730159f, 0.000000f, 0.000000f },
+ { 0.746032f, 0.000000f, 0.000000f },
+ { 0.761905f, 1.000000f, 1.000000f },
+ { 0.777778f, 1.000000f, 1.000000f },
+ { 0.793651f, 0.000000f, 0.000000f },
+ { 0.809524f, 0.000000f, 0.000000f },
+ { 0.825397f, 1.000000f, 1.000000f },
+ { 0.841270f, 1.000000f, 1.000000f },
+ { 0.857143f, 0.000000f, 0.000000f },
+ { 0.873016f, 0.000000f, 0.000000f },
+ { 0.888889f, 1.000000f, 1.000000f },
+ { 0.904762f, 1.000000f, 1.000000f },
+ { 0.920635f, 0.000000f, 0.000000f },
+ { 0.936508f, 0.000000f, 0.000000f },
+ { 0.952381f, 1.000000f, 1.000000f },
+ { 0.968254f, 1.000000f, 1.000000f },
+ { 0.984127f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.015873f, 1.000000f, 1.000000f },
+ { 0.031746f, 0.000000f, 0.000000f },
+ { 0.063492f, 0.000000f, 0.000000f },
+ { 0.079365f, 1.000000f, 1.000000f },
+ { 0.095238f, 0.000000f, 0.000000f },
+ { 0.126984f, 0.000000f, 0.000000f },
+ { 0.142857f, 1.000000f, 1.000000f },
+ { 0.158730f, 0.000000f, 0.000000f },
+ { 0.190476f, 0.000000f, 0.000000f },
+ { 0.206349f, 1.000000f, 1.000000f },
+ { 0.222222f, 0.000000f, 0.000000f },
+ { 0.253968f, 0.000000f, 0.000000f },
+ { 0.269841f, 1.000000f, 1.000000f },
+ { 0.285714f, 0.000000f, 0.000000f },
+ { 0.317460f, 0.000000f, 0.000000f },
+ { 0.333333f, 1.000000f, 1.000000f },
+ { 0.349206f, 0.000000f, 0.000000f },
+ { 0.380952f, 0.000000f, 0.000000f },
+ { 0.396825f, 1.000000f, 1.000000f },
+ { 0.412698f, 0.000000f, 0.000000f },
+ { 0.444444f, 0.000000f, 0.000000f },
+ { 0.460317f, 1.000000f, 1.000000f },
+ { 0.476190f, 0.000000f, 0.000000f },
+ { 0.507937f, 0.000000f, 0.000000f },
+ { 0.523810f, 1.000000f, 1.000000f },
+ { 0.539683f, 0.000000f, 0.000000f },
+ { 0.571429f, 0.000000f, 0.000000f },
+ { 0.587302f, 1.000000f, 1.000000f },
+ { 0.603175f, 0.000000f, 0.000000f },
+ { 0.634921f, 0.000000f, 0.000000f },
+ { 0.650794f, 1.000000f, 1.000000f },
+ { 0.666667f, 0.000000f, 0.000000f },
+ { 0.698413f, 0.000000f, 0.000000f },
+ { 0.714286f, 1.000000f, 1.000000f },
+ { 0.730159f, 0.000000f, 0.000000f },
+ { 0.761905f, 0.000000f, 0.000000f },
+ { 0.777778f, 1.000000f, 1.000000f },
+ { 0.793651f, 0.000000f, 0.000000f },
+ { 0.825397f, 0.000000f, 0.000000f },
+ { 0.841270f, 1.000000f, 1.000000f },
+ { 0.857143f, 0.000000f, 0.000000f },
+ { 0.888889f, 0.000000f, 0.000000f },
+ { 0.904762f, 1.000000f, 1.000000f },
+ { 0.920635f, 0.000000f, 0.000000f },
+ { 0.952381f, 0.000000f, 0.000000f },
+ { 0.968254f, 1.000000f, 1.000000f },
+ { 0.984127f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.015873f, 1.000000f, 1.000000f },
+ { 0.031746f, 1.000000f, 1.000000f },
+ { 0.047619f, 0.000000f, 0.000000f },
+ { 0.063492f, 0.000000f, 0.000000f },
+ { 0.079365f, 1.000000f, 1.000000f },
+ { 0.095238f, 1.000000f, 1.000000f },
+ { 0.111111f, 0.000000f, 0.000000f },
+ { 0.126984f, 0.000000f, 0.000000f },
+ { 0.142857f, 1.000000f, 1.000000f },
+ { 0.158730f, 1.000000f, 1.000000f },
+ { 0.174603f, 0.000000f, 0.000000f },
+ { 0.190476f, 0.000000f, 0.000000f },
+ { 0.206349f, 1.000000f, 1.000000f },
+ { 0.222222f, 1.000000f, 1.000000f },
+ { 0.238095f, 0.000000f, 0.000000f },
+ { 0.253968f, 0.000000f, 0.000000f },
+ { 0.269841f, 1.000000f, 1.000000f },
+ { 0.285714f, 1.000000f, 1.000000f },
+ { 0.301587f, 0.000000f, 0.000000f },
+ { 0.317460f, 0.000000f, 0.000000f },
+ { 0.333333f, 1.000000f, 1.000000f },
+ { 0.349206f, 1.000000f, 1.000000f },
+ { 0.365079f, 0.000000f, 0.000000f },
+ { 0.380952f, 0.000000f, 0.000000f },
+ { 0.396825f, 1.000000f, 1.000000f },
+ { 0.412698f, 1.000000f, 1.000000f },
+ { 0.428571f, 0.000000f, 0.000000f },
+ { 0.444444f, 0.000000f, 0.000000f },
+ { 0.460317f, 1.000000f, 1.000000f },
+ { 0.476190f, 1.000000f, 1.000000f },
+ { 0.492063f, 0.000000f, 0.000000f },
+ { 0.507937f, 0.000000f, 0.000000f },
+ { 0.523810f, 1.000000f, 1.000000f },
+ { 0.539683f, 1.000000f, 1.000000f },
+ { 0.555556f, 0.000000f, 0.000000f },
+ { 0.571429f, 0.000000f, 0.000000f },
+ { 0.587302f, 1.000000f, 1.000000f },
+ { 0.603175f, 1.000000f, 1.000000f },
+ { 0.619048f, 0.000000f, 0.000000f },
+ { 0.634921f, 0.000000f, 0.000000f },
+ { 0.650794f, 1.000000f, 1.000000f },
+ { 0.666667f, 1.000000f, 1.000000f },
+ { 0.682540f, 0.000000f, 0.000000f },
+ { 0.698413f, 0.000000f, 0.000000f },
+ { 0.714286f, 1.000000f, 1.000000f },
+ { 0.730159f, 1.000000f, 1.000000f },
+ { 0.746032f, 0.000000f, 0.000000f },
+ { 0.761905f, 0.000000f, 0.000000f },
+ { 0.777778f, 1.000000f, 1.000000f },
+ { 0.793651f, 1.000000f, 1.000000f },
+ { 0.809524f, 0.000000f, 0.000000f },
+ { 0.825397f, 0.000000f, 0.000000f },
+ { 0.841270f, 1.000000f, 1.000000f },
+ { 0.857143f, 1.000000f, 1.000000f },
+ { 0.873016f, 0.000000f, 0.000000f },
+ { 0.888889f, 0.000000f, 0.000000f },
+ { 0.904762f, 1.000000f, 1.000000f },
+ { 0.920635f, 1.000000f, 1.000000f },
+ { 0.936508f, 0.000000f, 0.000000f },
+ { 0.952381f, 0.000000f, 0.000000f },
+ { 0.968254f, 1.000000f, 1.000000f },
+ { 0.984127f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "hot" color map.
+ */
+ private static final float[][][] SPEC_HOT = {
+ { // R
+ { 0.000000f, 0.041600f, 0.041600f },
+ { 0.365079f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.365079f, 0.000000f, 0.000000f },
+ { 0.746032f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.746032f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } }
+ };
+
+ /**
+ * Specification for the "jet" color map.
+ */
+ private static final float[][][] SPEC_JET = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.350000f, 0.000000f, 0.000000f },
+ { 0.660000f, 1.000000f, 1.000000f },
+ { 0.890000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.500000f, 0.500000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.125000f, 0.000000f, 0.000000f },
+ { 0.375000f, 1.000000f, 1.000000f },
+ { 0.640000f, 1.000000f, 1.000000f },
+ { 0.910000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // B
+ { 0.000000f, 0.500000f, 0.500000f },
+ { 0.110000f, 1.000000f, 1.000000f },
+ { 0.340000f, 1.000000f, 1.000000f },
+ { 0.650000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "pink" color map.
+ */
+ private static final float[][][] SPEC_PINK = {
+ { // R
+ { 0.000000f, 0.117800f, 0.117800f },
+ { 0.015873f, 0.195857f, 0.195857f },
+ { 0.031746f, 0.250661f, 0.250661f },
+ { 0.047619f, 0.295468f, 0.295468f },
+ { 0.063492f, 0.334324f, 0.334324f },
+ { 0.079365f, 0.369112f, 0.369112f },
+ { 0.095238f, 0.400892f, 0.400892f },
+ { 0.111111f, 0.430331f, 0.430331f },
+ { 0.126984f, 0.457882f, 0.457882f },
+ { 0.142857f, 0.483867f, 0.483867f },
+ { 0.158730f, 0.508525f, 0.508525f },
+ { 0.174603f, 0.532042f, 0.532042f },
+ { 0.190476f, 0.554563f, 0.554563f },
+ { 0.206349f, 0.576204f, 0.576204f },
+ { 0.222222f, 0.597061f, 0.597061f },
+ { 0.238095f, 0.617213f, 0.617213f },
+ { 0.253968f, 0.636729f, 0.636729f },
+ { 0.269841f, 0.655663f, 0.655663f },
+ { 0.285714f, 0.674066f, 0.674066f },
+ { 0.301587f, 0.691980f, 0.691980f },
+ { 0.317460f, 0.709441f, 0.709441f },
+ { 0.333333f, 0.726483f, 0.726483f },
+ { 0.349206f, 0.743134f, 0.743134f },
+ { 0.365079f, 0.759421f, 0.759421f },
+ { 0.380952f, 0.766356f, 0.766356f },
+ { 0.396825f, 0.773229f, 0.773229f },
+ { 0.412698f, 0.780042f, 0.780042f },
+ { 0.428571f, 0.786796f, 0.786796f },
+ { 0.444444f, 0.793492f, 0.793492f },
+ { 0.460317f, 0.800132f, 0.800132f },
+ { 0.476190f, 0.806718f, 0.806718f },
+ { 0.492063f, 0.813250f, 0.813250f },
+ { 0.507937f, 0.819730f, 0.819730f },
+ { 0.523810f, 0.826160f, 0.826160f },
+ { 0.539683f, 0.832539f, 0.832539f },
+ { 0.555556f, 0.838870f, 0.838870f },
+ { 0.571429f, 0.845154f, 0.845154f },
+ { 0.587302f, 0.851392f, 0.851392f },
+ { 0.603175f, 0.857584f, 0.857584f },
+ { 0.619048f, 0.863731f, 0.863731f },
+ { 0.634921f, 0.869835f, 0.869835f },
+ { 0.650794f, 0.875897f, 0.875897f },
+ { 0.666667f, 0.881917f, 0.881917f },
+ { 0.682540f, 0.887896f, 0.887896f },
+ { 0.698413f, 0.893835f, 0.893835f },
+ { 0.714286f, 0.899735f, 0.899735f },
+ { 0.730159f, 0.905597f, 0.905597f },
+ { 0.746032f, 0.911421f, 0.911421f },
+ { 0.761905f, 0.917208f, 0.917208f },
+ { 0.777778f, 0.922958f, 0.922958f },
+ { 0.793651f, 0.928673f, 0.928673f },
+ { 0.809524f, 0.934353f, 0.934353f },
+ { 0.825397f, 0.939999f, 0.939999f },
+ { 0.841270f, 0.945611f, 0.945611f },
+ { 0.857143f, 0.951190f, 0.951190f },
+ { 0.873016f, 0.956736f, 0.956736f },
+ { 0.888889f, 0.962250f, 0.962250f },
+ { 0.904762f, 0.967733f, 0.967733f },
+ { 0.920635f, 0.973185f, 0.973185f },
+ { 0.936508f, 0.978607f, 0.978607f },
+ { 0.952381f, 0.983999f, 0.983999f },
+ { 0.968254f, 0.989361f, 0.989361f },
+ { 0.984127f, 0.994695f, 0.994695f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.015873f, 0.102869f, 0.102869f },
+ { 0.031746f, 0.145479f, 0.145479f },
+ { 0.047619f, 0.178174f, 0.178174f },
+ { 0.063492f, 0.205738f, 0.205738f },
+ { 0.079365f, 0.230022f, 0.230022f },
+ { 0.095238f, 0.251976f, 0.251976f },
+ { 0.111111f, 0.272166f, 0.272166f },
+ { 0.126984f, 0.290957f, 0.290957f },
+ { 0.142857f, 0.308607f, 0.308607f },
+ { 0.158730f, 0.325300f, 0.325300f },
+ { 0.174603f, 0.341178f, 0.341178f },
+ { 0.190476f, 0.356348f, 0.356348f },
+ { 0.206349f, 0.370899f, 0.370899f },
+ { 0.222222f, 0.384900f, 0.384900f },
+ { 0.238095f, 0.398410f, 0.398410f },
+ { 0.253968f, 0.411476f, 0.411476f },
+ { 0.269841f, 0.424139f, 0.424139f },
+ { 0.285714f, 0.436436f, 0.436436f },
+ { 0.301587f, 0.448395f, 0.448395f },
+ { 0.317460f, 0.460044f, 0.460044f },
+ { 0.333333f, 0.471405f, 0.471405f },
+ { 0.349206f, 0.482498f, 0.482498f },
+ { 0.365079f, 0.493342f, 0.493342f },
+ { 0.380952f, 0.517549f, 0.517549f },
+ { 0.396825f, 0.540674f, 0.540674f },
+ { 0.412698f, 0.562849f, 0.562849f },
+ { 0.428571f, 0.584183f, 0.584183f },
+ { 0.444444f, 0.604765f, 0.604765f },
+ { 0.460317f, 0.624669f, 0.624669f },
+ { 0.476190f, 0.643958f, 0.643958f },
+ { 0.492063f, 0.662687f, 0.662687f },
+ { 0.507937f, 0.680900f, 0.680900f },
+ { 0.523810f, 0.698638f, 0.698638f },
+ { 0.539683f, 0.715937f, 0.715937f },
+ { 0.555556f, 0.732828f, 0.732828f },
+ { 0.571429f, 0.749338f, 0.749338f },
+ { 0.587302f, 0.765493f, 0.765493f },
+ { 0.603175f, 0.781313f, 0.781313f },
+ { 0.619048f, 0.796819f, 0.796819f },
+ { 0.634921f, 0.812029f, 0.812029f },
+ { 0.650794f, 0.826960f, 0.826960f },
+ { 0.666667f, 0.841625f, 0.841625f },
+ { 0.682540f, 0.856040f, 0.856040f },
+ { 0.698413f, 0.870216f, 0.870216f },
+ { 0.714286f, 0.884164f, 0.884164f },
+ { 0.730159f, 0.897896f, 0.897896f },
+ { 0.746032f, 0.911421f, 0.911421f },
+ { 0.761905f, 0.917208f, 0.917208f },
+ { 0.777778f, 0.922958f, 0.922958f },
+ { 0.793651f, 0.928673f, 0.928673f },
+ { 0.809524f, 0.934353f, 0.934353f },
+ { 0.825397f, 0.939999f, 0.939999f },
+ { 0.841270f, 0.945611f, 0.945611f },
+ { 0.857143f, 0.951190f, 0.951190f },
+ { 0.873016f, 0.956736f, 0.956736f },
+ { 0.888889f, 0.962250f, 0.962250f },
+ { 0.904762f, 0.967733f, 0.967733f },
+ { 0.920635f, 0.973185f, 0.973185f },
+ { 0.936508f, 0.978607f, 0.978607f },
+ { 0.952381f, 0.983999f, 0.983999f },
+ { 0.968254f, 0.989361f, 0.989361f },
+ { 0.984127f, 0.994695f, 0.994695f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.015873f, 0.102869f, 0.102869f },
+ { 0.031746f, 0.145479f, 0.145479f },
+ { 0.047619f, 0.178174f, 0.178174f },
+ { 0.063492f, 0.205738f, 0.205738f },
+ { 0.079365f, 0.230022f, 0.230022f },
+ { 0.095238f, 0.251976f, 0.251976f },
+ { 0.111111f, 0.272166f, 0.272166f },
+ { 0.126984f, 0.290957f, 0.290957f },
+ { 0.142857f, 0.308607f, 0.308607f },
+ { 0.158730f, 0.325300f, 0.325300f },
+ { 0.174603f, 0.341178f, 0.341178f },
+ { 0.190476f, 0.356348f, 0.356348f },
+ { 0.206349f, 0.370899f, 0.370899f },
+ { 0.222222f, 0.384900f, 0.384900f },
+ { 0.238095f, 0.398410f, 0.398410f },
+ { 0.253968f, 0.411476f, 0.411476f },
+ { 0.269841f, 0.424139f, 0.424139f },
+ { 0.285714f, 0.436436f, 0.436436f },
+ { 0.301587f, 0.448395f, 0.448395f },
+ { 0.317460f, 0.460044f, 0.460044f },
+ { 0.333333f, 0.471405f, 0.471405f },
+ { 0.349206f, 0.482498f, 0.482498f },
+ { 0.365079f, 0.493342f, 0.493342f },
+ { 0.380952f, 0.503953f, 0.503953f },
+ { 0.396825f, 0.514344f, 0.514344f },
+ { 0.412698f, 0.524531f, 0.524531f },
+ { 0.428571f, 0.534522f, 0.534522f },
+ { 0.444444f, 0.544331f, 0.544331f },
+ { 0.460317f, 0.553966f, 0.553966f },
+ { 0.476190f, 0.563436f, 0.563436f },
+ { 0.492063f, 0.572750f, 0.572750f },
+ { 0.507937f, 0.581914f, 0.581914f },
+ { 0.523810f, 0.590937f, 0.590937f },
+ { 0.539683f, 0.599824f, 0.599824f },
+ { 0.555556f, 0.608581f, 0.608581f },
+ { 0.571429f, 0.617213f, 0.617213f },
+ { 0.587302f, 0.625727f, 0.625727f },
+ { 0.603175f, 0.634126f, 0.634126f },
+ { 0.619048f, 0.642416f, 0.642416f },
+ { 0.634921f, 0.650600f, 0.650600f },
+ { 0.650794f, 0.658682f, 0.658682f },
+ { 0.666667f, 0.666667f, 0.666667f },
+ { 0.682540f, 0.674556f, 0.674556f },
+ { 0.698413f, 0.682355f, 0.682355f },
+ { 0.714286f, 0.690066f, 0.690066f },
+ { 0.730159f, 0.697691f, 0.697691f },
+ { 0.746032f, 0.705234f, 0.705234f },
+ { 0.761905f, 0.727166f, 0.727166f },
+ { 0.777778f, 0.748455f, 0.748455f },
+ { 0.793651f, 0.769156f, 0.769156f },
+ { 0.809524f, 0.789314f, 0.789314f },
+ { 0.825397f, 0.808969f, 0.808969f },
+ { 0.841270f, 0.828159f, 0.828159f },
+ { 0.857143f, 0.846913f, 0.846913f },
+ { 0.873016f, 0.865261f, 0.865261f },
+ { 0.888889f, 0.883229f, 0.883229f },
+ { 0.904762f, 0.900837f, 0.900837f },
+ { 0.920635f, 0.918109f, 0.918109f },
+ { 0.936508f, 0.935061f, 0.935061f },
+ { 0.952381f, 0.951711f, 0.951711f },
+ { 0.968254f, 0.968075f, 0.968075f },
+ { 0.984127f, 0.984167f, 0.984167f },
+ { 1.000000f, 1.000000f, 1.000000f } }
+ };
+
+ /**
+ * Specification for the "prism" color map.
+ */
+ private static final float[][][] SPEC_PRISM = {
+ { // R
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 0.031746f, 1.000000f, 1.000000f },
+ { 0.047619f, 0.000000f, 0.000000f },
+ { 0.063492f, 0.000000f, 0.000000f },
+ { 0.079365f, 0.666667f, 0.666667f },
+ { 0.095238f, 1.000000f, 1.000000f },
+ { 0.126984f, 1.000000f, 1.000000f },
+ { 0.142857f, 0.000000f, 0.000000f },
+ { 0.158730f, 0.000000f, 0.000000f },
+ { 0.174603f, 0.666667f, 0.666667f },
+ { 0.190476f, 1.000000f, 1.000000f },
+ { 0.222222f, 1.000000f, 1.000000f },
+ { 0.238095f, 0.000000f, 0.000000f },
+ { 0.253968f, 0.000000f, 0.000000f },
+ { 0.269841f, 0.666667f, 0.666667f },
+ { 0.285714f, 1.000000f, 1.000000f },
+ { 0.317460f, 1.000000f, 1.000000f },
+ { 0.333333f, 0.000000f, 0.000000f },
+ { 0.349206f, 0.000000f, 0.000000f },
+ { 0.365079f, 0.666667f, 0.666667f },
+ { 0.380952f, 1.000000f, 1.000000f },
+ { 0.412698f, 1.000000f, 1.000000f },
+ { 0.428571f, 0.000000f, 0.000000f },
+ { 0.444444f, 0.000000f, 0.000000f },
+ { 0.460317f, 0.666667f, 0.666667f },
+ { 0.476190f, 1.000000f, 1.000000f },
+ { 0.507937f, 1.000000f, 1.000000f },
+ { 0.523810f, 0.000000f, 0.000000f },
+ { 0.539683f, 0.000000f, 0.000000f },
+ { 0.555556f, 0.666667f, 0.666667f },
+ { 0.571429f, 1.000000f, 1.000000f },
+ { 0.603175f, 1.000000f, 1.000000f },
+ { 0.619048f, 0.000000f, 0.000000f },
+ { 0.634921f, 0.000000f, 0.000000f },
+ { 0.650794f, 0.666667f, 0.666667f },
+ { 0.666667f, 1.000000f, 1.000000f },
+ { 0.698413f, 1.000000f, 1.000000f },
+ { 0.714286f, 0.000000f, 0.000000f },
+ { 0.730159f, 0.000000f, 0.000000f },
+ { 0.746032f, 0.666667f, 0.666667f },
+ { 0.761905f, 1.000000f, 1.000000f },
+ { 0.793651f, 1.000000f, 1.000000f },
+ { 0.809524f, 0.000000f, 0.000000f },
+ { 0.825397f, 0.000000f, 0.000000f },
+ { 0.841270f, 0.666667f, 0.666667f },
+ { 0.857143f, 1.000000f, 1.000000f },
+ { 0.888889f, 1.000000f, 1.000000f },
+ { 0.904762f, 0.000000f, 0.000000f },
+ { 0.920635f, 0.000000f, 0.000000f },
+ { 0.936508f, 0.666667f, 0.666667f },
+ { 0.952381f, 1.000000f, 1.000000f },
+ { 0.984127f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.031746f, 1.000000f, 1.000000f },
+ { 0.047619f, 1.000000f, 1.000000f },
+ { 0.063492f, 0.000000f, 0.000000f },
+ { 0.095238f, 0.000000f, 0.000000f },
+ { 0.126984f, 1.000000f, 1.000000f },
+ { 0.142857f, 1.000000f, 1.000000f },
+ { 0.158730f, 0.000000f, 0.000000f },
+ { 0.190476f, 0.000000f, 0.000000f },
+ { 0.222222f, 1.000000f, 1.000000f },
+ { 0.238095f, 1.000000f, 1.000000f },
+ { 0.253968f, 0.000000f, 0.000000f },
+ { 0.285714f, 0.000000f, 0.000000f },
+ { 0.317460f, 1.000000f, 1.000000f },
+ { 0.333333f, 1.000000f, 1.000000f },
+ { 0.349206f, 0.000000f, 0.000000f },
+ { 0.380952f, 0.000000f, 0.000000f },
+ { 0.412698f, 1.000000f, 1.000000f },
+ { 0.428571f, 1.000000f, 1.000000f },
+ { 0.444444f, 0.000000f, 0.000000f },
+ { 0.476190f, 0.000000f, 0.000000f },
+ { 0.507937f, 1.000000f, 1.000000f },
+ { 0.523810f, 1.000000f, 1.000000f },
+ { 0.539683f, 0.000000f, 0.000000f },
+ { 0.571429f, 0.000000f, 0.000000f },
+ { 0.603175f, 1.000000f, 1.000000f },
+ { 0.619048f, 1.000000f, 1.000000f },
+ { 0.634921f, 0.000000f, 0.000000f },
+ { 0.666667f, 0.000000f, 0.000000f },
+ { 0.698413f, 1.000000f, 1.000000f },
+ { 0.714286f, 1.000000f, 1.000000f },
+ { 0.730159f, 0.000000f, 0.000000f },
+ { 0.761905f, 0.000000f, 0.000000f },
+ { 0.793651f, 1.000000f, 1.000000f },
+ { 0.809524f, 1.000000f, 1.000000f },
+ { 0.825397f, 0.000000f, 0.000000f },
+ { 0.857143f, 0.000000f, 0.000000f },
+ { 0.888889f, 1.000000f, 1.000000f },
+ { 0.904762f, 1.000000f, 1.000000f },
+ { 0.920635f, 0.000000f, 0.000000f },
+ { 0.952381f, 0.000000f, 0.000000f },
+ { 0.984127f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.047619f, 0.000000f, 0.000000f },
+ { 0.063492f, 1.000000f, 1.000000f },
+ { 0.079365f, 1.000000f, 1.000000f },
+ { 0.095238f, 0.000000f, 0.000000f },
+ { 0.142857f, 0.000000f, 0.000000f },
+ { 0.158730f, 1.000000f, 1.000000f },
+ { 0.174603f, 1.000000f, 1.000000f },
+ { 0.190476f, 0.000000f, 0.000000f },
+ { 0.238095f, 0.000000f, 0.000000f },
+ { 0.253968f, 1.000000f, 1.000000f },
+ { 0.269841f, 1.000000f, 1.000000f },
+ { 0.285714f, 0.000000f, 0.000000f },
+ { 0.333333f, 0.000000f, 0.000000f },
+ { 0.349206f, 1.000000f, 1.000000f },
+ { 0.365079f, 1.000000f, 1.000000f },
+ { 0.380952f, 0.000000f, 0.000000f },
+ { 0.428571f, 0.000000f, 0.000000f },
+ { 0.444444f, 1.000000f, 1.000000f },
+ { 0.460317f, 1.000000f, 1.000000f },
+ { 0.476190f, 0.000000f, 0.000000f },
+ { 0.523810f, 0.000000f, 0.000000f },
+ { 0.539683f, 1.000000f, 1.000000f },
+ { 0.555556f, 1.000000f, 1.000000f },
+ { 0.571429f, 0.000000f, 0.000000f },
+ { 0.619048f, 0.000000f, 0.000000f },
+ { 0.634921f, 1.000000f, 1.000000f },
+ { 0.650794f, 1.000000f, 1.000000f },
+ { 0.666667f, 0.000000f, 0.000000f },
+ { 0.714286f, 0.000000f, 0.000000f },
+ { 0.730159f, 1.000000f, 1.000000f },
+ { 0.746032f, 1.000000f, 1.000000f },
+ { 0.761905f, 0.000000f, 0.000000f },
+ { 0.809524f, 0.000000f, 0.000000f },
+ { 0.825397f, 1.000000f, 1.000000f },
+ { 0.841270f, 1.000000f, 1.000000f },
+ { 0.857143f, 0.000000f, 0.000000f },
+ { 0.904762f, 0.000000f, 0.000000f },
+ { 0.920635f, 1.000000f, 1.000000f },
+ { 0.936508f, 1.000000f, 1.000000f },
+ { 0.952381f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "spring" color map.
+ */
+ private static final float[][][] SPEC_SPRING = {
+ { // R
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } }
+ };
+
+ /**
+ * Specification for the "summer" color map.
+ */
+ private static final float[][][] SPEC_SUMMER = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // G
+ { 0.000000f, 0.500000f, 0.500000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 0.400000f, 0.400000f },
+ { 1.000000f, 0.400000f, 0.400000f } }
+ };
+
+ /**
+ * Specification for the "winter" color map.
+ */
+ private static final float[][][] SPEC_WINTER = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.000000f, 0.000000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 1.000000f, 1.000000f, 1.000000f } },
+ { // B
+ { 0.000000f, 1.000000f, 1.000000f },
+ { 1.000000f, 0.500000f, 0.500000f } }
+ };
+
+ /**
+ * Specification for the "spectral" color map.
+ */
+ private static final float[][][] SPEC_SPECTRAL = {
+ { // R
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.050000f, 0.466700f, 0.466700f },
+ { 0.100000f, 0.533300f, 0.533300f },
+ { 0.150000f, 0.000000f, 0.000000f },
+ { 0.200000f, 0.000000f, 0.000000f },
+ { 0.250000f, 0.000000f, 0.000000f },
+ { 0.300000f, 0.000000f, 0.000000f },
+ { 0.350000f, 0.000000f, 0.000000f },
+ { 0.400000f, 0.000000f, 0.000000f },
+ { 0.450000f, 0.000000f, 0.000000f },
+ { 0.500000f, 0.000000f, 0.000000f },
+ { 0.550000f, 0.000000f, 0.000000f },
+ { 0.600000f, 0.000000f, 0.000000f },
+ { 0.650000f, 0.733300f, 0.733300f },
+ { 0.700000f, 0.933300f, 0.933300f },
+ { 0.750000f, 1.000000f, 1.000000f },
+ { 0.800000f, 1.000000f, 1.000000f },
+ { 0.850000f, 1.000000f, 1.000000f },
+ { 0.900000f, 0.866700f, 0.866700f },
+ { 0.950000f, 0.800000f, 0.800000f },
+ { 1.000000f, 0.800000f, 0.800000f } },
+ { // G
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.050000f, 0.000000f, 0.000000f },
+ { 0.100000f, 0.000000f, 0.000000f },
+ { 0.150000f, 0.000000f, 0.000000f },
+ { 0.200000f, 0.000000f, 0.000000f },
+ { 0.250000f, 0.466700f, 0.466700f },
+ { 0.300000f, 0.600000f, 0.600000f },
+ { 0.350000f, 0.666700f, 0.666700f },
+ { 0.400000f, 0.666700f, 0.666700f },
+ { 0.450000f, 0.600000f, 0.600000f },
+ { 0.500000f, 0.733300f, 0.733300f },
+ { 0.550000f, 0.866700f, 0.866700f },
+ { 0.600000f, 1.000000f, 1.000000f },
+ { 0.650000f, 1.000000f, 1.000000f },
+ { 0.700000f, 0.933300f, 0.933300f },
+ { 0.750000f, 0.800000f, 0.800000f },
+ { 0.800000f, 0.600000f, 0.600000f },
+ { 0.850000f, 0.000000f, 0.000000f },
+ { 0.900000f, 0.000000f, 0.000000f },
+ { 0.950000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.800000f, 0.800000f } },
+ { // B
+ { 0.000000f, 0.000000f, 0.000000f },
+ { 0.050000f, 0.533300f, 0.533300f },
+ { 0.100000f, 0.600000f, 0.600000f },
+ { 0.150000f, 0.666700f, 0.666700f },
+ { 0.200000f, 0.866700f, 0.866700f },
+ { 0.250000f, 0.866700f, 0.866700f },
+ { 0.300000f, 0.866700f, 0.866700f },
+ { 0.350000f, 0.666700f, 0.666700f },
+ { 0.400000f, 0.533300f, 0.533300f },
+ { 0.450000f, 0.000000f, 0.000000f },
+ { 0.500000f, 0.000000f, 0.000000f },
+ { 0.550000f, 0.000000f, 0.000000f },
+ { 0.600000f, 0.000000f, 0.000000f },
+ { 0.650000f, 0.000000f, 0.000000f },
+ { 0.700000f, 0.000000f, 0.000000f },
+ { 0.750000f, 0.000000f, 0.000000f },
+ { 0.800000f, 0.000000f, 0.000000f },
+ { 0.850000f, 0.000000f, 0.000000f },
+ { 0.900000f, 0.000000f, 0.000000f },
+ { 0.950000f, 0.000000f, 0.000000f },
+ { 1.000000f, 0.800000f, 0.800000f } }
+ };
+
+ private static class SpecCache {
+ final float[][][] spec;
+ ColorMap map;
+
+ SpecCache(float[][][] spec) {
+ this.spec = spec;
+ }
+ };
+
+ private static final Map<String, SpecCache> cache =
+ new HashMap<String, SpecCache>();
+
+ static {
+ cache.put("binary", new SpecCache(SPEC_BINARY));
+ cache.put("grey", new SpecCache(SPEC_GREY));
+ cache.put("autumn", new SpecCache(SPEC_AUTUMN));
+ cache.put("bone", new SpecCache(SPEC_BONE));
+ cache.put("cool", new SpecCache(SPEC_COOL));
+ cache.put("copper", new SpecCache(SPEC_COPPER));
+ cache.put("flag", new SpecCache(SPEC_FLAG));
+ cache.put("hot", new SpecCache(SPEC_HOT));
+ cache.put("jet", new SpecCache(SPEC_JET));
+ cache.put("pink", new SpecCache(SPEC_PINK));
+ cache.put("prism", new SpecCache(SPEC_PRISM));
+ cache.put("spring", new SpecCache(SPEC_SPRING));
+ cache.put("summer", new SpecCache(SPEC_SUMMER));
+ cache.put("winter", new SpecCache(SPEC_WINTER));
+ cache.put("spectral", new SpecCache(SPEC_SPECTRAL));
+ }
+
+ /**
+ * Size of the predefined maps returned by this class
+ */
+ public static final int MAP_SIZE = 256;
+
+ /**
+ * Returns the (unmodifiable) set of predefined map names.
+ */
+ public static Set<String> names() {
+ return Collections.unmodifiableSet(cache.keySet());
+ }
+
+ /**
+ * Get a color map by name.
+ *
+ * @param name
+ * The color map names
+ * @return The color map.
+ */
+ public static ColorMap get(String name) {
+ if (cache.containsKey(name)) {
+ SpecCache item = cache.get(name);
+ if (item.map == null) {
+ item.map = new LinearColorMap(MAP_SIZE, item.spec);
+ }
+ return item.map;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the "binary" color map
+ */
+ public static ColorMap binary() {
+ return get("binary");
+ }
+
+ /**
+ * Returns the "grey" color map
+ */
+ public static ColorMap grey() {
+ return get("grey");
+ }
+
+ /**
+ * Returns the "autumn" color map
+ */
+ public static ColorMap autumn() {
+ return get("autumn");
+ }
+
+ /**
+ * Returns the "bone" color map
+ */
+ public static ColorMap bone() {
+ return get("bone");
+ }
+
+ /**
+ * Returns the "cool" color map
+ */
+ public static ColorMap cool() {
+ return get("cool");
+ }
+
+ /**
+ * Returns the "copper" color map
+ */
+ public static ColorMap copper() {
+ return get("copper");
+ }
+
+ /**
+ * Returns the "flag" color map
+ */
+ public static ColorMap flag() {
+ return get("flag");
+ }
+
+ /**
+ * Returns the "hot" color map
+ */
+ public static ColorMap hot() {
+ return get("hot");
+ }
+
+ /**
+ * Returns the "jet" color map
+ */
+ public static ColorMap jet() {
+ return get("jet");
+ }
+
+ /**
+ * Returns the "pink" color map
+ */
+ public static ColorMap pink() {
+ return get("pink");
+ }
+
+ /**
+ * Returns the "prism" color map
+ */
+ public static ColorMap prism() {
+ return get("prism");
+ }
+
+ /**
+ * Returns the "spring" color map
+ */
+ public static ColorMap spring() {
+ return get("spring");
+ }
+
+ /**
+ * Returns the "summer" color map
+ */
+ public static ColorMap summer() {
+ return get("summer");
+ }
+
+ /**
+ * Returns the "winter" color map
+ */
+ public static ColorMap winter() {
+ return get("winter");
+ }
+
+ /**
+ * Returns the "spectral" color map
+ */
+ public static ColorMap spectral() {
+ return get("spectral");
+ }
+}
--- /dev/null
+package ie.dcu.image.colormap;
+
+import java.util.Arrays;
+
+/**
+ * A linear segment color map.
+ *
+ * @author Kevin McGuinness
+ */
+public class LinearColorMap extends ColorMap {
+
+ private static final long serialVersionUID = -7024754461124096870L;
+
+ public LinearColorMap(int size,
+ float[][] rspec,
+ float[][] gspec,
+ float[][] bspec)
+ {
+ super(size); // me :-P
+
+ float[] rvals = mapping(size, rspec);
+ for (int i = 0; i < size; i++) {
+ lut[i][0] = rvals[i];
+ }
+
+ float[] gvals = mapping(size, gspec);
+ for (int i = 0; i < size; i++) {
+ lut[i][1] = gvals[i];
+ }
+
+ float[] bvals = mapping(size, bspec);
+ for (int i = 0; i < size; i++) {
+ lut[i][2] = bvals[i];
+ }
+ }
+
+ public LinearColorMap(int size, float[][][] spec) {
+ this(size, spec[0], spec[1], spec[2]);
+ }
+
+ private static float[] mapping(int n, float[][] spec) {
+ final int X = 0;
+ final int Y0 = 1;
+ final int Y1 = 2;
+
+ // Non null
+ if (spec == null) {
+ throw new NullPointerException();
+ }
+
+ // Non empty
+ if (spec.length == 0) {
+ throw new IllegalArgumentException("spec empty");
+ }
+
+ // Number of cols must be 3
+ for (int i = 0; i < spec.length; i++) {
+ if (spec[i].length != 3) {
+ throw new IllegalArgumentException("number of cols must be 3");
+ }
+ }
+
+ // X value must start at zero and end at 1
+ if (spec[0][X] != 0.0 || spec[spec.length-1][X] != 1.0) {
+ throw new IllegalArgumentException("x value must start at zero and end at 1");
+ }
+
+ // X value must be increasing
+ for (int i = 1; i < spec.length; i++) {
+ if (spec[i][X] < spec[i-1][X]) {
+ throw new IllegalArgumentException("x value must be increasing");
+ }
+ }
+
+ float[] lut = new float[n];
+ Arrays.fill(lut, 0.0f);
+
+ lut[0] = clip(spec[0][Y1]);
+
+ // x values by 255
+ float[] x = new float[spec.length];
+ for (int i = 0; i < spec.length; i++) {
+ x[i] = spec[i][X] * (n-1);
+ }
+
+ // Indices
+ int[] idx = new int[n];
+ for (int i = 0, j = 0; i < n; i++) {
+ while (j != x.length && x[j] < i) {
+ j++;
+ }
+ idx[i] = j;
+ }
+
+ for (int i = 1; i < n-1; i++) {
+ float v = ((i - x[idx[i]-1]) / (x[idx[i]] - x[idx[i]-1]))
+ * (spec[idx[i]][Y0] - spec[idx[i]-1][Y1]) + spec[idx[i]-1][Y1];
+
+ lut[i] = clip(v);
+ }
+
+ lut[n-1] = clip(spec[spec.length-1][Y0]);
+
+ return lut;
+ }
+
+ private static final float clip(float v) {
+ return (v < 0) ? 0 : (v > 1 ? 1 : v);
+ }
+}
--- /dev/null
+package ie.dcu.image.dt;
+
+import ie.dcu.matrix.*;
+
+
+/**
+ * Compute distance transforms using Meijster's algorithm:
+ *
+ * For details on the algorithm see:
+ *
+ * <pre>
+ * A General Algorithm for Computing Distance Transforms in Linear Time.
+ * Meijster et al. Computational Imaging and Vision (2000)
+ * </pre>
+ *
+ * Meijster's algorithm appears to be the most efficient exact Euclidean
+ * distance transform algorithm in most situations. For more details see:
+ *
+ * <pre>
+ * 2D Euclidean distance transform algorithms: A comparative survey.
+ * Fabbri et al. ACM Computing Surveys (2008) vol. 40 (1) pp. 1-44
+ * </pre>
+ *
+ * <b>Note:</b> If the matrix given for transform contains no background
+ * pixels, then each pixel will have a value of Integer.MAX_VALUE - rows**2
+ * -cols**2
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public class DistanceTransform {
+ private IntMatrix matrix;
+ private int inf;
+ private boolean done;
+
+ /**
+ * Create distance transform object
+ */
+ public DistanceTransform() {
+ done = false;
+ }
+
+ /**
+ * Returns true if the computation has been done.
+ */
+ public boolean isComputed() {
+ return done;
+ }
+
+ /**
+ * Returns true if the transform has been initialized.
+ */
+ public boolean isInitialized() {
+ return matrix != null;
+ }
+
+
+ /**
+ * Private initializer
+ */
+ private void init(Matrix m) {
+ this.matrix = new IntMatrix(m.rows, m.cols);
+ this.inf = Integer.MAX_VALUE - m.rows * m.rows - m.cols * m.cols;
+ this.done = false;
+ }
+
+ /**
+ * Private routine to check if the transform is possible.
+ */
+ private boolean isTransformPossible() {
+ // Cannot be called when done
+ assert (!done);
+
+ for (int i = 0; i < matrix.size; i++) {
+ if (matrix.values[i] == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Initialize transform with an integer matrix.
+ *
+ * @param m
+ * An integer matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(IntMatrix m, int foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an short matrix.
+ *
+ * @param m
+ * An short matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(ShortMatrix m, short foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an byte matrix.
+ *
+ * @param m
+ * An byte matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(ByteMatrix m, byte foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an long matrix.
+ *
+ * @param m
+ * An long matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(LongMatrix m, long foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an double matrix.
+ *
+ * @param m
+ * An double matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(DoubleMatrix m, double foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an float matrix.
+ *
+ * @param m
+ * An float matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(FloatMatrix m, float foregroundValue) {
+ init(m);
+
+ for (int i = 0; i < m.size; i++) {
+ matrix.values[i] = m.values[i] == foregroundValue ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Initialize transform with an arbitrary matrix.
+ *
+ * @param m
+ * A matrix
+ * @param foregroundValue
+ * The value of a foreground pixel
+ * @return
+ * true if the transform is possible
+ */
+ public boolean init(Matrix m, Number foregroundValue) {
+ init(m);
+
+ double fgv = foregroundValue.doubleValue();
+ for (int i = 0; i < m.size; i++) {
+
+ matrix.values[i] = m.valueAt(i).doubleValue() == fgv ? inf : 0;
+ }
+
+ return isTransformPossible();
+ }
+
+ /**
+ * Performs the column transform
+ */
+ private final void transformCols() {
+
+ for (int j = 0; j < matrix.cols; j++) {
+
+ // Forward
+ for (int i = 1, b = 1; i < matrix.rows; i++) {
+ int idx = i * matrix.cols + j;
+
+ if (matrix.values[idx] > matrix.values[idx-matrix.cols]) {
+ matrix.values[idx] = matrix.values[idx-matrix.cols] + b;
+ b += 2;
+ } else {
+ b = 1;
+ }
+ }
+
+ // Backward
+ for (int i = matrix.rows - 2, b = 1; i >= 0; i--) {
+ int idx = i * matrix.cols + j;
+
+ if (matrix.values[idx] > matrix.values[idx+matrix.cols] + b) {
+ matrix.values[idx] = matrix.values[idx+matrix.cols] + b;
+ b += 2;
+ } else {
+ b = 1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Meijsters f function
+ */
+ private static int f(int x, int i, int pixel) {
+ return (x - i)*(x - i) + pixel;
+ }
+
+ /**
+ * Performs the row transform
+ */
+ private final void transformRows() {
+ // Meijster row transform
+
+ int[] s = new int[matrix.cols];
+ int[] t = new int[matrix.cols];
+ int[] r = new int[matrix.cols];
+
+ for (int i = 0; i < matrix.rows; i++) {
+ int q = s[0] = t[0] = 0;
+ int offset = i * matrix.cols;
+
+ for (int u = 1; u < matrix.cols; ++u) {
+ int im_r_u = matrix.values[offset + u];
+
+ while (q != -1 &&
+ f(t[q], s[q], matrix.values[offset + s[q]]) >
+ f(t[q], u, im_r_u)) {
+ --q;
+ }
+
+ if (q == -1) {
+ q = 0;
+ s[0] = u;
+ } else {
+ int w = 1 + ((u * u - (s[q] * s[q]) +
+ matrix.values[offset + u] - matrix.values[offset + s[q]])
+ / (2 * (u - s[q])));
+ if (w < matrix.cols) {
+ ++q;
+ s[q] = u;
+ t[q] = w;
+ }
+ }
+ }
+
+ System.arraycopy(matrix.values, offset, r, 0, matrix.cols);
+
+ for (int u = matrix.cols - 1; u != -1; --u) {
+ matrix.values[offset + u] = f(u, s[q], r[s[q]]);
+
+ if (u == t[q]) {
+ --q;
+ }
+ }
+ }
+ }
+
+ /**
+ * Carries out the transform if it has not already been completed
+ */
+ private void squareTransform() {
+ if (matrix == null) {
+ throw new IllegalStateException("transform object not initialized");
+ }
+
+ if (!done) {
+ if (matrix.rows > 0 && matrix.cols > 0) {
+ transformCols();
+ transformRows();
+ }
+ done = true;
+ }
+ }
+
+ /**
+ * Compute the square euclidean distance transform and return the result as
+ * a flattened integer array of square distance values, that has a size of
+ * rows * columns.
+ *
+ * @return flat array of square distance values.
+ */
+ public IntMatrix computeSquareTransform() {
+ squareTransform();
+ return matrix.clone();
+ }
+
+ /**
+ * Compute the euclidean distance transform and return the result as a
+ * flattened array of double precision floating point values. The returned
+ * array has a dimension of rows * columns.
+ *
+ * @return flattened array of euclidean distance values.
+ */
+ public DoubleMatrix computeTransform() {
+ squareTransform();
+
+ DoubleMatrix result = new DoubleMatrix(matrix.rows, matrix.cols);
+ for (int i = 0; i < matrix.size; i++) {
+ result.values[i] = Math.sqrt(matrix.values[i]);
+ }
+
+ return result;
+ }
+
+ /**
+ * Compute the euclidean distance transform and return the result as a
+ * flattened array of single precision floating point values. The returned
+ * array has a dimension of rows * columns.
+ *
+ * @return flattened array of euclidean distance values.
+ */
+ public FloatMatrix computeTransformFloat() {
+ squareTransform();
+
+ FloatMatrix result = new FloatMatrix(matrix.rows, matrix.cols);
+ for (int i = 0; i < matrix.size; i++) {
+ result.values[i] = (float) Math.sqrt(matrix.values[i]);
+ }
+
+ return result;
+ }
+
+ // Some test code
+ public static void main(String[] args) {
+
+ // Create rectangular mask
+ int[][] pixels = new int[11][11];
+ for (int i = 0; i < pixels.length; i++) {
+ for (int j = 0; j < pixels[i].length; j++) {
+ pixels[i][j] = 0;
+ }
+ }
+ pixels[5][5] = 1;
+
+ // Print mask
+ System.out.println("Pixels: ");
+ for (int i = 0; i < pixels.length; i++) {
+ for (int j = 0; j < pixels[i].length; j++) {
+ System.out.printf("%3d", pixels[i][j]);
+ System.out.print(' ');
+ }
+ System.out.println();
+ }
+
+ // Transform
+ DistanceTransform dt = new DistanceTransform();
+ dt.init(new IntMatrix(pixels), 0);
+ DoubleMatrix result = dt.computeTransform();
+
+ // Print transform
+ System.out.println("Result: ");
+ for (int i = 0; i < result.rows; i++) {
+ for (int j = 0; j < result.cols; j++) {
+ System.out.printf("%4.2f", result.doubleAt(i, j));
+ System.out.print(' ');
+ }
+ System.out.println();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.image.gray;
+
+import ie.dcu.matrix.DoubleMatrix;
+
+public class GrayConvolve {
+
+ public static DoubleMatrix convolve(
+ DoubleMatrix image, DoubleMatrix filter)
+ {
+ DoubleMatrix result = new DoubleMatrix(image.rows, image.cols);
+
+ for (int i = 0; i < image.rows; i++) {
+ for (int j = 0; j < image.cols; j++) {
+ double value = 0.0;
+
+ for (int ii = 0; ii < filter.rows; ii++) {
+ int i1 = i - (filter.rows >> 1) + ii;
+
+ if (image.hasRow(i1)) {
+ for (int jj = 0; jj < filter.cols; jj++) {
+ int j1 = j - (filter.cols >> 1) + jj;
+
+ if (image.hasCol(j1)) {
+ double filterValue = filter.doubleAt(ii, jj);
+ value += image.doubleAt(i1, j1) * filterValue;
+ }
+ }
+ }
+ }
+
+ result.setDoubleAt(i, j, value);
+ }
+ }
+
+ return result;
+ }
+}
--- /dev/null
+package ie.dcu.image.gray;
+
+import ie.dcu.image.ImageOp;
+
+public interface GrayImageOp extends ImageOp {
+
+
+}
--- /dev/null
+package ie.dcu.image.gray;
+
+import ie.dcu.image.AbstractImageOp;
+import ie.dcu.matrix.*;
+import ie.dcu.matrix.Matrix.Type;
+
+public class LaplaceFilterOp extends AbstractImageOp
+ implements GrayImageOp
+{
+
+ private boolean clampOutput = true;
+ private boolean simple = false;
+
+ public static DoubleMatrix rectify(ByteMatrix bm) {
+ DoubleMatrix im = new DoubleMatrix(bm.rows, bm.cols);
+ for (int i = 0; i < bm.size; i++) {
+ im.values[i] = (int) bm.values[i] - (int) Byte.MIN_VALUE;
+ }
+ return im;
+ }
+
+ @Override
+ protected Matrix processImage(MatrixProvider provider) {
+ Type type = provider.getDefaultMatrixType();
+
+ DoubleMatrix input;
+ if (type != null && type == Type.Byte) {
+ ByteMatrix matrix = (ByteMatrix) provider.getMatrix(Type.Byte, false);
+ input = rectify(matrix);
+ } else {
+ input = (DoubleMatrix) provider.getMatrix(Type.Double, false);
+ }
+
+ DoubleMatrix output = simple ? laplace4(input) : laplace8(input);
+
+
+ System.out.println("input min = " + input.minValue());
+ System.out.println("input max = " + input.maxValue());
+ System.out.println("output min = " + output.minValue());
+ System.out.println("output max = " + output.maxValue());
+
+ if (clampOutput) {
+
+ output.clamp(0, Double.MAX_VALUE);
+ }
+
+ return output;
+ }
+
+
+ public static DoubleMatrix laplace4(DoubleMatrix m) {
+ DoubleMatrix result = new DoubleMatrix(m.rows, m.cols);
+
+ double n0, n1, n2, n3;
+ for (int i = 0; i < m.rows; i++) {
+
+ n0 = (i != 0) ? 1 : 0;
+ n3 = (i != m.rows - 1) ? 1 : 0;
+
+ for (int j = 0; j < m.cols; j++) {
+
+ n1 = (j != 0) ? 1 : 0;
+ n2 = (j != m.cols-1) ? 1 : 0;
+
+ double value = (n0 + n1 + n2 + n3) * m.doubleAt(i, j);
+ value -= (n0 != 0) ? m.doubleAt(i-1, j) : 0;
+ value -= (n1 != 0) ? m.doubleAt(i, j-1) : 0;
+ value -= (n2 != 0) ? m.doubleAt(i, j+1) : 0;
+ value -= (n3 != 0) ? m.doubleAt(i+1, j) : 0;
+ result.setDoubleAt(i, j, value);
+ }
+ }
+
+ return result;
+ }
+
+ public static DoubleMatrix laplace8(DoubleMatrix m) {
+ DoubleMatrix result = new DoubleMatrix(m.rows, m.cols);
+
+ int a = m.rows - 1;
+ int b = m.cols - 1;
+
+ double n0, n1, n2, n3, n4, n5, n6, n7;
+ for (int i = 0; i < m.rows; i++) {
+
+ for (int j = 0; j < m.cols; j++) {
+
+ n0 = (i != 0 && j != 0) ? 1 : 0;
+ n1 = (i != 0) ? 1 : 0;
+ n2 = (i != 0 && j != b) ? 1 : 0;
+ n3 = (j != 0) ? 1 : 0;
+ n4 = (j != b) ? 1 : 0;
+ n5 = (i != a && j != 0) ? 1 : 0;
+ n6 = (i != a) ? 1 : 0;
+ n7 = (i != a && j != b) ? 1 : 0;
+
+ double w = (n0 + n1 + n2 + n3 + n4 + n5 + n6 + n7);
+ double v = w * m.doubleAt(i, j);
+
+ v -= (n0 != 0) ? m.doubleAt(i-1, j-1) : 0;
+ v -= (n1 != 0) ? m.doubleAt(i-1, j ) : 0;
+ v -= (n2 != 0) ? m.doubleAt(i-1, j+1) : 0;
+ v -= (n3 != 0) ? m.doubleAt(i , j-1) : 0;
+ v -= (n4 != 0) ? m.doubleAt(i , j+1) : 0;
+ v -= (n5 != 0) ? m.doubleAt(i+1, j-1) : 0;
+ v -= (n6 != 0) ? m.doubleAt(i+1, j ) : 0;
+ v -= (n7 != 0) ? m.doubleAt(i+1, j+1) : 0;
+
+ result.setDoubleAt(i, j, v);
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ie.dcu.cdvp.matrix.MatrixProvider#getDefaultMatrixType()
+ */
+ public Type getDefaultMatrixType() {
+ // we don't know
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * ByteMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of byte values.
+ *
+ * @author Kevin McGuinness
+ */
+public class ByteMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 6250820249861612603L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final ByteMatrix EMPTY_MATRIX = new ByteMatrix(0,0);
+
+ /**
+ * The byte values of the matrix in row-major order.
+ */
+ public final byte[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public ByteMatrix(int rows, int cols) {
+ super(Type.Byte, rows, cols);
+ values = new byte[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public ByteMatrix(int rows, int cols, byte ... values) {
+ super(Type.Byte, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of byte values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public ByteMatrix(byte[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the byte value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The byte value at (row, col).
+ */
+ public final byte byteAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the byte value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The byte value
+ */
+ public final byte byteAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the byte value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The byte value to set.
+ */
+ public final void setByteAt(int row, int col, byte value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the byte value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The byte value to set.
+ */
+ public final void setByteAt(Index2D index, byte value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the byte value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The byte value at the given offset.
+ */
+ public final byte byteAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the byte value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The byte value to set.
+ */
+ public final void setByteAt(int offset, byte value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest byte value in the matrix.
+ *
+ * @return
+ * The smallest byte value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Byte minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest byte value in the matrix.
+ *
+ * @return
+ * The largest byte value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Byte maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final ByteMatrix transpose() {
+ ByteMatrix m = new ByteMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public ByteMatrix clone() {
+ return new ByteMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ByteMatrix) {
+ ByteMatrix m = (ByteMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final ByteMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.byteValue());
+ return this;
+ }
+
+ /**
+ * Returns the Byte value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Byte value at (row, col).
+ */
+ @Override
+ public final Byte valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.byteValue();
+ }
+
+ /**
+ * Returns the Byte value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Byte value at the given offset.
+ */
+ @Override
+ public final Byte valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.byteValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final byte[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static ByteMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static ByteMatrix eye(int rows) {
+ ByteMatrix m = new ByteMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setByteAt(i, i, (byte) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static ByteMatrix zeros(int rows, int cols) {
+ ByteMatrix m = new ByteMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static ByteMatrix ones(int rows, int cols) {
+ ByteMatrix m = new ByteMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of byte values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of byte values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final byte[] flatten(byte[][] matrix, byte[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new byte[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(byte[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(byte[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(byte[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+/*
+ * DoubleMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of double values.
+ *
+ * @author Kevin McGuinness
+ */
+public class DoubleMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 3466326831906456667L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final DoubleMatrix EMPTY_MATRIX = new DoubleMatrix(0,0);
+
+ /**
+ * The double values of the matrix in row-major order.
+ */
+ public final double[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public DoubleMatrix(int rows, int cols) {
+ super(Type.Double, rows, cols);
+ values = new double[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public DoubleMatrix(int rows, int cols, double ... values) {
+ super(Type.Double, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of double values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public DoubleMatrix(double[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the double value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The double value at (row, col).
+ */
+ public final double doubleAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the double value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The double value
+ */
+ public final double doubleAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the double value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The double value to set.
+ */
+ public final void setDoubleAt(int row, int col, double value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the double value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The double value to set.
+ */
+ public final void setDoubleAt(Index2D index, double value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the double value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The double value at the given offset.
+ */
+ public final double doubleAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the double value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The double value to set.
+ */
+ public final void setDoubleAt(int offset, double value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest double value in the matrix.
+ *
+ * @return
+ * The smallest double value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Double minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest double value in the matrix.
+ *
+ * @return
+ * The largest double value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Double maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final DoubleMatrix transpose() {
+ DoubleMatrix m = new DoubleMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public DoubleMatrix clone() {
+ return new DoubleMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DoubleMatrix) {
+ DoubleMatrix m = (DoubleMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final DoubleMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.doubleValue());
+ return this;
+ }
+
+ /**
+ * Returns the Double value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Double value at (row, col).
+ */
+ @Override
+ public final Double valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.doubleValue();
+ }
+
+ /**
+ * Returns the Double value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Double value at the given offset.
+ */
+ @Override
+ public final Double valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.doubleValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final double[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static DoubleMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static DoubleMatrix eye(int rows) {
+ DoubleMatrix m = new DoubleMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setDoubleAt(i, i, (double) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static DoubleMatrix zeros(int rows, int cols) {
+ DoubleMatrix m = new DoubleMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static DoubleMatrix ones(int rows, int cols) {
+ DoubleMatrix m = new DoubleMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of double values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of double values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final double[] flatten(double[][] matrix, double[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new double[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(double[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(double[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(double[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+/*
+ * FloatMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of float values.
+ *
+ * @author Kevin McGuinness
+ */
+public class FloatMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 8664083529390696723L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final FloatMatrix EMPTY_MATRIX = new FloatMatrix(0,0);
+
+ /**
+ * The float values of the matrix in row-major order.
+ */
+ public final float[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public FloatMatrix(int rows, int cols) {
+ super(Type.Float, rows, cols);
+ values = new float[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public FloatMatrix(int rows, int cols, float ... values) {
+ super(Type.Float, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of float values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public FloatMatrix(float[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the float value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The float value at (row, col).
+ */
+ public final float floatAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the float value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The float value
+ */
+ public final float floatAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the float value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The float value to set.
+ */
+ public final void setFloatAt(int row, int col, float value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the float value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The float value to set.
+ */
+ public final void setFloatAt(Index2D index, float value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the float value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The float value at the given offset.
+ */
+ public final float floatAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the float value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The float value to set.
+ */
+ public final void setFloatAt(int offset, float value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest float value in the matrix.
+ *
+ * @return
+ * The smallest float value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Float minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest float value in the matrix.
+ *
+ * @return
+ * The largest float value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Float maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final FloatMatrix transpose() {
+ FloatMatrix m = new FloatMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public FloatMatrix clone() {
+ return new FloatMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof FloatMatrix) {
+ FloatMatrix m = (FloatMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final FloatMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.floatValue());
+ return this;
+ }
+
+ /**
+ * Returns the Float value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Float value at (row, col).
+ */
+ @Override
+ public final Float valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.floatValue();
+ }
+
+ /**
+ * Returns the Float value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Float value at the given offset.
+ */
+ @Override
+ public final Float valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.floatValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final float[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static FloatMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static FloatMatrix eye(int rows) {
+ FloatMatrix m = new FloatMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setFloatAt(i, i, (float) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static FloatMatrix zeros(int rows, int cols) {
+ FloatMatrix m = new FloatMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static FloatMatrix ones(int rows, int cols) {
+ FloatMatrix m = new FloatMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of float values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of float values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final float[] flatten(float[][] matrix, float[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new float[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(float[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(float[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(float[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+/*
+ * Index2D.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import java.io.Serializable;
+
+/**
+ * An immutable class encapsulating a two dimensional index.
+ *
+ * @author Kevin McGuinness
+ */
+public final class Index2D implements Serializable {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 2722263604670361463L;
+
+ /**
+ * The row index.
+ */
+ public final int i;
+
+ /**
+ * The column index.
+ */
+ public final int j;
+
+ /**
+ * Create an two dimensional index for the given co-ordinates.
+ *
+ * @param i
+ * The row index.
+ * @param j
+ * The column index.
+ */
+ public Index2D(int i, int j) {
+ this.i = i;
+ this.j = j;
+ }
+
+ /**
+ * Returns true if the passed object is an Index2D and both objects are
+ * have equal values.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Index2D) {
+ Index2D index = (Index2D) o;
+ return i == index.i && j == index.j;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the hash code.
+ */
+ @Override
+ public int hashCode() {
+ return (i ^ j);
+ }
+
+ /**
+ * Returns a string of the form i,j
+ */
+ public String toString() {
+ return String.format("%d,%d", i, j);
+ }
+}
--- /dev/null
+/*
+ * IntMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of int values.
+ *
+ * @author Kevin McGuinness
+ */
+public class IntMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 6142053146188137402L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final IntMatrix EMPTY_MATRIX = new IntMatrix(0,0);
+
+ /**
+ * The int values of the matrix in row-major order.
+ */
+ public final int[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public IntMatrix(int rows, int cols) {
+ super(Type.Int, rows, cols);
+ values = new int[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public IntMatrix(int rows, int cols, int ... values) {
+ super(Type.Int, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of int values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public IntMatrix(int[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the int value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The int value at (row, col).
+ */
+ public final int intAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the int value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The int value
+ */
+ public final int intAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the int value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The int value to set.
+ */
+ public final void setIntAt(int row, int col, int value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the int value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The int value to set.
+ */
+ public final void setIntAt(Index2D index, int value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the int value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The int value at the given offset.
+ */
+ public final int intAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the int value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The int value to set.
+ */
+ public final void setIntAt(int offset, int value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest int value in the matrix.
+ *
+ * @return
+ * The smallest int value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Integer minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest int value in the matrix.
+ *
+ * @return
+ * The largest int value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Integer maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final IntMatrix transpose() {
+ IntMatrix m = new IntMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public IntMatrix clone() {
+ return new IntMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof IntMatrix) {
+ IntMatrix m = (IntMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final IntMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.intValue());
+ return this;
+ }
+
+ /**
+ * Returns the Integer value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Integer value at (row, col).
+ */
+ @Override
+ public final Integer valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.intValue();
+ }
+
+ /**
+ * Returns the Integer value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Integer value at the given offset.
+ */
+ @Override
+ public final Integer valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.intValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final int[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static IntMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static IntMatrix eye(int rows) {
+ IntMatrix m = new IntMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setIntAt(i, i, (int) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static IntMatrix zeros(int rows, int cols) {
+ IntMatrix m = new IntMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static IntMatrix ones(int rows, int cols) {
+ IntMatrix m = new IntMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of int values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of int values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final int[] flatten(int[][] matrix, int[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new int[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(int[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(int[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(int[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+/*
+ * LongMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of long values.
+ *
+ * @author Kevin McGuinness
+ */
+public class LongMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 8453351009298723791L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final LongMatrix EMPTY_MATRIX = new LongMatrix(0,0);
+
+ /**
+ * The long values of the matrix in row-major order.
+ */
+ public final long[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public LongMatrix(int rows, int cols) {
+ super(Type.Long, rows, cols);
+ values = new long[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public LongMatrix(int rows, int cols, long ... values) {
+ super(Type.Long, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of long values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public LongMatrix(long[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the long value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The long value at (row, col).
+ */
+ public final long longAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the long value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The long value
+ */
+ public final long longAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the long value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The long value to set.
+ */
+ public final void setLongAt(int row, int col, long value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the long value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The long value to set.
+ */
+ public final void setLongAt(Index2D index, long value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the long value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The long value at the given offset.
+ */
+ public final long longAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the long value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The long value to set.
+ */
+ public final void setLongAt(int offset, long value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest long value in the matrix.
+ *
+ * @return
+ * The smallest long value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Long minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest long value in the matrix.
+ *
+ * @return
+ * The largest long value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Long maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final LongMatrix transpose() {
+ LongMatrix m = new LongMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public LongMatrix clone() {
+ return new LongMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof LongMatrix) {
+ LongMatrix m = (LongMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final LongMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.longValue());
+ return this;
+ }
+
+ /**
+ * Returns the Long value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Long value at (row, col).
+ */
+ @Override
+ public final Long valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.longValue();
+ }
+
+ /**
+ * Returns the Long value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Long value at the given offset.
+ */
+ @Override
+ public final Long valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.longValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final long[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static LongMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static LongMatrix eye(int rows) {
+ LongMatrix m = new LongMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setLongAt(i, i, (long) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static LongMatrix zeros(int rows, int cols) {
+ LongMatrix m = new LongMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static LongMatrix ones(int rows, int cols) {
+ LongMatrix m = new LongMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of long values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of long values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final long[] flatten(long[][] matrix, long[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new long[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(long[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(long[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(long[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+/*
+ * Matrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ *
+ * TODO: Optimizations in subclasses (toXMatrix)
+ */
+package ie.dcu.matrix;
+
+import java.io.*;
+import java.util.*;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Abstract base class for two dimensional matrix types.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class Matrix implements Cloneable, Serializable, MatrixProvider {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 7948337494015379755L;
+
+ /**
+ * Supported matrix types.
+ */
+ public static enum Type {
+ Byte,
+ Short,
+ Int,
+ Long,
+ Double,
+ Float
+ };
+
+ /**
+ * The matrix type. (never <code>null</code>).
+ */
+ public final Type type;
+
+ /**
+ * The number of rows (height) of the matrix (always >= 0).
+ */
+ public final int rows;
+
+ /**
+ * The number of rows (width) of the matrix (always >= 0).
+ */
+ public final int cols;
+
+ /**
+ * The total number of elements matrix (always >= 0).
+ */
+ public final int size;
+
+ /**
+ * Constructor for subclasses.
+ *
+ * @param type
+ * The matrix type (cannot be <code>null</code>).
+ * @param rows
+ * The number or rows in the matrix (must be >= 0).
+ * @param cols
+ * The number of columns in the matrix (must be >= 0).
+ */
+ protected Matrix(Type type, int rows, int cols) {
+ this(type, rows, cols, rows * cols);
+ }
+
+ /**
+ * Constructor for subclasses.
+ *
+ * The reason this constructor takes a size parameter is to allow
+ * subclasses to easily ensure the size of a passed array is correct.
+ *
+ * @param type
+ * The matrix type (cannot be <code>null</code>).
+ * @param rows
+ * The number or rows in the matrix (must be >= 0).
+ * @param cols
+ * The number of columns in the matrix (must be >= 0).
+ * @param size
+ * Must equal <code>rows * cols</code>.
+ */
+ protected Matrix(Type type, int rows, int cols, int size) {
+ if (type == null) {
+ throw new IllegalArgumentException("type == null");
+ }
+
+ if (rows < 0) {
+ throw new IllegalArgumentException("rows < 0");
+ }
+
+ if (cols < 0) {
+ throw new IllegalArgumentException("cols < 0");
+ }
+
+ if (size != rows * cols) {
+ throw new IllegalArgumentException("size != rows * cols");
+ }
+
+ this.type = type;
+ this.rows = rows;
+ this.cols = cols;
+ this.size = size;
+ }
+
+ /**
+ * Returns true if the given row index is in range.
+ *
+ * @param row
+ * The row index
+ */
+ public final boolean hasRow(int row) {
+ return row >= 0 && row < rows;
+ }
+
+ /**
+ * Returns true if the given column index is in range.
+ *
+ * @param col
+ * The column index
+ */
+ public final boolean hasCol(int col) {
+ return col >= 0 && col < cols;
+ }
+
+ /**
+ * Returns true if the given row and column index are in range.
+ *
+ * @param row
+ * The row index
+ * @param col
+ * The column index
+ */
+ public final boolean hasIndex(int row, int col) {
+ return row >= 0 && row < rows && col >= 0 && col < cols;
+ }
+
+ /**
+ * Returns true if the given two dimensional index is in range.
+ *
+ * @param index
+ * The two dimensional index
+ */
+ public final boolean hasIndex(Index2D index) {
+ return hasIndex(index.i, index.j);
+ }
+
+ /**
+ * Returns true if the given offset is in range.
+ *
+ * @param offset
+ * An absolute offset into the matrix.
+ */
+ public final boolean hasOffset(int offset) {
+ return offset >= 0 && offset < size;
+ }
+
+ /**
+ * Returns true if the given matrix is the same size as the receiver.
+ *
+ * @param m
+ * A matrix of any type.
+ */
+ public final boolean sizeEquals(Matrix m) {
+ return rows == m.rows && cols == m.cols;
+ }
+
+ /**
+ * Returns the width (number of columns) in the matrix.
+ */
+ public final int width() {
+ return cols;
+ }
+
+ /**
+ * Returns the height (number of rows) in the matrix.
+ */
+ public final int height() {
+ return rows;
+ }
+
+ /**
+ * Returns the offset of the given row index.
+ *
+ * @param row
+ * The row index.
+ */
+ public final int offsetOf(int row) {
+ return row * cols;
+ }
+
+ /**
+ * Returns the offset of the given row and column index.
+ *
+ * For efficiency this method does not check if the matrix is empty.
+ * The value col will be returned if it is.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ */
+ public final int offsetOf(int row, int col) {
+ return row * cols + col;
+ }
+
+ /**
+ * Returns the offset of the given two dimensional index.
+ *
+ * For efficiency this method does not check if the matrix is empty.
+ * The value index.j will be returned if it is.
+ *
+ * @param index
+ * A two dimensional index.
+ */
+ public final int offsetOf(Index2D index) {
+ return index.i * cols + index.j;
+ }
+
+ /**
+ * Returns the two dimensional index of the given offset.
+ *
+ * The return value is <code>null</code> if the matrix is empty.
+ *
+ * @param offset
+ * An absolute offset into the matrix.
+ * @return
+ * The two dimensional index of the offset.
+ */
+ public final Index2D indexOf(int offset) {
+ return size != 0 ? new Index2D(offset / cols, offset % cols) : null;
+ }
+
+ /**
+ * Returns true of the matrix has zero rows or zero columns.
+ */
+ public final boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * Returns the Number value at the given row and column.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * A Number value.
+ */
+ public Number valueAt(int row, int col) {
+ return valueAt(offsetOf(row, col));
+ }
+
+ /**
+ * Returns the Number value at the given two dimensional index.
+ *
+ * @param index
+ * The two dimensional index.
+ * @return
+ * A Number value.
+ */
+ public Number valueAt(Index2D index) {
+ return valueAt(offsetOf(index));
+ }
+
+ /**
+ * Set the Number value at the given row and column.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set.
+ */
+ public void setValueAt(int row, int col, Number value) {
+ setValueAt(offsetOf(row, col), value);
+ }
+
+ /**
+ * Set the Number value at the given two dimensional index.
+ *
+ * @param index
+ * The two dimensional index
+ * @param value
+ * The Number value to set.
+ */
+ public void setValueAt(Index2D index, Number value) {
+ setValueAt(offsetOf(index), value);
+ }
+
+ /**
+ * Fill the matrix with the given number value.
+ *
+ * @param number
+ * The number value.
+ * @return This matrix.
+ */
+ public Matrix fill(Number number) {
+ for (int i = 0; i < size; i++) {
+ setValueAt(i, number);
+ }
+ return this;
+ }
+
+ /**
+ * Clamp the matrix values in the given range.
+ *
+ * @param min
+ * The minimum value.
+ * @param max
+ * The maximum value.
+ * @return This matrix.
+ */
+ public Matrix clamp(Number min, Number max) {
+ Arrays.clamp(values(), min, max);
+ return this;
+ }
+
+ /**
+ * Returns the smallest value in the matrix.
+ *
+ * Comparisons are performed in double precision.
+ *
+ * @return
+ * The smallest value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public Number minValue() {
+ Number min = null;
+ if (!isEmpty()) {
+ min = valueAt(0);
+ for (int i = 1; i < size; i++) {
+ Number value = valueAt(i);
+ if (value.doubleValue() < min.doubleValue()) {
+ min = value;
+ }
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Returns the largest value in the matrix.
+ *
+ * Comparisons are performed in double precision.
+ *
+ * @return
+ * The largest value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public Number maxValue() {
+ Number max = null;
+ if (!isEmpty()) {
+ max = valueAt(0);
+ for (int i = 1; i < size; i++) {
+ Number value = valueAt(i);
+ if (value.doubleValue() > max.doubleValue()) {
+ max = value;
+ }
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Find the first index containing the given number. If no such index
+ * exists, <code>null</code> is returned.
+ *
+ * Comparison is performed using double precision floats.
+ *
+ * @param number
+ * A Number value.
+ * @return
+ * The first index matching the value, or null if none is found.
+ */
+ public Index2D findFirst(Number number) {
+ double value = number.doubleValue();
+ for (int i = 0, k = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++, k++) {
+ if (valueAt(k).doubleValue() == value) {
+ return new Index2D(i, j);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find the next index containing the given number. If no such index
+ * exists, <code>null</code> is returned.
+ *
+ * Comparison is performed using double precision floats.
+ *
+ * @param number
+ * A Number value.
+ * @param start
+ * The index to start at.
+ * @return
+ * The first index matching the value, or null if none is found.
+ */
+ public Index2D findNext(Index2D start, Number number) {
+ double value = number.doubleValue();
+ for (int i = start.i; i < rows; i++) {
+ int k = i * cols;
+ for (int j = start.j; j < cols; j++, k++) {
+
+ if (valueAt(k).doubleValue() == value) {
+ return new Index2D(i, j);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find the last index containing the given number. If no such index
+ * exists, <code>null</code> is returned.
+ *
+ * Comparison is performed using double precision floats.
+ *
+ * @param number
+ * A Number value.
+ * @return
+ * The last index matching the value, or null if none is found.
+ */
+ public Index2D findLast(Number number) {
+ double value = number.doubleValue();
+ for (int i = rows - 1; i >= 0; i--) {
+ for (int j = cols - 1; j >= 0; j--) {
+ if (valueAt(i,j).doubleValue() == value) {
+ return new Index2D(i, j);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find all indices containing the given number.
+ *
+ * The returned list is ordered from first found to last, with the search
+ * starting at the top left corner of the matrix and proceeding in row major
+ * order.
+ *
+ * Comparison is performed using double precision floats.
+ *
+ * @param number
+ * A Number value.
+ * @return
+ * A list of indices.
+ */
+ public List<Index2D> findAll(Number number) {
+ ArrayList<Index2D> indices = new ArrayList<Index2D>();
+
+ double value = number.doubleValue();
+ for (int i = 0, k = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++, k++) {
+ if (valueAt(k).doubleValue() == value) {
+ indices.add(new Index2D(i, j));
+ }
+ }
+ }
+
+ return indices;
+ }
+
+ /**
+ * Convert to a ByteMatrix, rounding and truncating if necessary.
+ *
+ * @return
+ * A new ByteMatrix.
+ */
+ public ByteMatrix toByteMatrix() {
+ return toByteMatrix(null);
+ }
+
+ /**
+ * Convert to a ByteMatrix, rounding and truncating if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new ByteMatrix.
+ */
+ public ByteMatrix toByteMatrix(ByteMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new ByteMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Convert to a ShortMatrix, rounding and truncating if necessary.
+ *
+ * @return
+ * A new ShortMatrix.
+ */
+ public ShortMatrix toShortMatrix() {
+ return toShortMatrix(null);
+ }
+
+ /**
+ * Convert to a ShortMatrix, rounding and truncating if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new ShortMatrix.
+ */
+ public ShortMatrix toShortMatrix(ShortMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new ShortMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Convert to an IntMatrix, rounding and truncating if necessary.
+ *
+ * @return
+ * A new IntMatrix.
+ */
+ public IntMatrix toIntMatrix() {
+ return toIntMatrix(null);
+ }
+
+ /**
+ * Convert to a IntMatrix, rounding and truncating if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new IntMatrix.
+ */
+ public IntMatrix toIntMatrix(IntMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new IntMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Convert to a LongMatrix, rounding and truncating if necessary.
+ *
+ * @return
+ * A new LongMatrix.
+ */
+ public LongMatrix toLongMatrix() {
+ return toLongMatrix(null);
+ }
+
+ /**
+ * Convert to a LongMatrix, rounding and truncating if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new LongMatrix.
+ */
+ public LongMatrix toLongMatrix(LongMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new LongMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Convert to an FloatMatrix, rounding if necessary.
+ *
+ * @return
+ * A new FloatMatrix.
+ */
+ public FloatMatrix toFloatMatrix() {
+ return toFloatMatrix(null);
+ }
+
+ /**
+ * Convert to an FloatMatrix, rounding if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new FloatMatrix.
+ */
+ public FloatMatrix toFloatMatrix(FloatMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new FloatMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Convert to an DoubleMatrix, rounding if necessary.
+ *
+ * @return
+ * A new DoubleMatrix.
+ */
+ public DoubleMatrix toDoubleMatrix() {
+ return toDoubleMatrix(null);
+ }
+
+ /**
+ * Convert to an DoubleMatrix, rounding if necessary.
+ *
+ * If the specified matrix is <code>null</code> or not the same
+ * size as this matrix, a new matrix is allocated.
+ *
+ * @param matrix
+ * The matrix to copy to.
+ * @return
+ * A new DoubleMatrix.
+ */
+ public DoubleMatrix toDoubleMatrix(DoubleMatrix matrix) {
+ if (matrix == null || !sizeEquals(matrix)) {
+ matrix = new DoubleMatrix(rows, cols);
+ }
+
+ Arrays.copy(values(), matrix.values);
+ return matrix;
+ }
+
+ /**
+ * Returns the Number value at the given offset.
+ *
+ * @param offset
+ * An absolute offset into the matrix.
+ * @return
+ * The Number value at the offset.
+ */
+ public abstract Number valueAt(int offset);
+
+ /**
+ * Set the Number value at the given offset.
+ *
+ * @param offset
+ * An absolute offset into the matrix.
+ * @param value
+ * The number value to set.
+ */
+ public abstract void setValueAt(int offset, Number value);
+
+ /**
+ * Returns true of the matrices are the same size and have equal values.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Matrix) {
+ Matrix m = (Matrix) obj;
+
+ if (sizeEquals(m)) {
+ for (int i = 0; i < size; i++) {
+ if (!valueAt(i).equals(m.valueAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the array of matrix values. The return type is dependent
+ * on the subclass, so Object is given here.
+ *
+ * @return The array of values.
+ */
+ public abstract Object values();
+
+ /**
+ * Returns a string representation of the matrix.
+ */
+ @Override
+ public String toString() {
+ String header = String.format("%dx%d Matrix (%s)", rows, cols, type);
+ if (rows < 10 && cols < 10) {
+ // Show the whole matrix
+ StringBuffer s = new StringBuffer(header);
+ s.append(" = [\n");
+ MatrixFormatter f = new MatrixFormatter();
+ f.format(this, s);
+ s.append(" ]");
+ return s.toString();
+ }
+ return header;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ie.dcu.cdvp.matrix.MatrixProvider#getDefaultMatrixType()
+ */
+ public Type getDefaultMatrixType() {
+ return type;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ie.dcu.cdvp.matrix.MatrixProvider#getMatrix(boolean)
+ */
+ public Matrix getMatrix(boolean alwaysCopy) {
+ return getMatrix(null, alwaysCopy);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see ie.dcu.cdvp.matrix.MatrixProvider#getMatrix(ie.dcu.cdvp.matrix.Matrix.Type, boolean)
+ */
+ public Matrix getMatrix(Type type, boolean alwaysCopy) {
+
+ if (type == null || type == this.type) {
+ try {
+ return (alwaysCopy) ? (Matrix) clone() : this;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ switch (type) {
+ case Byte:
+ return toByteMatrix();
+ case Short:
+ return toShortMatrix();
+ case Int:
+ return toIntMatrix();
+ case Long:
+ return toLongMatrix();
+ case Float:
+ return toFloatMatrix();
+ case Double:
+ return toDoubleMatrix();
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+}
--- /dev/null
+package ie.dcu.matrix;
+
+/**
+ * Handles formating matrices so that all columns are equal width.
+ *
+ * @author Kevin McGuinness
+ */
+public class MatrixFormatter {
+
+ private static final String NaN = "NaN";
+ private static final String Inf = "Inf";
+
+ // Options
+ private boolean supressSmallValues;
+ private boolean signAlwaysShown;
+ private int precision;
+
+ // Internal variables
+ private boolean expFormat;
+ private boolean largeExponent;
+ private String numberFormat;
+ private String specialFormat;
+ private int maxStrLen;
+
+ public MatrixFormatter() {
+
+ // Setup defaults
+ supressSmallValues = false;
+ signAlwaysShown = false;
+ precision = 8;
+ }
+
+ public boolean isSupressSmallValues() {
+ return supressSmallValues;
+ }
+
+ public void setSupressSmallValues(boolean value) {
+ this.supressSmallValues = value;
+ }
+
+ public boolean isSignAlwaysShown() {
+ return signAlwaysShown;
+ }
+
+ public void setSignAlwaysShown(boolean value) {
+ this.signAlwaysShown = value;
+ }
+
+ public int getPrecision() {
+ return precision;
+ }
+
+ public void setPrecision(int precision) {
+ if (precision < 0) {
+ throw new IllegalArgumentException("precision < 0");
+ }
+ this.precision = precision;
+ }
+
+ public String format(Matrix m) {
+ return format(m, null).toString();
+ }
+
+ public StringBuffer format(Matrix m, StringBuffer s) {
+ if (s == null) {
+ s = new StringBuffer();
+ }
+
+ if (!m.isEmpty()) {
+ switch (m.type) {
+ case Byte:
+ case Short:
+ case Int:
+ case Long:
+ formatIntegerMatrix(m, s);
+ break;
+ default:
+ formatDecimalMatrix(m, s);
+ break;
+ }
+ }
+
+ return s;
+ }
+
+ public static String toString(Matrix m) {
+ return new MatrixFormatter().format(m, null).toString();
+ }
+
+ private void formatIntegerMatrix(Matrix m, StringBuffer s) {
+ analyseIntegerMatrix(m);
+
+ for (int i = 0; i < m.rows; i++) {
+ for (int j = 0; j < m.cols; j++) {
+ long value = m.valueAt(i, j).longValue();
+ s.append(formatValue(value));
+ }
+ if (i != m.rows - 1) {
+ s.append('\n');
+ }
+ }
+ }
+
+ private void formatDecimalMatrix(Matrix m, StringBuffer s) {
+ analyseDecimalMatrix(m);
+
+ for (int i = 0; i < m.rows; i++) {
+ for (int j = 0; j < m.cols; j++) {
+ double v = m.valueAt(i, j).doubleValue();
+ s.append(formatValue(v));
+ }
+ if (i != m.rows - 1) {
+ s.append('\n');
+ }
+ }
+ }
+
+ private String formatValue(double v) {
+ if (Double.isNaN(v)) {
+ return String.format(specialFormat, NaN);
+ }
+
+ if (Double.isInfinite(v)) {
+ return String.format(specialFormat, v < 0 ? "-" + Inf : Inf);
+ }
+
+ StringBuilder s = new StringBuilder(String.format(numberFormat, v));
+
+ if (largeExponent) {
+ char sign = s.charAt(s.length()-4);
+ if (sign == '+' || sign == '-') {
+ s.insert(s.length()-3, '0');
+ }
+
+ } else if (expFormat) {
+ if (s.charAt(s.length()-4) == '0') {
+ s.deleteCharAt(s.length()-4);
+ }
+ s.insert(0, ' ');
+ } else if (v != 0) {
+ while (s.charAt(s.length()-1) == '0' && s.charAt(s.length()-2) != '.') {
+ s.deleteCharAt(s.length()-1);
+ s.insert(0, ' ');
+ }
+ }
+
+ return s.toString();
+ }
+
+ private String formatValue(long value) {
+ return String.format(numberFormat, value);
+ }
+
+ private void analyseIntegerMatrix(Matrix m) {
+ long min = m.maxValue().longValue();
+ long max = m.minValue().longValue();
+
+ int digits = Math.max(
+ String.valueOf(min).length(),
+ String.valueOf(max).length());
+
+ maxStrLen = digits + 1;
+ if (min >= 0 && signAlwaysShown) {
+ maxStrLen++;
+ }
+
+ StringBuilder nfmt = new StringBuilder("%");
+
+ if (signAlwaysShown) {
+ nfmt.append('+');
+ }
+
+ nfmt.append(maxStrLen);
+ nfmt.append('d');
+ numberFormat = nfmt.toString();
+ }
+
+ private void analyseDecimalMatrix(Matrix m) {
+ expFormat = false;
+ largeExponent = false;
+
+ boolean hasSpecial = false;
+ boolean hasNonZero = false;
+ double minAbsNzVal = Double.MAX_VALUE;
+ double maxAbsNzVal = Double.MIN_VALUE;
+
+ for (int i = 0; i < m.size; i++) {
+ double value = m.valueAt(i).doubleValue();
+
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ hasSpecial = true;
+ continue;
+ }
+
+ if (value != 0.0) {
+ hasNonZero = true;
+
+ double abs = Math.abs(value);
+
+ if (abs < minAbsNzVal) {
+ minAbsNzVal = abs;
+ }
+
+ if (abs > maxAbsNzVal) {
+ maxAbsNzVal = abs;
+ }
+ }
+ }
+
+ if (hasNonZero) {
+
+ if (maxAbsNzVal > 1e8) {
+ expFormat = true;
+ }
+
+ if (!supressSmallValues) {
+ if (minAbsNzVal < 0.0001 || maxAbsNzVal / minAbsNzVal > 1000.0) {
+ expFormat = true;
+ }
+ }
+
+ } else {
+
+ maxAbsNzVal = 0.0;
+ minAbsNzVal = 0.0;
+ }
+
+ int truePrecision = precision;
+
+ if (expFormat) {
+ maxStrLen = 8 + truePrecision;
+
+ if ((minAbsNzVal >= 0 && minAbsNzVal < 1e-99) || maxAbsNzVal > 1e100) {
+ largeExponent = true;
+ maxStrLen++;
+ }
+
+ } else {
+
+ if (hasNonZero) {
+ String fmt = String.format("%%.%df", precision);
+ truePrecision = maxDigitCount(fmt, m, precision);
+ } else {
+ truePrecision = 0;
+ }
+
+ truePrecision = Math.min(precision, truePrecision);
+ int intLength = String.valueOf((long) maxAbsNzVal).length();
+ maxStrLen = intLength + truePrecision + 3;
+
+ if (hasSpecial) {
+ int maxSpecialLen = Math.max(NaN.length(), Inf.length()+1);
+ maxStrLen = Math.max(maxStrLen, maxSpecialLen);
+ }
+ }
+
+ // Compute number format
+ StringBuffer nfmt = new StringBuffer("%");
+ nfmt.append(signAlwaysShown ? "+" : "");
+ nfmt.append(maxStrLen);
+ nfmt.append('.');
+ nfmt.append(truePrecision);
+ nfmt.append(expFormat? 'e' : 'f');
+ numberFormat = nfmt.toString();
+
+ // Compute special format
+ StringBuffer sfmt = new StringBuffer("%");
+ sfmt.append(maxStrLen);
+ sfmt.append('s');
+ specialFormat = sfmt.toString();
+ }
+
+ private static int maxDigitCount(String format, Matrix m, int precision) {
+ int max = 0;
+ for (int i = 0; i < m.size; i++) {
+ double v = m.valueAt(i).doubleValue();
+ if (!Double.isNaN(v) && !Double.isInfinite(v) && v != 0) {
+ int count = digitCount(format, v, precision);
+ if (count > max) {
+ max = count;
+ }
+ }
+ }
+ return max;
+ }
+
+ private static int digitCount(String format, double value, int precision) {
+ String s = String.format(format, value);
+ StringBuilder z = new StringBuilder(s);
+ while (z.length() > 0 && z.charAt(z.length()-1) == '0') {
+ z.deleteCharAt(z.length()-1);
+ }
+ return precision - s.length() + z.length();
+ }
+}
--- /dev/null
+package ie.dcu.matrix;
+
+/**
+ * Interface for classes that can provide matrices.
+ *
+ * @author Kevin McGuinness
+ */
+public interface MatrixProvider {
+
+ /**
+ * Returns the default type created by this matrix provider if no type
+ * is specified when calling <code>getMatrix()</code>. This may be null
+ * if it is unknown.
+ *
+ * @return The default matrix type.
+ */
+ public Matrix.Type getDefaultMatrixType();
+
+ /**
+ * Request a matrix from the matrix provider.
+ *
+ * @param alwaysCopy
+ * If <code>true</code> the a copy is always returned, otherwise
+ * the matrix may or may not be a copy.
+ * @return A matrix.
+ */
+ public Matrix getMatrix(boolean alwaysCopy);
+
+ /**
+ * Request a matrix of the given type from the matrix provider.
+ *
+ * @param type
+ * The matrix type. If <code>null</code> then the most convenient
+ * type for the receiver will be returned.
+ * @param alwaysCopy
+ * If <code>true</code> the a copy is always returned, otherwise
+ * the matrix may or may not be a copy.
+ * @return A matrix.
+ */
+ public Matrix getMatrix(Matrix.Type type, boolean alwaysCopy);
+}
--- /dev/null
+/*
+ * ShortMatrix.java
+ *
+ * Copyright (c) 2009 by Kevin McGuinness
+ */
+package ie.dcu.matrix;
+
+import ie.dcu.array.Arrays;
+
+/**
+ * Two dimensional matrix of short values.
+ *
+ * @author Kevin McGuinness
+ */
+public class ShortMatrix extends Matrix {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 515302276679708530L;
+
+ /**
+ * Shared empty matrix
+ */
+ private static final ShortMatrix EMPTY_MATRIX = new ShortMatrix(0,0);
+
+ /**
+ * The short values of the matrix in row-major order.
+ */
+ public final short[] values;
+
+ /**
+ * Construct an uninitialized matrix of the given size.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ */
+ public ShortMatrix(int rows, int cols) {
+ super(Type.Short, rows, cols);
+ values = new short[size];
+ }
+
+ /**
+ * Construct a matrix of the given size with the given values.
+ *
+ * @param rows
+ * The number of rows
+ * @param cols
+ * The number of columns
+ * @param values
+ * The values to the matrix will have (not copied).
+ */
+ public ShortMatrix(int rows, int cols, short ... values) {
+ super(Type.Short, rows, cols, values.length);
+ this.values = values;
+ }
+
+ /**
+ * Construct a matrix of the given size by copying the values in the
+ * given two dimensional array.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * @param matrix
+ * A two dimensional array of short values.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public ShortMatrix(short[][] matrix) {
+ this(rowsIn(matrix), colsIn(matrix));
+ flatten(matrix, values);
+ }
+
+ /**
+ * Returns the short value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The short value at (row, col).
+ */
+ public final short shortAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Returns the short value at the index.
+ *
+ * @param index
+ * The index
+ * @return
+ * The short value
+ */
+ public final short shortAt(Index2D index) {
+ return values[offsetOf(index)];
+ }
+
+ /**
+ * Set the short value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The short value to set.
+ */
+ public final void setShortAt(int row, int col, short value) {
+ values[offsetOf(row, col)] = value;
+ }
+
+ /**
+ * Set the short value at the given index.
+ *
+ * @param index
+ * The index.
+ * @param value
+ * The short value to set.
+ */
+ public final void setShortAt(Index2D index, short value) {
+ values[offsetOf(index)] = value;
+ }
+
+ /**
+ * Returns the short value at the given offset in the matrix.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The short value at the given offset.
+ */
+ public final short shortAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the short value at the given offset in the matrix.
+ *
+ * @param offset
+ * The matrix offset
+ * @param value
+ * The short value to set.
+ */
+ public final void setShortAt(int offset, short value) {
+ values[offset] = value;
+ }
+
+ /**
+ * Returns the smallest short value in the matrix.
+ *
+ * @return
+ * The smallest short value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Short minValue() {
+ return Arrays.min(values);
+ }
+
+ /**
+ * Returns the largest short value in the matrix.
+ *
+ * @return
+ * The largest short value, or <code>null</code> if the
+ * matrix is empty.
+ */
+ public final Short maxValue() {
+ return Arrays.max(values);
+ }
+
+ /**
+ * Returns a transposed version of the matrix.
+ */
+ public final ShortMatrix transpose() {
+ ShortMatrix m = new ShortMatrix(cols, rows);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ m.values[offsetOf(j,i)] = values[offsetOf(i, j)];
+ }
+ }
+ return m;
+ }
+
+ /**
+ * Returns a deep copy of the matrix.
+ */
+ @Override
+ public ShortMatrix clone() {
+ return new ShortMatrix(rows, cols, values.clone());
+ }
+
+ /**
+ * Returns true if both matrices are equal in value.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ShortMatrix) {
+ ShortMatrix m = (ShortMatrix) obj;
+ if (sizeEquals(m)) {
+ return java.util.Arrays.equals(values, m.values);
+ }
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Fills the matrix with the %{primative} value of the given number.
+ *
+ * @param number
+ * The number to fill the matrix with (will be truncated if
+ * necessary).
+ * @return
+ * This matrix.
+ */
+ @Override
+ public final ShortMatrix fill(Number number) {
+ java.util.Arrays.fill(values, number.shortValue());
+ return this;
+ }
+
+ /**
+ * Returns the Short value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @return
+ * The Short value at (row, col).
+ */
+ @Override
+ public final Short valueAt(int row, int col) {
+ return values[offsetOf(row, col)];
+ }
+
+ /**
+ * Set the Number value at the given row and column index.
+ *
+ * @param row
+ * The row index.
+ * @param col
+ * The column index.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int row, int col, Number value) {
+ values[offsetOf(row, col)] = value.shortValue();
+ }
+
+ /**
+ * Returns the Short value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @return
+ * The Short value at the given offset.
+ */
+ @Override
+ public final Short valueAt(int offset) {
+ return values[offset];
+ }
+
+ /**
+ * Set the Number value at the given matrix offset.
+ *
+ * @param offset
+ * The absolute offset in the matrix.
+ * @param value
+ * The Number value to set (will be truncated if necessary).
+ */
+ @Override
+ public final void setValueAt(int offset, Number value) {
+ values[offset] = value.shortValue();
+ }
+
+ /**
+ * Returns the matrix values (not copied).
+ */
+ @Override
+ public final short[] values() {
+ return values;
+ }
+
+ /**
+ * Returns a shared empty matrix instance.
+ */
+ public static ShortMatrix empty() {
+ return EMPTY_MATRIX;
+ }
+
+ /**
+ * Construct and return an identity matrix with the given number of rows.
+ *
+ * @param rows
+ * The number of rows/columns for the matrix to have.
+ * @return
+ * A new identity matrix.
+ */
+ public static ShortMatrix eye(int rows) {
+ ShortMatrix m = new ShortMatrix(rows, rows);
+ m.fill(0);
+ for (int i = 0; i < rows; i++) {
+ m.setShortAt(i, i, (short) 1);
+ }
+ return m;
+ }
+
+ /**
+ * Construct and return an zero matrix with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new zero matrix.
+ */
+ public static ShortMatrix zeros(int rows, int cols) {
+ ShortMatrix m = new ShortMatrix(rows, rows);
+ m.fill(0);
+ return m;
+ }
+
+ /**
+ * Construct and return a matrix of ones with the given number of rows
+ * and columns.
+ *
+ * @param rows
+ * The number of rows for the matrix to have.
+ * @param cols
+ * The number of cols for the matrix to have.
+ * @return
+ * A new matrix of ones.
+ */
+ public static ShortMatrix ones(int rows, int cols) {
+ ShortMatrix m = new ShortMatrix(rows, rows);
+ m.fill(1);
+ return m;
+ }
+
+ /**
+ * Copy the given two dimensional array of short values into the given
+ * array. If the passed array does not contain at enough room for the
+ * passed matrix, a new array is allocated and returned.
+ *
+ * The passed two dimensional array must consist of arrays that have
+ * the same dimension.
+ *
+ * The copy is performed such that the flattened array is in row major
+ * order.
+ *
+ * @param matrix
+ * The 2D array of short values to flatten
+ * @param array
+ * The array to copy to (can be <code>null</code>).
+ * @return
+ * A flattened version of the matrix.
+ * @throws IllegalArgumentException
+ * If the matrix does not contain arrays of the same dimension.
+ */
+ public static final short[] flatten(short[][] matrix, short[] array) {
+ int rows = matrix.length;
+ int cols = matrix.length > 0 ? matrix[0].length : 0;
+ int size = rows * cols;
+
+ if (array == null || array.length < size) {
+ array = new short[size];
+ }
+
+ for (int i = 0; i < rows; i++) {
+
+ if (cols != matrix[i].length) {
+ throw new IllegalArgumentException();
+ }
+
+ System.arraycopy(matrix[i], 0, array, cols * i, cols);
+ }
+
+ return array;
+ }
+
+ /**
+ * Returns the number of rows in the given two dimensional array.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of rows.
+ */
+ public static final int rowsIn(short[][] matrix) {
+ return matrix.length;
+ }
+
+ /**
+ * Returns the number of columns in the given two dimensional array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The number of columns.
+ */
+ public static final int colsIn(short[][] matrix) {
+ return matrix.length > 0 ? matrix[0].length : 0;
+ }
+
+ /**
+ * Returns the total number of elements in the given two dimensional
+ * array.
+ *
+ * This method assumes that the arrays in the given matrix all have
+ * equal lengths.
+ *
+ * @param matrix
+ * A two dimensional array.
+ * @return
+ * The total number of elements.
+ */
+ public static final int sizeOf(short[][] matrix) {
+ return rowsIn(matrix) * colsIn(matrix);
+ }
+}
--- /dev/null
+package ie.dcu.plugin;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.Map;
+
+public class Plugin {
+ private final File root;
+ private final Map<String, String> metadata;
+ private final URLClassLoader loader;
+
+ Plugin(File root, Map<String, String> metadata, URLClassLoader loader) {
+ this.root = root;
+ this.metadata = metadata;
+ this.loader = loader;
+ }
+
+ public String getName() {
+ return getMetadata("name");
+ }
+
+ public Map<String, String> getMetadata() {
+ return metadata;
+ }
+
+ public String getMetadata(String key) {
+ return metadata.get(key);
+ }
+
+ public File getRootFolder() {
+ return root;
+ }
+
+ public File getResource(String name) {
+ return new File(root, name);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return loader.loadClass(name);
+ }
+
+ public Object loadObject(String name)
+ throws PluginException
+ {
+ try {
+ Class<?> clazz = loadClass(name);
+ Object instance = clazz.newInstance();
+ return instance;
+ } catch (ClassNotFoundException e) {
+ throw new PluginException(e);
+ } catch (InstantiationException e) {
+ throw new PluginException(e);
+ } catch (IllegalAccessException e) {
+ throw new PluginException(e);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.plugin;
+
+import ie.dcu.util.OsUtils;
+
+import java.io.File;
+import java.net.*;
+
+public class PluginClassLoader extends URLClassLoader {
+
+ private final File root;
+
+ public PluginClassLoader(File root, URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ this.root = root;
+ }
+
+ @Override
+ protected String findLibrary(String libname) {
+ String path = findLibrary0(libname);
+ System.out.println("findLibrary: " + libname + " => " + path);
+ return path;
+ }
+
+ private String[] getPossibleLibraryNames(String libname) {
+ if (OsUtils.isLinux64()) {
+ return new String[] {
+ libname,
+ System.mapLibraryName(libname + "64"),
+ System.mapLibraryName(libname)
+ };
+ } else {
+ return new String[] {
+ libname,
+ System.mapLibraryName(libname)
+ };
+ }
+ }
+
+ private String findLibrary0(String libname) {
+ String path;
+
+ // Get a list of potential library names
+ String[] libnames = getPossibleLibraryNames(libname);
+
+ // Try plugin base folder
+ if ((path = getPathIfExists(root, libnames)) != null) {
+ return path;
+ }
+
+ // Try plugin lib/ subfolder
+ File libFolder = new File(root, "lib");
+ if (libFolder.isDirectory()) {
+ if ((path = getPathIfExists(libFolder, libnames)) != null) {
+ return path;
+ }
+ }
+
+ // Pass off to the superclass
+ return super.findLibrary(libname);
+ }
+
+ private String getPathIfExists(File root, String[] filenames) {
+ for (String filename : filenames) {
+ File file = new File(root, filename);
+ if (file.isFile()) {
+ return file.getAbsolutePath();
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+package ie.dcu.plugin;
+
+public class PluginException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PluginException() {
+ }
+
+ public PluginException(String message) {
+ super(message);
+ }
+
+ public PluginException(Throwable cause) {
+ super(cause);
+ }
+
+ public PluginException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package ie.dcu.plugin;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+
+public class PluginLoader {
+ public static final String SPECFILE = "plugin.xml";
+
+ private final File root;
+ private final Map<String, String> metadata;
+ private final List<JarResource> jarFiles;
+ private URLClassLoader pluginClassLoader;
+
+ public PluginLoader(File root) {
+ this.root = root;
+ this.metadata = new HashMap<String, String>();
+ this.jarFiles = new ArrayList<JarResource>();
+
+ // Extract plugin name
+ String name = root.getName();
+ int idx = name.lastIndexOf('.');
+ if (idx > 1) {
+ name = name.substring(0, idx);
+ }
+ metadata.put("name", name);
+ }
+
+ public File getRootFolder() {
+ return root;
+ }
+
+ public File getSpecFile() {
+ return new File(root, SPECFILE);
+ }
+
+ public Map<String, String> getMetadata() {
+ return metadata;
+ }
+
+ public String getMetaValue(String key) {
+ return metadata.get(key);
+ }
+
+ public Plugin load() throws PluginException {
+ try {
+ loadMetadata();
+ loadJars();
+ return new Plugin(root, metadata, pluginClassLoader);
+ } catch (SAXException e) {
+ throw new PluginException(e);
+ } catch (IOException e) {
+ throw new PluginException(e);
+ }
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (pluginClassLoader == null) {
+ throw new IllegalStateException("load() not called");
+ }
+ return pluginClassLoader.loadClass(name);
+ }
+
+ private void addJarFile(String path) throws MalformedURLException {
+ jarFiles.add(new JarResource(root, path));
+ }
+
+ private void addMetadata(String key, String value) {
+ metadata.put(key, value);
+ }
+
+ private void loadMetadata() throws SAXException, IOException {
+ BufferedReader reader = new BufferedReader(
+ new FileReader(getSpecFile()));
+ try {
+ InputSource source = new InputSource(reader);
+ XMLReader xmlreader = XMLReaderFactory.createXMLReader();
+ xmlreader.setContentHandler(new SaxHandler());
+ xmlreader.parse(source);
+ } finally {
+ reader.close();
+ }
+ }
+
+ private void loadJars() {
+ URL[] jarUrls = new URL[jarFiles.size()];
+ int i = 0;
+ for (JarResource resource : jarFiles) {
+ jarUrls[i++] = resource.url;
+ }
+ ClassLoader parent = getClass().getClassLoader();
+ pluginClassLoader = new PluginClassLoader(root, jarUrls, parent);
+ }
+
+ private class SaxHandler extends DefaultHandler {
+ private Attributes attrs;
+ private String elem;
+
+ String getMandatoryAttribute(String name) throws SAXException {
+ String value = attrs.getValue(name);
+ if (value == null) {
+ exception("Missing mandatory attribute '%s'", name);
+ }
+ return value;
+ }
+
+ private void exception(String message, Object ... args)
+ throws SAXException {
+ String a = String.format(message, args);
+ String m = String.format("In element <%s>: %s", elem, a);
+ throw new SAXException(m);
+ }
+
+ public void startElement(String uri, String localName, String name,
+ Attributes attributes) throws SAXException {
+ elem = name;
+ attrs = attributes;
+ parseElem();
+ }
+
+ private void parseElem() throws SAXException {
+ if (elem.equals("meta")) {
+ String name = getMandatoryAttribute("name");
+ String value = getMandatoryAttribute("value");
+ addMetadata(name, value);
+ } else if (elem.equals("jar")) {
+ String path = getMandatoryAttribute("file");
+ try {
+ addJarFile(path);
+ } catch (MalformedURLException e) {
+ throw new SAXException(e);
+ }
+
+ } else {
+ // Ignore other elements
+ }
+ }
+ }
+}
+
+class JarResource {
+ public final File file;
+ public final URI uri;
+ public final URL url;
+
+ public JarResource(File root, String path) throws MalformedURLException {
+ if (new File(path).isAbsolute()) {
+ this.file = new File(path);
+ } else {
+ this.file = new File(root, path);
+ }
+ uri = file.toURI();
+ url = uri.toURL();
+ }
+}
+
--- /dev/null
+package ie.dcu.plugin;
+
+import java.io.File;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * I should probably use OSGi or something for this, but I couldn't be
+ * bothered...
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public class PluginManager {
+ private static final Logger log = Logger.getLogger("PluginManager");
+ private List<String> searchPath;
+ private List<Plugin> plugins;
+
+ public PluginManager() {
+ searchPath = new ArrayList<String>();
+ plugins = new ArrayList<Plugin>();
+ }
+
+ public List<String> searchPath() {
+ return searchPath;
+ }
+
+ public List<Plugin> plugins() {
+ return Collections.unmodifiableList(plugins);
+ }
+
+ public void loadPlugins() {
+ for (String folder : searchPath()) {
+ loadPluginsFrom(folder);
+ }
+ }
+
+ private void loadPluginsFrom(String path) {
+ log.info("Loading plugins from: " + path);
+ File file = new File(path);
+ if (file.isDirectory()) {
+ loadPluginFromFromFolder(file);
+ }
+ }
+
+ private void loadPluginFromFromFolder(File file) {
+ for (File f : file.listFiles()) {
+ if (f.isDirectory() && f.getName().endsWith(".plugin")) {
+ loadPlugin(f);
+ }
+ }
+ }
+
+ public boolean hasPlugin(File pluginFolder) {
+ String name = pluginFolder.getName();
+ for (Plugin p : plugins) {
+ if (p.getRootFolder().getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void loadPlugin(File pluginFile) {
+ if (!hasPlugin(pluginFile)) {
+ log.info("Loading plugin: " + pluginFile.getName());
+ PluginLoader loader = new PluginLoader(pluginFile);
+ try {
+ Plugin plugin = loader.load();
+ plugins.add(plugin);
+ } catch (PluginException ex) {
+ log.severe("Error loading plugin: " + ex.getMessage());
+ }
+ } else {
+ log.info("Skipping plugin (already loaded): " + pluginFile.getName());
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment;
+
+import ie.dcu.segment.annotate.*;
+import ie.dcu.segment.util.*;
+import ie.dcu.swt.SwtUtils;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+import java.util.zip.*;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Instances of this class manage the context of an interactive segmentation
+ * task. A segmentation context object is passed to an instance of
+ * {@link Segmenter} when the segmentation is initialized or updated. The
+ * segmentation context encapsulates the image being segmented, the current
+ * segmentation mask, the and the annotations that have been applied to the
+ * image.
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public class SegmentationContext {
+
+ /**
+ * The file extension given to a this object when it is saved on a disk
+ */
+ public static final String EXTENSION = ".ctx";
+
+ /**
+ * The original image data
+ */
+ private final ImageData imageData;
+
+ /**
+ * The file that the context was loaded from or last saved to... this is
+ * either the original image or a segmentation context file
+ */
+ private File file;
+
+ // lazy create (use getters for these fields)
+ private AnnotationManager annotations;
+ private SegmentationMask mask;
+ private Image image;
+ private Rectangle bounds;
+
+ /**
+ * Internal constructor to create an instance of the segmentation context
+ * from the given image data object and a file object.
+ *
+ * @param imageData
+ * The image data object.
+ * @param file
+ * The file from which the image data was loaded.
+ */
+ private SegmentationContext(ImageData imageData, File file) {
+ this.imageData = imageData;
+ this.file = file;
+ }
+
+ /**
+ * Returns <code>true</code> if the receiver is associated with an existing
+ * segmentation context file.
+ */
+ public boolean hasContextFile() {
+ return isContextFile(file);
+ }
+
+ /**
+ * Returns the file associated with the receiver. This is either the file
+ * that the receiver was last stored to, or the image file that was used to
+ * create the context.
+ */
+ public File getFile() {
+ return file;
+ }
+
+ /**
+ * Get the folder containing the file associated with the receiver.
+ *
+ * @see #getFile()
+ */
+ public File getFolder() {
+ return file.getParentFile();
+ }
+
+ /**
+ * Return an appropriate filename that can be used for saving the receiver.
+ *
+ * @see #save(File)
+ */
+ public String getDefaultFilename() {
+
+ String name = file.getName();
+ if (hasContextFile()) {
+ return name;
+ }
+ return FileUtils.replaceExtension(name, EXTENSION);
+ }
+
+ /**
+ * Returns the current annotations that have been applied to the image.
+ *
+ * These annotations can be converted to a raster using the
+ * {@link AnnotationRasterizer} utility class.
+ *
+ * @return An instance of {@link AnnotationManager}
+ */
+ public AnnotationManager getAnnotations() {
+ if (annotations == null) {
+ annotations = new AnnotationManager();
+ }
+ return annotations;
+ }
+
+ /**
+ * Returns the bounds of the current image and segmentation mask.
+ *
+ * @return An SWT Rectangle instance.
+ */
+ public Rectangle getBounds() {
+ if (bounds == null) {
+ bounds = new Rectangle(0, 0, imageData.width, imageData.height);
+ }
+ return bounds;
+ }
+
+ /**
+ * Returns the current segmentation mask.
+ *
+ * @return A {@link SegmentationMask} instance.
+ */
+ public SegmentationMask getMask() {
+ if (mask == null) {
+ mask = new SegmentationMask(getBounds());
+ }
+ return mask;
+ }
+
+ /**
+ * Returns the current image being segmented. This method returns an an SWT
+ * Image instance, constructing it if it is not already available. The
+ * {@link #getImageData()} method returns an SWT ImageData representation of
+ * the current image.
+ *
+ * @see #getImageData()
+ */
+ public Image getImage() {
+ if (image == null || image.isDisposed()) {
+ image = new Image(Display.getCurrent(), imageData);
+ }
+ return image;
+ }
+
+ /**
+ * Returns the image data. This is the preferred way of obtaining the image
+ * pixels since it will never create a new SWT Image instance. The utility
+ * classes {@link ImageArgbConverter} and {@link ImageByteConverter} can be
+ * used to convert the image data to a pixel array.
+ *
+ * @return An SWT ImageData instance.
+ */
+ public ImageData getImageData() {
+ return imageData;
+ }
+
+ /**
+ * Disposes of the SWT Image instance, if it exists. The Image instance will
+ * be recreated the next time {@link #getImage()} is called.
+ */
+ public void dispose() {
+ // Dispose image
+ if (image != null && !image.isDisposed()) {
+ image.dispose();
+ image = null;
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if the SWT Image managed by the receiver is
+ * disposed.
+ */
+ public boolean isDisposed() {
+ if (image != null) {
+ return image.isDisposed();
+ }
+ return true;
+ }
+
+ /**
+ * Create a new segmentation by loading the an image from the given file.
+ * JPG, PNG, GIF, and BMP images are supported.
+ *
+ * @param image
+ * An image file.
+ * @return A new segmentation context object.
+ * @throws IOException
+ * If the segmentation context cannot be created.
+ */
+ public static SegmentationContext create(File image) throws IOException {
+
+ // Create a new segmentation context
+ return new SegmentationContext(SwtUtils.loadImageData(image), image);
+ }
+
+ /**
+ * Save the receiver to the given file. The context can be loaded again at a
+ * later stage using the {@link #load(File)} method.
+ *
+ * <p>
+ * The saved context is essentially a ZIP file that contains a PNG
+ * representation of the current image, a PNG representation of the current
+ * mask file, and a binary representation of the current annotations.
+ * </p>
+ *
+ * @param file
+ * A file.
+ * @throws IOException
+ * If there is error saving the receiver to the given file.
+ */
+ public void save(File file) throws IOException {
+ // Based on zip file format
+ ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file));
+
+ try {
+ // Low (fast) compression.. most of data is PNG compressed anyway
+ out.setLevel(0);
+
+ // Create an entry for the image
+ ZipEntry entry = new ZipEntry("image.png");
+
+ // Start the entry
+ out.putNextEntry(entry);
+
+ // Store the image
+ SwtUtils.saveImage(getImageData(), out, SWT.IMAGE_PNG);
+
+ // End the entry
+ out.closeEntry();
+
+ // Create an entry for the mask
+ entry = new ZipEntry("mask.png");
+
+ // Start the entry
+ out.putNextEntry(entry);
+
+ // Save the mask
+ getMask().save(out);
+
+ // End the entry
+ out.closeEntry();
+
+ // Create an entry for the annotations
+ entry = new ZipEntry("markup.dat");
+
+ // Put next entry
+ out.putNextEntry(entry);
+
+ // Store the markup
+ getAnnotations().save(out);
+
+ // close entry
+ out.closeEntry();
+
+ } finally {
+
+ // Close
+ out.close();
+
+ // Set new filename
+ this.file = file;
+ }
+ }
+
+ /**
+ * Load a segmentation context object from the disk.
+ *
+ * @param file
+ * A file containing the saved context.
+ * @return A new segmentation context object.
+ * @throws IOException
+ * If there is an error loading the segmentation context file.
+ */
+ public static SegmentationContext load(File file) throws IOException {
+ ZipInputStream in = new ZipInputStream(new BufferedInputStream(
+ new FileInputStream(file)));
+
+ ImageData imageData = null;
+ SegmentationMask mask = null;
+ AnnotationManager annotations = null;
+
+ try {
+ ZipEntry entry;
+ while ((entry = in.getNextEntry()) != null) {
+
+ String name = entry.getName();
+
+ if (name.equals("image.png")) {
+ imageData = SwtUtils.loadImageData(in);
+
+ } else if (name.equals("mask.png")) {
+ mask = SegmentationMask.read(in);
+
+ } else if (name.equals("markup.dat")) {
+ annotations = new AnnotationManager();
+ annotations.load(in);
+ }
+
+ in.closeEntry();
+ }
+
+ } finally {
+ in.close();
+ }
+
+ if (imageData == null) {
+ throw new IOException("No image found in context file");
+ }
+
+ if (mask == null) {
+ throw new IOException("No mask found in context file");
+ }
+
+ if (annotations == null) {
+ throw new IOException("No annotations found in context file");
+ }
+
+ SegmentationContext ctx = new SegmentationContext(imageData, file);
+
+ ctx.mask = mask;
+ ctx.annotations = annotations;
+
+ return ctx;
+ }
+
+ /**
+ * Static method that checks if the given file appears to be a segmentation
+ * context file.
+ *
+ * @param file
+ * A file.
+ * @return <code>true</code> if the file appears to be a stored segmentation
+ * context file.
+ */
+ public static boolean isContextFile(File file) {
+ if (file != null) {
+ String name = file.getName().toLowerCase();
+ return name.endsWith(EXTENSION);
+ }
+ return false;
+ }
+
+ /**
+ * Add an annotation listener. This method simply forwards the call to the
+ * {@link AnnotationManager} class.
+ *
+ * @param listener
+ * An {@link AnnotationListener} instance.
+ */
+ public void addAnnotationListener(AnnotationListener listener) {
+ getAnnotations().addAnnotationListener(listener);
+ }
+
+ /**
+ * Remove an annotation listener. This method simply forwards the call to
+ * the {@link AnnotationManager} class.
+ *
+ * @param listener
+ * An {@link AnnotationListener} instance.
+ */
+ public void removeAnnotationListener(AnnotationListener listener) {
+ getAnnotations().removeAnnotationListener(listener);
+ }
+}
--- /dev/null
+package ie.dcu.segment;
+
+import ie.dcu.matrix.ByteMatrix;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+
+
+/**
+ * Class that represents a segmentation mask.
+ *
+ * A segmentation mask is a two dimensional matrix of byte values. It is used
+ * to represents which pixels in an image that are part of the foreground, and
+ * which are part of the background.
+ *
+ * @author Kevin McGuinness
+ */
+public final class SegmentationMask extends ByteMatrix {
+
+ /**
+ * Serialization UID
+ */
+ private static final long serialVersionUID = 4110412128756091726L;
+
+
+ /**
+ * Constant value for an unknown pixel.
+ */
+ public static final byte UNKNOWN = 0;
+
+
+ /**
+ * Constant value for a foreground pixel.
+ */
+ public static final byte FOREGROUND = 1;
+
+
+ /**
+ * Constant value of a background pixel.
+ */
+ public static final byte BACKGROUND = 2;
+
+
+ /**
+ * The width of the segmentation mask.
+ */
+ public final int width;
+
+
+ /**
+ * The height of the segmentation mask.
+ */
+ public final int height;
+
+
+ /**
+ * Create a segmentation mask using the specified dimensions. All pixels in
+ * the mask are initially set to {@link SegmentationMask#UNKNOWN}.
+ *
+ * @param width
+ * The width.
+ * @param height
+ * The height.
+ */
+ public SegmentationMask(int width, int height) {
+ super(height, width);
+ this.width = width;
+ this.height = height;
+ clear();
+ }
+
+
+ /**
+ * Create a segmentation mask using the specified dimensions. All pixels in
+ * the mask are initially set to {@link SegmentationMask#UNKNOWN}.
+ *
+ * @param bounds
+ * The bounds, only the width and height are used.
+ */
+ public SegmentationMask(Rectangle bounds) {
+ this(bounds.width, bounds.height);
+ }
+
+
+ /**
+ * Set all pixels in the mask to UNKNOWN.
+ */
+ public final void clear() {
+ fill(UNKNOWN);
+ }
+
+
+ /**
+ * Get the offset of pixel (x, y).
+ *
+ * @param x
+ * The x-coordinate.
+ * @param y
+ * The y-coordinate.
+ * @return The offset of pixel at (x, y).
+ */
+ public final int offsetOfPixel(int x, int y) {
+ return x + y * width;
+ }
+
+
+ /**
+ * Set the pixel at (x,y) to the given type. The type must be one of:
+ *
+ * <ul>
+ * <li> {@link SegmentationMask#FOREGROUND} </li>
+ * <li> {@link SegmentationMask#BACKGROUND} </li>
+ * <li> {@link SegmentationMask#UNKNOWN} </li>
+ * </ul>
+ *
+ * <b>Note:</b> This method does not check the type for correctness.
+ * <p>
+ *
+ * @param x
+ * The x-coordinate.
+ * @param y
+ * The y-coordinate.
+ * @param type
+ * The type.
+ */
+ public final void setPixel(int x, int y, byte type) {
+ values[x + y * width] = type;
+ }
+
+
+ /**
+ * Get the type of pixel at position (x, y).
+ *
+ * @see SegmentationMask#setPixel(int, int, byte)
+ *
+ * @param x
+ * The x-coordinate.
+ * @param y
+ * The y-coordinate.
+ * @return The type of pixel.
+ */
+ public final byte getPixel(int x, int y) {
+ return values[x + y * width];
+ }
+
+
+ /**
+ * Returns true if the mask doesn't contain any foreground
+ * or background pixels (All pixels are UNKNOWN)
+ *
+ * @return <code>true</code> for an unknown mask.
+ */
+ public final boolean isUnknown() {
+ for (int i = 0; i < size; i++) {
+ switch (values[i]) {
+ case FOREGROUND:
+ case BACKGROUND:
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Check if the image contains valid pixel values.
+ *
+ * @return <code>true</code> if the image contains valid pixel values.
+ */
+ public final boolean check() {
+ for (int i = 0; i < size; i++) {
+ switch (values[i]) {
+ case UNKNOWN:
+ case FOREGROUND:
+ case BACKGROUND:
+ continue;
+ default:
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Get the bounds of the mask.
+ *
+ * @return The bounds.
+ */
+ public final Rectangle getBounds() {
+ return new Rectangle(0, 0, width, height);
+ }
+
+
+ /**
+ * Returns a string representation of the mask.
+ */
+ public String toString() {
+ return String.format("SegmentationMask{ %d, %d }", width, height);
+ }
+
+
+ /**
+ * Save the segmentation mask as a PNG image.
+ *
+ * @param file
+ * The file to save it to.
+ * @throws IOException
+ * If the mask cannot be saved for some reason.
+ */
+ public void save(File file) throws IOException {
+
+ // Check if file is a directory
+ if (file.isDirectory()) {
+ throw new FileNotFoundException(String.format("%s is a directory", file));
+ }
+
+ // Check if we can write to file
+ if (file.exists()) {
+ if (!file.canWrite()) {
+ throw new IOException(String.format("Cannot write to %s", file));
+ }
+ }
+
+ // Check pixels are ok
+ if (!check()) {
+ throw new IOException("Image contains invalid pixels, cannot save");
+ }
+
+ // Create image loader
+ ImageLoader loader = new ImageLoader();
+
+ // Set loader data
+ loader.data = new ImageData[] { createImageData() };
+
+ try {
+ // Save image
+ loader.save(file.getAbsolutePath(), SWT.IMAGE_PNG);
+
+ } catch (SWTException e) {
+ // Change unchecked exception to checked one
+ throw new IOException("SWT Exception: " + e.getMessage());
+ }
+
+ // Done
+ return;
+ }
+
+ /**
+ * Store the segmentation mask to an output stream as a PNG image.
+ *
+ * @param out
+ * The output stream.
+ * @throws IOException
+ * If an error occurs while saving.
+ */
+ public void save(OutputStream out) throws IOException {
+
+ // Check pixels are ok
+ if (!check()) {
+ throw new IOException("Image contains invalid pixels, cannot save");
+ }
+
+ // Create image loader
+ ImageLoader loader = new ImageLoader();
+
+ // Set loader data
+ loader.data = new ImageData[] { createImageData() };
+
+ try {
+ // Save image
+ loader.save(out, SWT.IMAGE_PNG);
+
+ } catch (SWTException e) {
+ // Change unchecked exception to checked one
+ throw new IOException("SWT Exception: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Create an SWT ImageData object from the mask.
+ */
+ private ImageData createImageData() {
+ // Create palette
+ PaletteData palette = getPalette();
+
+ // Create 8 bit indexed image
+ ImageData data = new ImageData(width, height, 8, palette);
+
+ // Blit pixels
+ for (int y = 0; y < data.height; y++) {
+ int off1 = y * width;
+ int off2 = y * data.bytesPerLine;
+ System.arraycopy(this.values, off1, data.data, off2, width);
+ }
+
+ return data;
+ }
+
+
+ /**
+ * Load the mask from a PNG image.
+ *
+ * @param file
+ * The file to load from.
+ * @throws IOException
+ * If the mask cannot be loaded for some reason.
+ */
+ public void load(File file) throws IOException {
+ load(loadImageData(file));
+ }
+
+
+ /**
+ * Load the mask from a PNG image.
+ *
+ * @param file
+ * The file to load from.
+ * @return The mask
+ * @throws IOException
+ * If the mask cannot be loaded for some reason.
+ */
+ public static SegmentationMask read(File file) throws IOException {
+ // Load image data
+ ImageData data = loadImageData(file);
+
+ // Create compatible mask
+ SegmentationMask mask = new SegmentationMask(data.width, data.height);
+
+ // Load data into the mask
+ mask.load(data);
+
+ // Done
+ return mask;
+ }
+
+ /**
+ * Load the mask from a stream containing a PNG image.
+ *
+ * @param in
+ * The stream.
+ * @return The mask
+ * @throws IOException
+ * If the mask cannot be loaded for some reason.
+ */
+ public static SegmentationMask read(InputStream in) throws IOException {
+ // Load image data
+ ImageData data = loadImageData(in);
+
+ // Create compatible mask
+ SegmentationMask mask = new SegmentationMask(data.width, data.height);
+
+ // Load data into the mask
+ mask.load(data);
+
+ // Done
+ return mask;
+ }
+
+
+ private static ImageData loadImageData(File file) throws IOException {
+ // Ensure file exists
+ if (!file.exists()) {
+ throw new FileNotFoundException(String.format("%s does not exist", file));
+ }
+
+ ImageLoader loader = new ImageLoader();
+
+ // Load pixels
+ ImageData[] images;
+ try {
+ images = loader.load(file.getAbsolutePath());
+ } catch (SWTException e) {
+
+ // Change unchecked exception to checked one
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+
+ // Ensure we have at least one image
+ if (images.length < 1) {
+ throw new IOException(String.format("No images found in %s", file));
+ }
+
+ // Grab image data
+ return images[0];
+ }
+
+
+ private static ImageData loadImageData(InputStream in) throws IOException {
+ ImageLoader loader = new ImageLoader();
+
+ // Load pixels
+ ImageData[] images;
+ try {
+ images = loader.load(in);
+ } catch (SWTException e) {
+
+ // Change unchecked exception to checked one
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+
+ // Ensure we have at least one image
+ if (images.length < 1) {
+ throw new IOException(String.format("No images found in stream"));
+ }
+
+ // Grab image data
+ return images[0];
+ }
+
+
+ private void load(ImageData data) throws IOException {
+
+ // Check dimensions
+ if (width != data.width || height != data.height) {
+ throw new IOException(
+ String.format("Invalid dimensions (%d,%d)", data.width, data.height));
+ }
+
+ // Check if we have the original format
+ if (data.palette.isDirect || data.depth != 8) {
+
+ // Okay we definitely don't, do it the slow way
+ paletteTranslateLoad(data);
+
+ } else {
+
+ // Assume that we do have the original pixels and copy pixels
+ blitLoad(data);
+
+ // Test our assumption
+ if (!check()) {
+
+ // We were wrong
+ paletteTranslateLoad(data);
+ }
+ }
+
+ // Check pixels are valid
+ if (!check()) {
+ throw new IOException("Image contains invalid pixels");
+ }
+ }
+
+
+ private void blitLoad(ImageData data) {
+ for (int y = 0; y < data.height; y++) {
+ int off1 = y * data.bytesPerLine;
+ int off2 = y * width;
+ System.arraycopy(data.data, off1, this.values, off2, width);
+ }
+ }
+
+
+ private void paletteTranslateLoad(ImageData data) {
+ PaletteData palette = getPalette();
+
+ for (int y = 0; y < height; y++) {
+ int yoff = y * width;
+
+ for (int x = 0; x < width; x++) {
+
+ // Palette translation
+ int pel = data.getPixel(x, y);
+ RGB rgb = data.palette.getRGB(pel);
+
+ // Check for valid rgb, use background pixel on fail
+ int val = BACKGROUND;
+ for (int i = 0; i < palette.colors.length; i++) {
+ if (palette.colors[i].equals(rgb)) {
+ val = i;
+ break;
+ }
+ }
+
+ // Set
+ this.values[x+yoff] = (byte) val;
+ }
+ }
+ }
+
+
+ private PaletteData getPalette() {
+ RGB[] colors = new RGB[] {
+ new RGB(128,128,128),
+ new RGB(255, 255, 255),
+ new RGB(0,0,0)
+ };
+
+ // Create palette
+ return new PaletteData(colors);
+ }
+}
--- /dev/null
+package ie.dcu.segment;
+
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.segment.annotate.Annotation;
+import ie.dcu.segment.options.Option;
+import ie.dcu.segment.util.AbstractSegmenter;
+
+import java.util.Collection;
+
+/**
+ * The interface that interactive segmentation algorithms are required to
+ * implement.
+ *
+ * Algorithm implementors can alternatively extend the {@link AbstractSegmenter}
+ * class.
+ *
+ *
+ * @author Kevin McGuinness
+ * @version 1.1
+ */
+public interface Segmenter {
+
+ /**
+ * Implementors should return <code>true</code> if the segmentation is
+ * available on the current platform. If, for example, the segmentation
+ * algorithm requires native libraries, and there are no such libraries
+ * compiled for the current platform, this method should return
+ * <code>false</code>.
+ *
+ * @return <code>true</code> if the algorithm is available on this platform,
+ * <code>false</code> otherwise.
+ */
+ public boolean isAvailable();
+
+ /**
+ * Returns the name of the segmentation algorithm.
+ *
+ * @return The segmentation algorithm name. (never <code>null</code>).
+ */
+ public String getName();
+
+
+ /**
+ * Returns a short description of the segmentation algorithm.
+ *
+ * @return A description (never <code>null</code>).
+ */
+ public String getDescription();
+
+
+ /**
+ * Returns the name of the author or vendor.
+ *
+ * @return The vendor.
+ */
+ public String getVendor();
+
+
+ /**
+ * Returns <code>true</code> if the algorithm is "fast". Implementors should
+ * return <code>true</code> if a call to update typically takes less that a
+ * second. Otherwise <code>false</code> should be returned. If the speed varies
+ * or you are unsure, return <code>false</code>.
+ *
+ * @return <code>true</code> if the algorithm performs fast updates.
+ */
+ public boolean isFast();
+
+
+ /**
+ * Get the options (parameters) for the algorithm. This method may return an
+ * empty collection, but may not return <code>null</code>.
+ *
+ * @return A collection of configurable options.
+ */
+ public Collection<Option<?>> getOptions();
+
+
+ /**
+ * Initialize a new segmentation. This method is provided to
+ * allow algorithms to initialize any data structures needed
+ * for segmentation and to perform any pre-segmentation
+ * necessary.
+ *
+ * @param ctx
+ * The segmentation context.
+ */
+ public void init(SegmentationContext ctx);
+
+
+ /**
+ * Do a full update of the segmentation based on the current context.
+ *
+ * @param ctx
+ * The segmentation context.
+ */
+ public void update(SegmentationContext ctx);
+
+
+ /**
+ * Perform an incremental update when a single annotation is added to the
+ * context. This method is provided for algorithms that can improve
+ * performance by only updating based on the last annotation change.
+ *
+ * @param ctx
+ * The segmentation context.
+ * @param a
+ * The last annotation.
+ */
+ public void added(SegmentationContext ctx, Annotation a);
+
+
+ /**
+ * Perform an incremental update when a single annotation is removed from the
+ * context. This method is provided for algorithms that can improve
+ * performance by only updating based on the last annotation change.
+ *
+ * @param ctx
+ * The segmentation context.
+ * @param a
+ * The removed annotation.
+ */
+ public void removed(SegmentationContext ctx, Annotation a);
+
+
+ /**
+ * Perform any operations required before moving on to the next segmentation
+ * context. This method is provided to allow algorithms to dispose of any
+ * internal data structures or resources.
+ *
+ * @param ctx The segmentation context.
+ */
+ public void finish(SegmentationContext ctx);
+
+
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+import ie.dcu.swt.*;
+
+import java.util.*;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Class encapsulating a single image annotation.
+ *
+ * @author Kevin McGuinness
+ */
+public class Annotation {
+ private final AnnotationType type;
+ private final LinkedList<Point> points;
+ private Rectangle bounds;
+ private int lineWidth;
+
+ /**
+ * Create an annotation of the given type.
+ *
+ * @param type
+ * The annotation type.
+ */
+ public Annotation(AnnotationType type) {
+ this(type, 1);
+ }
+
+ /**
+ * Create an annotation of the given type and brush size.
+ *
+ * @param type
+ * The annotation type.
+ * @param width
+ * The brush size.
+ */
+ public Annotation(AnnotationType type, int width) {
+ this (type, width, null);
+ }
+
+ /**
+ * Create an annotation of the given type, brush size, and with
+ * the given initial point.
+ *
+ * @param type
+ * The annotation type.
+ * @param width
+ * The brush size.
+ * @param pt
+ * The initial point.
+ */
+ public Annotation(AnnotationType type, int width, Point pt) {
+ assert (type != null);
+ this.type = type;
+ points = new LinkedList<Point>();
+ setLineWidth(width);
+
+ if (pt != null) {
+ add(pt);
+ }
+ }
+
+
+ /**
+ * Set the annotation brush size.
+ *
+ * @param width
+ * The brush size (must be > 0).
+ */
+ public void setLineWidth(int width) {
+ assert (width > 0);
+ lineWidth = width;
+ }
+
+ /**
+ * Get the annotation brush size.
+ */
+ public int getLineWidth() {
+ return lineWidth;
+ }
+
+ /**
+ * Get the type of annotation.
+ */
+ public AnnotationType getType() {
+ return type;
+ }
+
+ /**
+ * Add a point to the annotation.
+ *
+ * @param pt
+ * A point
+ */
+ public void add(Point pt) {
+ // Add point
+ points.add(pt);
+
+ // Expand bounds
+ Rectangle m = new Rectangle(
+ pt.x-1, pt.y-1, 3, 3
+ );
+
+ if (bounds == null) {
+ bounds = SwtUtils.clone(m);
+ } else {
+ bounds.add(m);
+ }
+ }
+
+ /**
+ * Returns the last point in the annotation, or <code>null</code> if the
+ * annotation has no points.
+ */
+ public Point last() {
+ return points.getLast();
+ }
+
+ /**
+ * Returns the first point in the annotation, or <code>null</code> if the
+ * annotation has no points.
+ */
+ public Point first() {
+ return points.getFirst();
+ }
+
+ /**
+ * Render the points onto an {@link ObservableImage} instance.
+ *
+ * @param image
+ * An {@link ObservableImage}.
+ */
+ public void paint(ObservableImage image) {
+ GC gc = image.beginPaint();
+ paint(gc, true);
+ image.endPaint(getBounds());
+ }
+
+ /**
+ * Render the annotations onto an SWT image instance.
+ *
+ * @param image
+ * An image.
+ * @param antialias
+ * Flag indicating whether antialiasing should be done.
+ */
+ public void paint(Image image, boolean antialias) {
+ GC gc = new GC(image);
+ paint(gc, antialias);
+ gc.dispose();
+ }
+
+ /**
+ * Render the annotation onto the graphics context.
+ *
+ * @param gc
+ * A graphics context.
+ * @param antialias
+ * Flag indicating whether antialiasing should be done.
+ */
+ public void paint(GC gc, boolean antialias) {
+ int npoints = points.size();
+ if (npoints == 0) return;
+
+ if (antialias) {
+ gc.setAntialias(SWT.ON);
+ }
+ gc.setLineCap(SWT.CAP_ROUND);
+ gc.setLineJoin(SWT.JOIN_ROUND);
+
+ gc.setLineWidth(lineWidth);
+ gc.setForeground(type.getColor());
+
+ Point last = null;
+
+ // Ensure single point or zero length lines are drawn
+ switch (npoints) {
+ case 1:
+ case 2:
+ Point pt = points.get(0);
+ last = new Point(pt.x+1, pt.y);
+ }
+
+ // Draw points
+ for (Point p : points) {
+ if (last != null) {
+ gc.drawLine(last.x, last.y, p.x, p.y);
+ }
+
+ last = p;
+ }
+ }
+
+ /**
+ * Returns a rectangle that encloses all pixels that this annotation
+ * represents. If the annotation is empty, returns <code>null</code>.
+ */
+ public Rectangle getBounds() {
+ if (bounds != null) {
+ return new Rectangle(
+ bounds.x - lineWidth / 2,
+ bounds.y - lineWidth / 2,
+ bounds.width + lineWidth,
+ bounds.height + lineWidth
+ );
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns an unmodifiable list of points for this annotation
+ */
+ public List<Point> points() {
+ return Collections.unmodifiableList(points);
+ }
+
+ /**
+ * Returns the number of points in this annotation.
+ */
+ public int count() {
+ return points.size();
+ }
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+/**
+ * Class to simplify implementation of annotation listeners. For use when the
+ * listener is only interested in changes in the generic sense.
+ *
+ * @see AnnotationListener
+ * @author Kevin McGuinness
+ */
+public abstract class AnnotationAdapter implements AnnotationListener {
+
+ /**
+ * Forwards call to {@link #annotationsChanged(AnnotationEvent)}
+ */
+ public void annotationPerformed(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+ /**
+ * Forwards call to {@link #annotationsChanged(AnnotationEvent)}
+ */
+ public void annotationRedone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+ /**
+ * Forwards call to {@link #annotationsChanged(AnnotationEvent)}
+ */
+ public void annotationUndone(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+ /**
+ * Forwards call to {@link #annotationsChanged(AnnotationEvent)}
+ */
+ public void annotationsCleared(AnnotationEvent e) {
+ annotationsChanged(e);
+ }
+
+ /**
+ * Called when any changes to the annotations occur.
+ *
+ * @param e The {@link AnnotationEvent} object.
+ */
+ public abstract void annotationsChanged(AnnotationEvent e);
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+/**
+ * Annotation event class
+ *
+ * @see AnnotationListener
+ * @author Kevin McGuinness
+ */
+public class AnnotationEvent {
+ public final AnnotationManager manager;
+ public final Annotation annotation;
+ public final Type type;
+
+
+ public AnnotationEvent(
+ AnnotationManager manager,
+ Annotation annotation,
+ Type type
+ ) {
+ this.manager = manager;
+ this.annotation = annotation;
+ this.type = type;
+ }
+
+ /**
+ * Event type
+ */
+ public enum Type {
+ Undone,
+ Redone,
+ Added,
+ Cleared
+ }
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+/**
+ * Interface for classes interested in annotation events.
+ *
+ * @see AnnotationEvent
+ * @author Kevin McGuinness
+ */
+public interface AnnotationListener {
+ public void annotationPerformed(AnnotationEvent e);
+ public void annotationUndone(AnnotationEvent e);
+ public void annotationRedone(AnnotationEvent e);
+ public void annotationsCleared(AnnotationEvent e);
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+import ie.dcu.swt.ObservableImage;
+
+import java.io.*;
+import java.util.*;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Class that manages a set of image annotations.
+ *
+ * @author Kevin McGuinness
+ */
+public class AnnotationManager {
+ private static final String FILE_TAG = "Annotations";
+ private final LinkedList<Annotation> annotations;
+ private final LinkedList<Annotation> redos;
+ private final List<AnnotationListener> listeners;
+
+ public AnnotationManager() {
+ annotations = new LinkedList<Annotation>();
+ redos = new LinkedList<Annotation>();
+ listeners = new ArrayList<AnnotationListener>(2);
+ }
+
+
+ public void add(Annotation a) {
+ annotations.addLast(a);
+ redos.clear();
+ fireAnnotationPerformed(a);
+ }
+
+
+ public boolean canUndo() {
+ return !annotations.isEmpty();
+ }
+
+
+ public boolean canRedo() {
+ return !redos.isEmpty();
+ }
+
+
+ public void undo() {
+ if (canUndo()) {
+ Annotation a = annotations.removeLast();
+ redos.addFirst(a);
+ fireAnnotationUndone(a);
+ }
+ }
+
+
+ public void redo() {
+ if (canRedo()) {
+ Annotation a = redos.removeFirst();
+ annotations.addLast(a);
+ fireAnnotationRedone(a);
+ }
+ }
+
+
+ public void clear() {
+ if (annotations.size() > 0) {
+ annotations.clear();
+ redos.clear();
+ fireAnnotationsCleared();
+ }
+ }
+
+
+ public Annotation getUndoable() {
+ if (!canUndo()) {
+ return null;
+ }
+ return annotations.getLast();
+ }
+
+
+ public Annotation getRedoable() {
+ if (!canRedo()) {
+ return null;
+ }
+ return redos.getFirst();
+ }
+
+
+ public Annotation last() {
+ if (!annotations.isEmpty()) {
+ return annotations.getLast();
+ }
+ return null;
+ }
+
+
+ public List<Annotation> annotations() {
+ return Collections.unmodifiableList(annotations);
+ }
+
+
+ public int count() {
+ return annotations.size();
+ }
+
+
+ public int countForeground() {
+ int count = 0;
+ for (Annotation a : annotations) {
+ if (a.getType() == AnnotationType.Foreground) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+
+ public int countBackground() {
+ int count = 0;
+ for (Annotation a : annotations) {
+ if (a.getType() == AnnotationType.Background) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+
+ public boolean hasForegroundAnnotation() {
+ for (Annotation a : annotations) {
+ if (a.getType() == AnnotationType.Foreground) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public boolean hasBackgroundAnnotation() {
+ for (Annotation a : annotations) {
+ if (a.getType() == AnnotationType.Background) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public void paint(ObservableImage image) {
+ for (Annotation a : annotations) {
+ a.paint(image);
+ }
+ }
+
+
+ public void paint(GC gc, boolean antialias) {
+ for (Annotation a : annotations) {
+ a.paint(gc, antialias);
+ }
+ }
+
+
+ public void paint(Image image, boolean antialias) {
+ for (Annotation a : annotations) {
+ a.paint(image, antialias);
+ }
+ }
+
+
+ public Rectangle getBounds() {
+ Rectangle rect = new Rectangle(0,0,0,0);
+ for (Annotation a : annotations) {
+ rect.add(a.getBounds());
+ }
+ return rect;
+ }
+
+
+ public void addAnnotationListener(AnnotationListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void removeAnnotationListener(AnnotationListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ private void fireAnnotationsCleared() {
+ AnnotationEvent e = null;
+ for (AnnotationListener listener : listeners) {
+ if (e == null) {
+ e = new AnnotationEvent(this, null, AnnotationEvent.Type.Cleared);
+ }
+ listener.annotationsCleared(e);
+ }
+ }
+
+
+ private void fireAnnotationPerformed(Annotation a) {
+ AnnotationEvent e = null;
+ for (AnnotationListener listener : listeners) {
+ if (e == null) {
+ e = new AnnotationEvent(this, a, AnnotationEvent.Type.Added);
+ }
+ listener.annotationPerformed(e);
+ }
+ }
+
+
+ private void fireAnnotationUndone(Annotation a) {
+ AnnotationEvent e = null;
+ for (AnnotationListener listener : listeners) {
+ if (e == null) {
+ e = new AnnotationEvent(this, a, AnnotationEvent.Type.Undone);
+ }
+ listener.annotationUndone(e);
+ }
+ }
+
+
+ private void fireAnnotationRedone(Annotation a) {
+ AnnotationEvent e = null;
+ for (AnnotationListener listener : listeners) {
+ if (e == null) {
+ e = new AnnotationEvent(this, a, AnnotationEvent.Type.Redone);
+ }
+ listener.annotationRedone(e);
+ }
+ }
+
+
+ public void save(File file) throws IOException {
+ FileOutputStream out = new FileOutputStream(file);
+
+ try {
+ save(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public void save(OutputStream o) throws IOException {
+
+ DataOutputStream out = new DataOutputStream(
+ new BufferedOutputStream(o));
+
+ // Write tag and count
+ out.writeUTF(FILE_TAG);
+ out.writeInt(annotations.size());
+
+ for (Annotation a : annotations) {
+
+ // Write parameters
+ out.writeInt(a.getType().ordinal());
+ out.writeInt(a.getLineWidth());
+ out.writeInt(a.points().size());
+
+ // Write points
+ for (Point p : a.points()) {
+ out.writeInt(p.x);
+ out.writeInt(p.y);
+ }
+ }
+
+ // Flush
+ out.flush();
+ }
+
+ public void load(File file) throws IOException {
+
+ FileInputStream in = new FileInputStream(file);
+
+ try {
+ load(in);
+ } finally {
+ in.close();
+ }
+ }
+
+ public void load(InputStream is) throws IOException {
+
+ // In case container is being reused, clear old data first
+ annotations.clear();
+ redos.clear();
+
+ // Create and buffer data input stream
+ DataInputStream in = new DataInputStream(
+ new BufferedInputStream(is)
+ );
+
+ // Check tag
+ String tag = in.readUTF();
+ if (!tag.equals(FILE_TAG)) {
+ throw new IOException("Unrecognized file format");
+ }
+
+ // Read annotations
+ int n = in.readInt();
+ for (int i = 0; i < n; i++) {
+
+ // Read parameters
+ int typeid = in.readInt();
+ int lineWidth = in.readInt();
+ int npoints = in.readInt();
+
+
+ // Create annotation
+ AnnotationType type = AnnotationType.valueOf(typeid);
+ Annotation a = new Annotation(type, lineWidth);
+
+
+ // Read points
+ for (int j = 0; j < npoints; j++) {
+ int x = in.readInt();
+ int y = in.readInt();
+ a.add(new Point(x, y));
+ }
+
+ //Add annotation
+ add(a);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.annotate;
+
+import org.eclipse.jface.resource.*;
+import org.eclipse.jface.util.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Annotation type enumeration.
+ *
+ * The color of the foreground/background brushes is stored in the JFace
+ * ColorRegistry using the {@link #key} value to generate the key. If changed
+ * the {@link #getColor()} method will return the new color. will return the new
+ * color.
+ *
+ * @author Kevin McGuinness
+ *
+ */
+public enum AnnotationType implements IPropertyChangeListener {
+ // Ordinal used for serialization, don't change order
+ Foreground,
+ Background;
+
+
+ /**
+ * Default foreground color.
+ */
+ public static final RGB DEFAULT_FOREGROUND = new RGB(255,0,0);
+
+
+ /**
+ * Default background color.
+ */
+ public static final RGB DEFAULT_BACKGROUND = new RGB(0,0,255);
+
+
+ /**
+ * Annotation color registry key.
+ */
+ public final String key;
+
+
+ /**
+ * Annotation brush color.
+ */
+ private Color color;
+
+
+ /**
+ * Initializer
+ */
+ private AnnotationType() {
+ key = String.format("%s#%s", getClass().getName(), name());
+ }
+
+
+ /**
+ * Get the color of the annotation brush.
+ *
+ * @return The annotation brush color.
+ */
+ public Color getColor() {
+ if (color == null) {
+
+ // Get registry
+ ColorRegistry cr = JFaceResources.getColorRegistry();
+
+
+ // Set default
+ if (!cr.hasValueFor(key)) {
+ switch (this) {
+ case Foreground:
+ cr.put(key, DEFAULT_FOREGROUND);
+ break;
+ default:
+ cr.put(key, DEFAULT_BACKGROUND);
+ }
+ }
+
+ cr.addListener(this);
+
+ // Get color
+ color = cr.get(key);
+ }
+
+ return color;
+ }
+
+
+ /**
+ * Returns the annotation type, inverted.
+ *
+ * @return The opposite annotation type.
+ */
+ public AnnotationType invert() {
+ switch (this) {
+ case Foreground:
+ return Background;
+ case Background:
+ return Foreground;
+ }
+ throw new Error();
+ }
+
+
+ /**
+ * Returns the annotation type corresponding to the given ordinal.
+ *
+ * @param ordinal
+ * The ordinal.
+ * @return The annotation type.
+ * @throws IllegalArgumentException
+ * If the given ordinal value is out of range.
+ */
+ public static AnnotationType valueOf(int ordinal) throws IllegalArgumentException {
+ AnnotationType[] values = values();
+ if (ordinal < 0 || ordinal >= values.length) {
+ throw new IllegalArgumentException("No such ordinal value: " + ordinal);
+ }
+ return values[ordinal];
+ }
+
+
+ /**
+ * Listen for changes in the color registry
+ */
+ public void propertyChange(PropertyChangeEvent e) {
+ if (e.getProperty().equals(key)) {
+ JFaceResources.getColorRegistry().removeListener(this);
+ // Flag property needs reloading
+ color = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * Abstract implementation of the {@link Option} interface.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractOption<T> implements Option<T> {
+ private final OptionType type;
+ private final String name;
+ private final String description;
+ private final T defaultValue;
+ private T value;
+
+
+ protected AbstractOption(
+ OptionType type, String name, String description, T defaultValue) {
+
+ // Preconditions
+ assert (type != null);
+ assert (name != null);
+ assert (description != null);
+ assert (defaultValue != null);
+
+ // Assign
+ this.type = type;
+ this.name = name;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.value = defaultValue;
+ }
+
+
+ public OptionType getType() {
+ return type;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+
+ public String getDescription() {
+ return description;
+ }
+
+
+ public T getDefaultValue() {
+ return defaultValue;
+ }
+
+
+ public T getValue() {
+ return value;
+ }
+
+
+ public void setValue(Object value) {
+ if (value == null) {
+ this.value = defaultValue;
+ } else {
+ Conversion<T> conv = convert(value);
+ if (conv.isOk()) {
+ this.value = conv.getResult();
+ } else {
+ this.value = defaultValue;
+ }
+ }
+ }
+
+
+ public T[] values() {
+ return null;
+ }
+
+
+ protected Conversion<T> valid(T result) {
+ return new Conversion<T>(result, null);
+ }
+
+
+ protected Conversion<T> invalid(String message) {
+ return new Conversion<T>(null, message);
+ }
+
+
+ protected Conversion<T> invalid(String format, Object... args) {
+ return new Conversion<T>(null, String.format(format, args));
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * The result of an attempted conversion.
+ *
+ * @author Kevin McGuinness
+ */
+public class Conversion<T> {
+ private final String message;
+ private final T result;
+
+ /**
+ * Create a conversion object with the given message and result. The message
+ * should be null if the conversion was valid.
+ *
+ * @param result
+ * The result, or <code>null</code>.
+ * @param message
+ * A message, or <code>null</code>.
+ */
+ public Conversion(T result, String message) {
+ this.message = message;
+ this.result = result;
+ }
+
+ /**
+ * Returns <code>true</code> of the conversion went OK.
+ */
+ public boolean isOk() {
+ return message == null;
+ }
+
+ /**
+ * Returns an error message if the conversion failed.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns the result of the conversion if it succeeded.
+ */
+ public T getResult() {
+ return result;
+ }
+
+ public String toString() {
+ if (isOk()) {
+ return String.format("Conversion [ value = %s ]", result);
+ } else {
+ return String.format("Conversion [ error = %s ]", message);
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * A decimal (floating point) segmentation algorithm parameter.
+ *
+ * @author Kevin McGuinness
+ */
+public class DecimalOption extends AbstractOption<Double> {
+ private final double min;
+ private final double max;
+
+
+ /**
+ * Create a decimal option with the given name, description, and default
+ * value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ */
+ public DecimalOption(String name, String description, double def) {
+ this(name, description, def, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ }
+
+ /**
+ * Create a decimal option with the given name, description, default
+ * value, minimum allowable value, and maximum allowable value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ * @param min
+ * The minimum allowable value for the option
+ * @param max
+ * The maximum allowable value for the option
+ */
+ public DecimalOption(String name, String description, double def, double min, double max) {
+ super(OptionType.Decimal, name, description, def);
+ this.min = min;
+ this.max = max;
+ }
+
+ public Conversion<Double> convert(Object value) {
+ if (value instanceof Double) {
+ return valid((Double) value);
+ } else if (value != null) {
+ try {
+ return checkRange(new Double(value.toString().trim()));
+ } catch (NumberFormatException e) {
+ // Invalid
+ String mesg = e.getLocalizedMessage();
+ return invalid("%s is not an decimal number: %s", getName(), mesg);
+ }
+ }
+ return invalid("value is null");
+ }
+
+
+ private Conversion<Double> checkRange(double val) {
+ if (val >= min && val <= max) {
+ // OK
+ return valid(val);
+
+ }
+
+ // Out of range
+ String format = "%s must be in ther range (%f, %f), value is %f";
+ return invalid(format, getName(), min, max, val);
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * A segmentation algorithm parameter that can assume one of a set of predefined
+ * values.
+ *
+ * @author Kevin McGuinness
+ */
+public class EnumOption<T extends Enum<T>> extends AbstractOption<T> {
+ private Class<T> clazz;
+
+
+ /**
+ * Create an enum option.
+ *
+ * @param clazz
+ * The class of the enum.
+ * @param name
+ * The option name.
+ * @param description
+ * The option description.
+ * @param def
+ * The default value.
+ */
+ public EnumOption(Class<T> clazz, String name, String description, T def) {
+ super(OptionType.Enumeration, name, description, def);
+ this.clazz = clazz;
+ }
+
+
+ public T[] values() {
+ return clazz.getEnumConstants();
+ }
+
+
+ public Conversion<T> convert(Object value) {
+ if (clazz.isInstance(value)) {
+ return valid(clazz.cast(value));
+ } else if (value != null) {
+
+ String str = value.toString().trim();
+ Conversion<T> conv;
+ try {
+ conv = valid(Enum.valueOf(clazz, str));
+ } catch (IllegalArgumentException e) {
+ conv = invalid("Invalid value \"%s\" for %s", value, getName());
+ }
+
+ if (!conv.isOk()) {
+ // Try convert from an integer
+ try {
+ int x = Integer.parseInt(str);
+ T[] v = values();
+ if (x > 0 && x < v.length) {
+ return valid(v[x]);
+ }
+
+ } catch (NumberFormatException e) {
+ // Ignore .. we already have an error message from the string conversion
+ }
+ }
+ }
+ return invalid("%s is null", getName());
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * An integer segmentation algorithm parameter.
+ *
+ * @author Kevin McGuinness
+ */
+public class IntegerOption extends AbstractOption<Integer> {
+ private final int min;
+ private final int max;
+
+
+ /**
+ * Create ain integer option with the given name, description, and default
+ * value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ */
+ public IntegerOption(String name, String description, int def) {
+ this(name, description, def, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+
+ /**
+ * Create an integer option with the given name, description, default
+ * value, minimum allowable value, and maximum allowable value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ * @param min
+ * The minimum allowable value for the option
+ * @param max
+ * The maximum allowable value for the option
+ */
+ public IntegerOption(String name, String description, int def, int min, int max) {
+ super(OptionType.Integer, name, description, def);
+ this.min = min;
+ this.max = max;
+ }
+
+
+ public Conversion<Integer> convert(Object value) {
+ if (value instanceof Integer) {
+ return checkRange((Integer) value);
+ } else if (value != null) {
+
+ try {
+ return checkRange(new Integer(value.toString().trim()));
+ } catch (NumberFormatException e) {
+ // Invalid
+ String mesg = e.getLocalizedMessage();
+ return invalid("%s is not an integer: %s", getName(), mesg);
+ }
+ }
+ return invalid("%s is null", getName());
+ }
+
+
+ private Conversion<Integer> checkRange(int val) {
+ if (val >= min && val <= max) {
+ // OK
+ return valid(val);
+
+ }
+
+ // Out of range
+ String format = "%s must be in ther range (%d, %d), value is %d";
+ return invalid(format, getName(), min, max, val);
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * Interface for a segmentation algorithm parameter.
+ *
+ * @author Kevin McGuinness
+ */
+public interface Option<T> {
+
+ /**
+ * Return the option type
+ */
+ public OptionType getType();
+
+ /**
+ * Try to convert a object to the required type for the option.
+ *
+ * @param value
+ * An object.
+ * @return A conversion object containing the result or an error message.
+ */
+ public Conversion<T> convert(Object value);
+
+ /**
+ * Set the option to the given object value.
+ *
+ * @param value
+ * A value.
+ */
+ public void setValue(Object value);
+
+ /**
+ * Get the current value.
+ */
+ public T getValue();
+
+ /**
+ * Get the default option value.
+ */
+ public T getDefaultValue();
+
+ /**
+ * Returns all allowable values, or <code>null</code>.
+ */
+ public T[] values();
+
+ /**
+ * Return the option name.
+ */
+ public String getName();
+
+ /**
+ * Return a description of the option
+ */
+ public String getDescription();
+}
+
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * Enumeration of segmentation algorithm parameter types.
+ *
+ * @author Kevin McGuinness
+ */
+public enum OptionType {
+
+ /**
+ * A textual (string) option.
+ *
+ * @see TextOption
+ */
+ Text,
+
+ /**
+ * An integer option
+ *
+ * @see IntegerOption
+ */
+ Integer,
+
+ /**
+ * A decimal option
+ *
+ * @see DecimalOption
+ */
+ Decimal,
+
+ /**
+ * A toggle option
+ *
+ * @see ToggleOption
+ */
+ Toggle,
+
+ /**
+ * An enumerative option
+ *
+ * @see EnumOption
+ */
+ Enumeration;
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+import ie.dcu.segment.Segmenter;
+
+import java.util.*;
+import java.util.List;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Dynamically builds a dialog box for the segmentation algorithms options.
+ *
+ * @author Kevin McGuinness
+ */
+public class SegmenterOptionDialog extends TitleAreaDialog {
+ private final List<OptionEditor> editors;
+ private final Segmenter segmenter;
+
+
+ public SegmenterOptionDialog(Shell parent, Segmenter segmenter) {
+ super(parent);
+
+ // Setup final fields
+ this.editors = new LinkedList<OptionEditor>();
+ this.segmenter = segmenter;
+
+ // Make it resizable
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ }
+
+
+ @Override
+ protected void okPressed() {
+ // Apply changes
+ for (OptionEditor editor : editors) {
+ editor.apply();
+ }
+
+ // Close
+ super.okPressed();
+ }
+
+
+ @Override
+ protected Control createContents(Composite parent) {
+ Control contents = super.createContents(parent);
+
+ // Set dialog title
+ getShell().setText("Segmenter Options");
+
+ // Set title area title
+ setTitle("Configure Segmenter Options");
+
+ // Set message
+ setMessage(String.format("Configure parameters for %s",
+ segmenter.getName()));
+
+ // Done
+ return contents;
+ }
+
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ // Create scrolled area
+ ScrolledComposite scroller = new ScrolledComposite(composite,
+ SWT.H_SCROLL | SWT.V_SCROLL);
+
+ // Configure scrolled area
+ scroller.setLayoutData(new GridData(GridData.FILL_BOTH));
+ scroller.setExpandHorizontal(true);
+ scroller.setExpandVertical(true);
+ scroller.setAlwaysShowScrollBars(false);
+
+ // Create content
+ Composite content = new Composite(scroller, SWT.NONE);
+
+ // Create content layout
+ GridLayout layout = new GridLayout();
+ layout.marginTop = layout.marginBottom = 10;
+ layout.marginLeft = layout.marginRight = 5;
+ layout.numColumns = 2;
+ content.setLayout(layout);
+
+ // Create listener
+ StatusListener listener = new StatusListener();
+
+ // Add options
+ for (Option<?> opt : segmenter.getOptions()) {
+
+ // Create option label
+ Label label = new Label(content, SWT.NONE);
+ label.setText(opt.getName());
+ label.setToolTipText(opt.getDescription());
+
+ // Create editor layout
+ GridData data = new GridData();
+ data.horizontalIndent = 25;
+ data.grabExcessHorizontalSpace = true;
+ data.horizontalAlignment = SWT.FILL;
+
+ // Create editor
+ OptionEditor editor = createEditor(content, opt);
+ editor.getControl().setLayoutData(data);
+ editor.addConversionListener(listener);
+ }
+
+ // Set scroll area content
+ scroller.setContent(content);
+
+ // Compute scroll area min size
+ scroller.setMinSize(content.computeSize(
+ SWT.DEFAULT, SWT.DEFAULT)
+ );
+
+ // Done
+ return composite;
+ }
+
+
+ @Override
+ protected Point getInitialSize() {
+ return new Point(450,350);
+ }
+
+
+ public OptionEditor createEditor(Composite parent, Option<?> option) {
+ OptionEditor editor;
+
+ // Use an combo editor for enums, otherwise use a text editor
+ switch (option.getType()) {
+ case Enumeration:
+ editor = new ComboEditor(parent, option);
+ break;
+ case Toggle:
+ editor = new ToggleEditor(parent, option);
+ break;
+ default:
+ editor = new TextEditor(parent, option);
+ }
+
+ editors.add(editor);
+ return editor;
+ }
+
+
+ /**
+ * Updates error message and OK button status when a conversion occurs.
+ *
+ * @param conversion
+ * The conversion.
+ */
+ private void handleConversion(Conversion<?> conversion) {
+ boolean ok = false;
+
+ if (conversion.isOk()) {
+ // Check all others
+ Conversion<?> conv = null;
+ for (OptionEditor editor : editors) {
+ Conversion<?> current = editor.convert();
+ if (!current.isOk()) {
+ conv = current;
+ break;
+ }
+ }
+ if (conv == null) {
+ setErrorMessage(null);
+ ok = true;
+ } else {
+ setErrorMessage(conv.getMessage());
+ }
+ } else {
+
+ setErrorMessage(conversion.getMessage());
+ }
+
+ getButton(IDialogConstants.OK_ID).setEnabled(ok);
+ }
+
+
+ /**
+ * Updates error message when edits and focus changes occur.
+ */
+ private final class StatusListener implements ConversionListener {
+ public void conversionPerformed(Conversion<?> conversion) {
+ handleConversion(conversion);
+ }
+ }
+}
+
+
+
+
+interface ConversionListener {
+ public void conversionPerformed(Conversion<?> conversion);
+}
+
+
+interface OptionEditor {
+ public Control getControl();
+ public void addConversionListener(ConversionListener listener);
+ public void removeConversionListener(ConversionListener listener);
+ public void apply();
+ public Conversion<?> convert();
+}
+
+
+abstract class AbstractEditor implements OptionEditor {
+ private final List<ConversionListener> listeners;
+ protected final Option<?> option;
+ protected final Control control;
+
+ public AbstractEditor(Composite parent, Option<?> option) {
+ this.listeners = new ArrayList<ConversionListener>(1);
+ this.option = option;
+ this.control = createControl(parent, option);
+ }
+
+
+ public Control getControl() {
+ return control;
+ }
+
+
+ public void addConversionListener(ConversionListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void removeConversionListener(ConversionListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ protected void fireConversionPerformed(Conversion<?> conversion) {
+ for (ConversionListener listener : listeners) {
+ listener.conversionPerformed(conversion);
+ }
+ }
+
+
+ protected abstract Control createControl(
+ Composite parent, Option<?> option
+ );
+}
+
+
+class TextEditor extends AbstractEditor {
+ private Text text;
+
+
+ public TextEditor(Composite parent, Option<?> option) {
+ super(parent, option);
+ }
+
+
+ @Override
+ protected Control createControl(Composite parent, Option<?> opt) {
+ text = new Text(parent, SWT.BORDER);
+ text.setToolTipText(opt.getDescription());
+ text.setText(opt.getValue().toString());
+
+ Verifier verifier = new Verifier();
+ text.addModifyListener(verifier);
+ text.addFocusListener(verifier);
+
+ return text;
+ }
+
+
+ public void apply() {
+ option.setValue(text.getText());
+ }
+
+
+ public Conversion<?> convert() {
+ return option.convert(text.getText());
+ }
+
+
+ private final class Verifier extends FocusAdapter implements ModifyListener {
+ public void focusLost(FocusEvent e) {
+ fireConversionPerformed(convert());
+ }
+
+
+ public void modifyText(ModifyEvent e) {
+ fireConversionPerformed(convert());
+ }
+ }
+}
+
+
+class ComboEditor extends AbstractEditor {
+ private Combo combo;
+ private Object[] values;
+
+
+ public ComboEditor(Composite parent, Option<?> option) {
+ super(parent, option);
+ }
+
+
+ @Override
+ protected Control createControl(Composite parent, Option<?> opt) {
+ values = opt.values();
+
+ combo = new Combo(parent, SWT.READ_ONLY);
+ combo.setToolTipText(opt.getDescription());
+
+ for (Object v : values) {
+ combo.add(v.toString());
+ }
+
+ combo.setText(opt.getValue().toString());
+ return combo;
+ }
+
+
+ public void apply() {
+ int idx = combo.getSelectionIndex();
+ option.setValue(values[idx]);
+ }
+
+
+ public Conversion<?> convert() {
+ int idx = combo.getSelectionIndex();
+ return option.convert(values[idx]);
+ }
+}
+
+
+class ToggleEditor extends AbstractEditor {
+ private Button check;
+
+
+ public ToggleEditor(Composite parent, Option<?> option) {
+ super(parent, option);
+ }
+
+
+ @Override
+ protected Control createControl(Composite parent, Option<?> opt) {
+ check = new Button(parent, SWT.CHECK);
+ Boolean value = (Boolean) opt.getValue();
+ check.setSelection(value);
+ check.addSelectionListener(new ToggleListener());
+ updateText();
+ return check;
+ }
+
+
+ private void updateText() {
+ boolean selection = check.getSelection();
+ check.setText(selection ? "Yes" : "No");
+ }
+
+
+ public void apply() {
+ option.setValue(check.getSelection());
+ }
+
+
+ public Conversion<?> convert() {
+ return option.convert(check.getSelection());
+ }
+
+
+ private final class ToggleListener extends SelectionAdapter {
+ public void widgetSelected(SelectionEvent e) {
+ updateText();
+ // No need to fire conversion event, will always be correct
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * A textual (string) segmentation algorithm parameter.
+ *
+ * @author Kevin McGuinness
+ */
+public class TextOption extends AbstractOption<String> {
+
+ /**
+ * Create a text option with the given name, description, and default
+ * value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ */
+ public TextOption(String name, String description, String def) {
+ super(OptionType.Text, name, description, def);
+ }
+
+
+ public Conversion<String> convert(Object value) {
+ return valid(value.toString());
+ }
+}
--- /dev/null
+package ie.dcu.segment.options;
+
+/**
+ * A boolean segmentation algorithm parameter.
+ *
+ * @author Kevin McGuinness
+ */
+public class ToggleOption extends AbstractOption<Boolean> {
+
+ /**
+ * Create a toggle option with the given name, description, and default
+ * value.
+ *
+ * @param name
+ * The option name
+ * @param description
+ * A description of the option
+ * @param def
+ * The default value for the option
+ */
+ public ToggleOption(String name, String description, boolean def) {
+ super(OptionType.Toggle, name, description, def);
+ }
+
+
+ public Conversion<Boolean> convert(Object value) {
+ if (value instanceof Boolean) {
+ return valid((Boolean) value);
+ } else if (value != null) {
+ Boolean val = convert(value.toString());
+ if (val != null) {
+ return valid(val);
+ }
+ return invalid("%s is not a valid boolean, value = %s", getName(), value);
+ }
+ return invalid("value is null");
+ }
+
+
+ /**
+ * Performs a conversion from a string to a boolean. If the value is "yes",
+ * "true" or "1", then the result is <code>true</code>. Otherwise if the
+ * value is "no", "false" or "0" the result is <code>false</code>. Any
+ * other value produces <code>null</code>. The string is trimmed and case
+ * is ignored.
+ *
+ * @param str
+ * The string to convert.
+ * @return <code>true</code>, <code>false</code> or <code>null</code>.
+ */
+ private Boolean convert(String str) {
+ String v = str.trim();
+
+ if (v.equalsIgnoreCase("true") ||
+ v.equalsIgnoreCase("yes") ||
+ v.equals("1"))
+ {
+ return true;
+
+ } else if (v.equalsIgnoreCase("false") ||
+ v.equalsIgnoreCase("no") ||
+ v.equals("0"))
+ {
+ return false;
+ }
+
+ return null;
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * An overlaid view of the image, segmentation mask, and markup.
+ *
+ * @author Kevin McGuinness
+ */
+public class CombinedPainter implements SegmentationPainter {
+ public static final String NAME = "Combined";
+
+ private ImageData maskData;
+ private Image maskImage;
+
+
+ public String getName() {
+ return NAME;
+ }
+
+
+ public String getDescription() {
+ return "An overlaid view of the image, segmentation mask, and markup";
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+
+ GC gc = im.beginPaint();
+
+ // Paint image
+ gc.drawImage(ctx.getImage(), 0, 0);
+
+ // Paint mask
+ createVisibleMask(ctx.getMask());
+ gc.drawImage(maskImage, 0, 0);
+
+ // Paint all annotations
+ ctx.getAnnotations().paint(im);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ private void createVisibleMask(SegmentationMask mask) {
+ dispose();
+
+ if (isNewMaskDataRequired(mask.getBounds())) {
+ maskData = createMaskData(mask.getBounds());
+ }
+
+ // Blit in pixels
+ for (int y = 0, i = 0; y < mask.height; y++) {
+ int rowOffset = y * maskData.bytesPerLine;
+ for (int x = 0; x < mask.width; x++) {
+ byte alpha, index;
+
+ switch (mask.values[i]) {
+ case SegmentationMask.BACKGROUND:
+ alpha = (byte) 128;
+ index = 2;
+ break;
+ case SegmentationMask.FOREGROUND:
+ alpha = (byte) 128;
+ index = 1;
+ break;
+ default:
+ alpha = 0;
+ index = 0;
+ break;
+ }
+
+ // The SWT ImageData buffer doesn't usually have it's rows aligned
+ // contiguously in memory (i.e. there are > width bytes per scan-line in
+ // the buffer), so we can't directly copy in at the same index as the
+ // mask.
+ maskData.data[x + rowOffset] = index;
+
+ // However, the alpha data is always aligned correctly
+ maskData.alphaData[i] = alpha;
+
+ // Next location in the mask
+ i++;
+ }
+ }
+
+ // Create new mask
+ maskImage = new Image(Display.getCurrent(), maskData);
+ }
+
+
+ private boolean isNewMaskDataRequired(Rectangle bounds) {
+ if (maskData == null) {
+ return true;
+ } else {
+ return maskData.width != bounds.width || maskData.height != bounds.height;
+ }
+ }
+
+
+ private static ImageData createMaskData(Rectangle bounds) {
+ RGB[] colors = new RGB[] {
+ new RGB(128,128,128),
+ new RGB(255,255,255),
+ new RGB(0, 0, 0)
+ };
+
+ // Create 3 color indexed palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create 8 bit indexed image
+ ImageData data = new ImageData(bounds.width, bounds.height, 8, palette);
+
+ // Create alpha mask
+ data.alphaData = new byte[bounds.width*bounds.height];
+
+ // Create and return the image
+ return data;
+ }
+
+
+ public void dispose() {
+ // Dispose mask
+ if (maskImage != null) {
+ if (!maskImage.isDisposed()) {
+ maskImage.dispose();
+ }
+ maskImage = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Shows the foreground region of the image. The background is greyed out.
+ *
+ * @author Kevin McGuinness
+ */
+public class ForegroundOnlyPainter implements SegmentationPainter {
+
+ private ImageData maskData;
+ private Image maskImage;
+
+
+ public String getName() {
+ return "Foreground Only";
+ }
+
+
+ public String getDescription() {
+ return "Shows the foreground region of the image.";
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+ GC gc = im.beginPaint();
+
+ // Paint image
+ gc.drawImage(ctx.getImage(), 0, 0);
+
+ // Paint mask
+ createVisibleMask(ctx.getMask());
+ gc.drawImage(maskImage, 0, 0);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ private void createVisibleMask(SegmentationMask mask) {
+ dispose();
+
+ if (isNewMaskDataRequired(mask.getBounds())) {
+ maskData = createMaskData(mask.getBounds());
+ }
+
+ // Blit in pixels
+ byte[] buff = new byte[maskData.width];
+ for (int y = 0, i = 0; y < mask.height; y++) {
+ for (int x = 0; x < mask.width; x++) {
+ switch (mask.values[i++]) {
+ case SegmentationMask.BACKGROUND:
+ // black
+ buff[x] = 1;
+ break;
+ default:
+ // transparent
+ buff[x] = 0;
+ }
+ }
+ maskData.setPixels(0, y, buff.length, buff, 0);
+ }
+
+ // Create new mask
+ maskImage = new Image(Display.getCurrent(), maskData);
+ }
+
+
+ private boolean isNewMaskDataRequired(Rectangle bounds) {
+ if (maskData == null) {
+ return true;
+ } else {
+ return maskData.width != bounds.width || maskData.height != bounds.height;
+ }
+ }
+
+
+ private static ImageData createMaskData(Rectangle bounds) {
+ RGB[] colors = new RGB[] {
+ new RGB(255,255,255),
+ new RGB(128,128,128)
+ };
+
+ // Create binary indexed palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create 1 bit indexed image
+ ImageData data = new ImageData(
+ bounds.width, bounds.height, 1, palette);
+
+ // Set transparent pixel
+ data.transparentPixel = 0;
+
+ // Create and return the image
+ return data;
+ }
+
+
+ public void dispose() {
+ // Dispose mask
+ if (maskImage != null) {
+ if (!maskImage.isDisposed()) {
+ maskImage.dispose();
+ }
+ maskImage = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Shows the foreground region of the image. The background is greyed out.
+ *
+ * @author Kevin and Nikhil
+ */
+public class LabelPainter implements SegmentationPainter {
+
+ private ImageData maskData;
+ private Image maskImage;
+
+
+ public String getName() {
+ return "Display Label";
+ }
+
+
+ public String getDescription() {
+ return "Shows the segmented piece with label attached.";
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+ GC gc = im.beginPaint();
+
+ // Paint image
+ gc.drawImage(ctx.getImage(), 0, 0);
+
+ // Paint mask
+ createVisibleMask(ctx.getMask());
+ gc.drawImage(maskImage, 0, 0);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ private void createVisibleMask(SegmentationMask mask) {
+ dispose();
+
+ if (isNewMaskDataRequired(mask.getBounds())) {
+ maskData = createMaskData(mask.getBounds());
+ }
+
+ // Blit in pixels
+ byte[] buff = new byte[maskData.width];
+ for (int y = 0, i = 0; y < mask.height; y++) {
+ for (int x = 0; x < mask.width; x++) {
+ switch (mask.values[i++]) {
+ case SegmentationMask.BACKGROUND:
+ // black
+ buff[x] = 1;
+ break;
+ default:
+ // transparent
+ buff[x] = 0;
+ }
+ }
+ maskData.setPixels(0, y, buff.length, buff, 0);
+ }
+
+ // Create new mask
+ maskImage = new Image(Display.getCurrent(), maskData);
+ }
+
+
+ private boolean isNewMaskDataRequired(Rectangle bounds) {
+ if (maskData == null) {
+ return true;
+ } else {
+ return maskData.width != bounds.width || maskData.height != bounds.height;
+ }
+ }
+
+
+ private static ImageData createMaskData(Rectangle bounds) {
+ RGB[] colors = new RGB[] {
+ new RGB(255,255,255),
+ new RGB(128,128,128)
+ };
+
+ // Create binary indexed palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create 1 bit indexed image
+ ImageData data = new ImageData(
+ bounds.width, bounds.height, 1, palette);
+
+ // Set transparent pixel
+ data.transparentPixel = 0;
+
+ // Create and return the image
+ return data;
+ }
+
+
+ public void dispose() {
+ // Dispose mask
+ if (maskImage != null) {
+ if (!maskImage.isDisposed()) {
+ maskImage.dispose();
+ }
+ maskImage = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.swt.*;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.GC;
+
+/**
+ * Shows the current markup (annotations) only
+ *
+ * @author Kevin McGuinness
+ */
+public class MarkupPainter implements SegmentationPainter {
+ public static final String NAME = "Markup";
+
+
+ public String getDescription() {
+ return "Shows the current markup (annotations) only";
+ }
+
+
+ public String getName() {
+ return NAME;
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+
+ GC gc = im.beginPaint();
+
+ // Paint white backdrop
+ SwtUtils.setBackground(gc, SWT.COLOR_WHITE);
+ gc.fillRectangle(im.getBounds());
+
+ // Paint all annotations
+ ctx.getAnnotations().paint(im);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ public void dispose() {
+ // Nothing needs to be disposed
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Shows the current segmentation mask
+ *
+ * @author Kevin McGuinness
+ */
+public class MaskPainter implements SegmentationPainter {
+ public static final String NAME = "Mask";
+
+ private ImageData maskData;
+ private Image maskImage;
+
+ public String getDescription() {
+ return "Shows the current segmentation mask";
+ }
+
+
+ public String getName() {
+ return NAME;
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+
+ GC gc = im.beginPaint();
+
+ // Paint mask
+ createVisibleMask(ctx.getMask());
+ gc.drawImage(maskImage, 0, 0);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ private void createVisibleMask(SegmentationMask mask) {
+ dispose();
+
+ // Check if we have a compatible buffer
+ if (isNewMaskDataRequired(mask.getBounds())) {
+ maskData = createMaskData(mask.getBounds());
+ }
+
+ // Blit in pixels
+ for (int y = 0; y < mask.height; y++) {
+ int maskOffset = y * mask.width;
+ int dataOffset = y * maskData.bytesPerLine;
+ System.arraycopy(mask.values, maskOffset, maskData.data, dataOffset, mask.width);
+ }
+
+ // Create new mask
+ maskImage = new Image(Display.getCurrent(), maskData);
+ }
+
+ private boolean isNewMaskDataRequired(Rectangle bounds) {
+ if (maskData == null) {
+ return true;
+ } else {
+ return maskData.width != bounds.width || maskData.height != bounds.height;
+ }
+ }
+
+
+ private static ImageData createMaskData(Rectangle bounds) {
+ RGB[] colors = new RGB[] {
+ new RGB(128,128,128),
+ new RGB(255,255,255),
+ new RGB(0, 0, 0)
+ };
+
+ // Create 3 color indexed palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create 8 bit indexed image
+ ImageData data = new ImageData(bounds.width, bounds.height, 8, palette);
+
+ // Create and return the image
+ return data;
+ }
+
+
+ public void dispose() {
+ /// Dispose mask
+ if (maskImage != null) {
+ if (!maskImage.isDisposed()) {
+ maskImage.dispose();
+ }
+ maskImage = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Shows the original image
+ *
+ * @author Kevin McGuinness
+ */
+public class OriginalPainter implements SegmentationPainter {
+ public static final String NAME = "Original";
+
+
+ public String getDescription() {
+ return "Shows the original image";
+ }
+
+
+ public String getName() {
+ return NAME;
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+ GC gc = im.beginPaint();
+
+ // Paint image
+ gc.drawImage(ctx.getImage(), 0, 0);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ public void dispose() {
+ // Nothing needs to be disposed
+ }
+
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+import ie.dcu.segment.*;
+import ie.dcu.swt.ObservableImage;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Shows an an overlay of foreground border on the image
+ *
+ * @author Kevin McGuinness
+ */
+public class OutlineOverlayPainter implements SegmentationPainter {
+
+ private ImageData maskData;
+ private Image maskImage;
+
+ public String getName() {
+ return "Outline Overlaid";
+ }
+
+
+ public String getDescription() {
+ return "Shows an an overlay of foreground border on the image";
+ }
+
+
+ public void paint(SegmentationContext ctx, ObservableImage im) {
+ GC gc = im.beginPaint();
+
+ // Paint image
+ gc.drawImage(ctx.getImage(), 0, 0);
+
+ // Paint mask
+ createVisibleMask(ctx.getMask());
+ gc.drawImage(maskImage, 0, 0);
+
+ // Commit changes
+ im.endPaint();
+ }
+
+
+ private void createVisibleMask(SegmentationMask mask) {
+ dispose();
+
+ if (isNewMaskDataRequired(mask.getBounds())) {
+ maskData = createMaskData(mask.getBounds());
+ }
+
+ // Set pixels
+ byte[] buff = new byte[maskData.width];
+ for (int y = 0, i = 0; y < mask.height-1; y++) {
+ for (int x = 0; x < mask.width-1; x++) {
+ // Make transparent
+ buff[x] = 0;
+
+ // Current pixel
+ byte pix1 = mask.values[i];
+
+ // Neighbor to right & neighbor below
+ byte pix2 = mask.values[i+1];
+ byte pix3 = mask.values[i+mask.width];
+
+ // Set pixel if either neighbor is different
+ if (pix1 != pix2 || pix1 != pix3) {
+ buff[x] = 1;
+ }
+
+ // Next pixel
+ i++;
+ }
+
+ if (mask.width != 0) {
+ // Last pixel in row
+ byte pix1 = mask.values[i];
+ byte pix2 = mask.values[i+mask.width];
+ buff[mask.width-1] = (pix1 != pix2) ? (byte) 1 : 0;
+
+ // Next row
+ i++;
+ }
+
+ // Blit pixels
+ maskData.setPixels(0, y, buff.length, buff, 0);
+ }
+
+ // Last row
+ if (mask.height != 0) {
+ int yoff = (mask.height - 1) * mask.width;
+ for (int x = 0; x < mask.width-1; x++) {
+ byte pix1 = mask.values[x+yoff];
+ byte pix2 = mask.values[x+yoff+1];
+ buff[x] = (pix1 != pix2) ? (byte) 1 : 0;
+ }
+
+ // Blit
+ maskData.setPixels(0, mask.height-1, buff.length, buff, 0);
+ }
+
+ // Create new mask
+ maskImage = new Image(Display.getCurrent(), maskData);
+ }
+
+
+ private boolean isNewMaskDataRequired(Rectangle bounds) {
+ if (maskData == null) {
+ return true;
+ } else {
+ return maskData.width != bounds.width || maskData.height != bounds.height;
+ }
+ }
+
+
+ private static ImageData createMaskData(Rectangle bounds) {
+ RGB[] colors = new RGB[] {
+ new RGB(255,255,255),
+ new RGB(255,0,0)
+ };
+
+ // Create binary indexed palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create 1 bit indexed image
+ ImageData data = new ImageData(
+ bounds.width, bounds.height, 1, palette);
+
+ // Set transparent pixel
+ data.transparentPixel = 0;
+
+ // Create and return the image
+ return data;
+ }
+
+
+ public void dispose() {
+ // Dispose mask
+ if (maskImage != null) {
+ if (!maskImage.isDisposed()) {
+ maskImage.dispose();
+ }
+ maskImage = null;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.painters;
+
+import ie.dcu.segment.SegmentationContext;
+import ie.dcu.swt.ObservableImage;
+
+/**
+ * Interface for classes that can be used to visualize an image and it's
+ * segmentation mask.
+ *
+ *
+ * @author Kevin McGuinness
+ */
+public interface SegmentationPainter {
+
+ /**
+ * Returns the painter name (Shown in the UI)
+ */
+ public String getName();
+
+ /**
+ * Returns a description of the painter.
+ */
+ public String getDescription();
+
+ /**
+ * Paint the segmentation context onto the observable image
+ *
+ * @param ctx
+ * The current segmentation context.
+ * @param im
+ * An observable image instance.
+ */
+ public void paint(SegmentationContext ctx, ObservableImage im);
+
+ /**
+ * Dispose of any resources (SWT image objects, colors, etc.)
+ */
+ public void dispose();
+}
--- /dev/null
+package ie.dcu.segment.util;
+
+
+
+import ie.dcu.array.Arrays;
+import ie.dcu.image.binary.MorphOp;
+import ie.dcu.matrix.*;
+import ie.dcu.segment.*;
+import ie.dcu.segment.annotate.Annotation;
+import ie.dcu.segment.options.Option;
+
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ * Abstract implementation of a {@link Segmenter} object.
+ *
+ * @author Kevin McGuinness
+ */
+public abstract class AbstractSegmenter implements Segmenter {
+
+ /**
+ * Segmenter options map.
+ */
+ private final Map<String, Option<?>> options;
+
+
+ /**
+ * Logger for subclass messages.
+ */
+ protected final Logger log;
+
+
+ /**
+ * Name of the segmentation algorithm. Should be set by subclass during
+ * construction.
+ */
+ protected String name;
+
+
+ /**
+ * Description of the segmentation algorithm. Should be set by subclass
+ * during construction.
+ */
+ protected String description;
+
+
+ /**
+ * Vendor of the segmentation algorithm. Should be set by subclass
+ * during construction. This should be either the author of the
+ * segmentation algorithm, or a company or institution name.
+ */
+ protected String vendor;
+
+
+ /**
+ * Default return value for the {@link #isFast()} method,
+ * <code>false</code> by default.
+ */
+ protected boolean fast;
+
+
+ /**
+ * Flag that indicates if the segmentation algorithm is available on the
+ * current platform. Defaults to <code>true</code>.
+ */
+ protected boolean available;
+
+
+ /**
+ * Constructor.
+ */
+ public AbstractSegmenter() {
+ options = new LinkedHashMap<String, Option<?>>();
+ log = Logger.getLogger(getClass().getSimpleName());
+ name = "";
+ description = "";
+ vendor = "";
+ available = true;
+ }
+
+
+ /**
+ * Default implementation returns the {@link #available} field.
+ */
+ public boolean isAvailable() {
+ return available;
+ }
+
+
+ /**
+ * Default implementation returns the {@link #name} field.
+ */
+ public String getName() {
+ return name;
+ }
+
+
+ /**
+ * Default implementation returns the {@link #description} field.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+
+ /**
+ * Default implementation returns the {@link #vendor} field.
+ */
+ public String getVendor() {
+ return vendor;
+ }
+
+
+ /**
+ * Default implementation returns the {@link #options} map.
+ */
+ public Collection<Option<?>> getOptions() {
+ return Collections.unmodifiableCollection(options.values());
+ }
+
+
+ /**
+ * Default implementation returns the {@link #fast} field.
+ */
+ public boolean isFast() {
+ return fast;
+ }
+
+
+ /**
+ * Default implementation calls {@link #segment(SegmentationContext)}.
+ */
+ public void added(SegmentationContext ctx, Annotation a) {
+ segment(ctx);
+ }
+
+
+ /**
+ * Default implementation calls {@link #segment(SegmentationContext)}.
+ */
+ public void removed(SegmentationContext ctx, Annotation a) {
+ segment(ctx);
+ }
+
+
+ /**
+ * Default implementation calls {@link #segment(SegmentationContext)}.
+ */
+ public void update(SegmentationContext ctx) {
+ segment(ctx);
+ }
+
+
+
+ /**
+ * Perform a full update of the segmentation.
+ *
+ * @param ctx
+ * The segmentation context.
+ */
+ protected abstract void segment(SegmentationContext ctx);
+
+
+
+ /**
+ * Add an option (parameter) to the {@link #options} map.
+ *
+ * @param option
+ * The option
+ */
+ protected void add(Option<?> option) {
+ options.put(option.getName(), option);
+ }
+
+
+ /**
+ * Get an option (parameter) from the {@link #options} map.
+ *
+ * @param <T>
+ * The option type.
+ * @param clazz
+ * The option class.
+ * @param option
+ * The option name.
+ * @return The option.
+ */
+ protected <T> T get(Class<T> clazz, String option) {
+ Option<?> opt = options.get(option);
+ return clazz.cast(opt.getValue());
+ }
+
+ /**
+ * Perform a morphological smooth on the contour of the given segmentation
+ * mask.
+ *
+ * @param mask
+ * A segmentation mask.
+ */
+ protected void smoothContour(SegmentationMask mask) {
+ // TODO: Maybe optimize this? The old MorphOps.smooth might be faster
+ MorphOp op = new MorphOp.Smooth();
+ op.setForegroundValue(SegmentationMask.FOREGROUND);
+ op.setBackgroundValue(SegmentationMask.BACKGROUND);
+ op.setInputProvider(mask);
+ ByteMatrix matrix = (ByteMatrix)
+ op.getMatrix(Matrix.Type.Byte, false);
+ Arrays.copy(matrix.values, mask.values);
+ }
+
+ /**
+ * Copy the non-zero markup pixels to the segmentation mask.
+ *
+ * @param mask
+ * The segmentation mask.
+ * @param markup
+ * The rasterized markup pixels.
+ */
+ protected void retainMarkup(SegmentationMask mask, byte[] markup) {
+ for (int i = 0; i < markup.length; i++) {
+ if (markup[i] != 0) {
+ mask.values[i] = markup[i];
+ }
+ }
+ }
+}
--- /dev/null
+package ie.dcu.segment.util;
+
+import ie.dcu.segment.annotate.*;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.util.List;
+
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * Rasterize the annotations using the Graphics2D API. The annotations are drawn
+ * onto a byte buffer, with one byte per pixel.
+ *
+ * @author Kevin McGuinness
+ */
+public class AnnotationRasterizer {
+ private static final byte[] R = { (byte) 128, (byte) 255, (byte) 0 };
+ private static final byte[] G = { (byte) 128, (byte) 0, (byte) 0 };
+ private static final byte[] B = { (byte) 128, (byte) 0, (byte) 255 };
+
+ private static final Color[] COLORS = {
+ new Color(128, 128, 128),
+ new Color(255, 0, 0),
+ new Color( 0, 0, 255)
+ };
+
+ private static final ColorModel cm = new IndexColorModel(8,3,R,G,B);
+
+ private final int width;
+ private final int height;
+ private final int npixels;
+ private final WritableRaster wr;
+ private final BufferedImage im;
+ private transient byte[] buff;
+
+ public AnnotationRasterizer(int width, int height) {
+ this.width = width;
+ this.height = height;
+ this.npixels = width * height;
+ this.wr = cm.createCompatibleWritableRaster(width, height);
+ this.im = new BufferedImage(cm, wr, false, null);
+ }
+
+
+ public byte[] rasterize(AnnotationManager am) {
+ paint(am);
+ return getDataBuffer();
+ }
+
+
+ private void paint(AnnotationManager am) {
+
+ Graphics2D g = im.createGraphics();
+
+ // Clear background
+ g.setColor(COLORS[0]);
+ g.fillRect(0, 0, width, height);
+
+ // For each annotation
+ int lastWidth = -1;
+ for (Annotation a : am.annotations()) {
+
+ List<Point> pts = a.points();
+
+ // Skip empty
+ if (pts.size() == 0) {
+ continue;
+ }
+
+ // Check if we need to change the stroke
+ int width = a.getLineWidth();
+ if (width != lastWidth) {
+ g.setStroke(createStroke(width));
+ lastWidth = width;
+ }
+
+ // Set the color
+ switch (a.getType()) {
+ case Foreground:
+ g.setColor(COLORS[1]);
+ break;
+ case Background:
+ g.setColor(COLORS[2]);
+ break;
+ }
+
+ // Paint the points
+ if (pts.size() > 1) {
+ Point last = null;
+ for (Point p : pts) {
+ if (last != null) {
+ g.drawLine(last.x, last.y, p.x, p.y);
+ }
+
+ last = p;
+ }
+ } else {
+ // If only one point
+ Point p = pts.get(0);
+ g.drawLine(p.x, p.y, p.x, p.y);
+ }
+ }
+
+ // Commit changes
+ g.dispose();
+ }
+
+
+ private byte[] getDataBuffer() {
+
+ // Optimize the typical case by avoiding a copy if unnecessary.
+ DataBuffer buffer = wr.getDataBuffer();
+ if (buffer instanceof DataBufferByte) {
+
+ // Good, we have a compatible data buffer
+ DataBufferByte bytebuf = (DataBufferByte) buffer;
+ int banks = bytebuf.getNumBanks();
+ if (banks == 1) {
+
+ // Smashin, only one data bank is in use
+ byte[] buff = bytebuf.getData();
+
+ // Sanity check
+ if (buff.length == npixels) {
+ return buff;
+ }
+ }
+ }
+
+ // Handle some unexpected data buffer by copying pixels
+ if (buff == null) {
+ buff = new byte[npixels];
+ }
+
+ return (byte[]) wr.getDataElements(0, 0, width, height, buff);
+ }
+
+
+
+ private static final Stroke createStroke(int width) {
+ return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ }
+
+}
--- /dev/null
+package ie.dcu.segment.util;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Convert a SWT ImageData object to a linear array of ARGB integers (alpha
+ * channel is ignored).
+ *
+ * @author Kevin McGuinness
+ */
+public class ImageArgbConverter {
+ private final int npixels;
+ private final int[] pixels;
+
+ /**
+ * Create an converter object for images of the given size.
+ *
+ * @param width
+ * The image width.
+ * @param height
+ * The image height.
+ */
+ public ImageArgbConverter(int width, int height) {
+ npixels = width*height;
+ pixels = new int[npixels];
+ }
+
+
+ /**
+ * Convert an SWT ImageData object to a linear array of integers. Each
+ * integer is a contains the red, green, and blue channels, ordered as
+ * 0x00RRGGBB. The alpha channel is ignored.
+ *
+ * @param data
+ * An image data object.
+ * @param clone
+ * If <code>false</code> returns a shared array, otherwise returns a
+ * new array.
+ * @return A int array of ARGB values in row-major order.
+ */
+ public int[] convert(ImageData data, boolean clone) {
+ PaletteData p = data.palette;
+
+ data.getPixels(0, 0, npixels, pixels, 0);
+ if (p.isDirect) {
+ for (int i = 0; i < npixels; i++) {
+ // Unpack
+ int r, g, b, pel = pixels[i];
+
+ // red
+ r = pel & p.redMask;
+ r = (p.redShift < 0) ?
+ r >>> -p.redShift : r << p.redShift;
+
+ // green
+ g = pel & p.greenMask;
+ g = (p.greenShift < 0) ?
+ g >>> -p.greenShift : g << p.greenShift;
+
+ // blue
+ b = pel & p.blueMask;
+ b = (p.blueShift < 0) ?
+ b >>> -p.blueShift : b << p.blueShift;
+
+ // re-pack
+ pixels[i] = pack(r, g, b);
+ }
+ } else {
+ // Indirect palette
+ for (int i = 0; i < npixels; i++) {
+ RGB color = p.colors[pixels[i]];
+ pixels[i] = pack(color.red, color.green, color.blue);
+ }
+ }
+
+ if (clone) {
+ return pixels.clone();
+ }
+
+ return pixels;
+ }
+
+ /**
+ * Convert an SWT ImageData object to a linear array of integers. Each
+ * integer is a contains the red, green, and blue channels, ordered as
+ * 0x00RRGGBB. The alpha channel is ignored.
+ *
+ * @param data
+ * An image data object.
+ * @return A int array of ARGB values in row-major order.
+ */
+ public static int[] convert(ImageData data) {
+ return new ImageArgbConverter(data.width, data.height).convert(data, false);
+ }
+
+ private static final int pack(int r, int g, int b) {
+ return 0x00ffffff & ((r << 16) | (g << 8) | b);
+ }
+}
--- /dev/null
+package ie.dcu.segment.util;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Converts an SWT ImageData object to a linear array of bytes.
+ *
+ * @author Kevin McGuinness
+ */
+public class ImageByteConverter {
+ private final int npixels;
+ private final int nsamples;
+ private final int[] pixels;
+ private final byte[] samples;
+
+ /**
+ * Create an converter object for images of the given size.
+ *
+ * @param width
+ * The image width.
+ * @param height
+ * The image height.
+ */
+ public ImageByteConverter(int width, int height) {
+ this.npixels = width*height;
+ this.nsamples = npixels*3;
+ this.pixels = new int[npixels];
+ this.samples = new byte[nsamples];
+ }
+
+ /**
+ * Convert an SWT ImageData object to a linear array of bytes. The returned
+ * array contains three bytes per pixel, and is in row-major order. The
+ * bytes are ordered RGB.
+ *
+ * @param data
+ * An image data object.
+ * @param clone
+ * If <code>false</code> returns a shared array, otherwise returns a
+ * new array.
+ * @return A byte array of RGB values in row-major order.
+ */
+ public byte[] convert(ImageData data, boolean clone) {
+ PaletteData p = data.palette;
+
+ data.getPixels(0, 0, npixels, pixels, 0);
+ if (p.isDirect) {
+ for (int i = 0, j = 0; i < npixels; i++) {
+ // Unpack
+ int r, g, b, pel = pixels[i];
+
+ // red
+ r = pel & p.redMask;
+ r = (p.redShift < 0) ?
+ r >>> -p.redShift : r << p.redShift;
+
+ // green
+ g = pel & p.greenMask;
+ g = (p.greenShift < 0) ?
+ g >>> -p.greenShift : g << p.greenShift;
+
+ // blue
+ b = pel & p.blueMask;
+ b = (p.blueShift < 0) ?
+ b >>> -p.blueShift : b << p.blueShift;
+
+ // assign
+ samples[j++] = (byte) r;
+ samples[j++] = (byte) g;
+ samples[j++] = (byte) b;
+ }
+ } else {
+ // Indirect palette
+ for (int i = 0, j = 0; i < npixels; i++) {
+ RGB color = p.colors[pixels[i]];
+ samples[j++] = (byte) color.red;
+ samples[j++] = (byte) color.green;
+ samples[j++] = (byte) color.blue;
+ }
+ }
+
+ if (clone) {
+ return samples.clone();
+ }
+
+ return samples;
+ }
+
+ /**
+ * Convert an SWT image data object to a linear array of bytes. The returned
+ * array contains three bytes per pixel, and is in row-major order. The
+ * bytes are ordered RGB.
+ *
+ * @param data
+ * An image data object.
+ * @return A byte array of RGB values in row-major order.
+ */
+ public static byte[] convert(ImageData data) {
+ return new ImageByteConverter(data.width, data.height).convert(data, false);
+ }
+}
--- /dev/null
+package ie.dcu.stats;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+import javax.swing.*;
+
+import ie.dcu.array.Arrays;
+import static java.awt.RenderingHints.*;
+
+/**
+ * Implementation of the inversion method for simulating random variables.
+ *
+ * @author Kevin McGuinness
+ */
+public final class InversionMethod {
+
+ /**
+ * The probability distribution function
+ */
+ private final double[] pdf;
+
+ /**
+ * The cumulative distribution function
+ */
+ private final double[] cdf;
+
+
+ /**
+ * Initialize the inversion method with the given discrete probability
+ * distribution function. The distribution function will be normalized the
+ * sum of all it's elements is one.
+ *
+ * The values of the pdf function must be all greater than 0 and contain at
+ * least one positive value.
+ *
+ * @param pdf
+ * A probability distribution function.
+ */
+ public InversionMethod(double[] pdf) {
+ this.pdf = normalize(check(pdf));
+ this.cdf = cumsum(this.pdf);
+ }
+
+ /**
+ * Returns the distribution size
+ */
+ public final int size() {
+ return this.pdf.length;
+ }
+
+ /**
+ * Extract a random variable from to the distribution.
+ *
+ * The returned value is in the range [0..n) where n is the size of the
+ * distribution.
+ *
+ * Complexity is O(log(n)).
+ *
+ * @return A random variable
+ */
+ public final int random() {
+ return lbound(cdf, Math.random());
+ }
+
+ /**
+ * Returns the arg min{x : array[x] >= value}
+ *
+ * Complexity is O(log(n)).
+ */
+ private static int lbound(double[] array, double value) {
+ int lo = 0;
+ int hi = array.length - 1;
+ while (lo <= hi) {
+ int mid = (lo + hi) >> 1;
+ double x = array[mid];
+
+ if (x < value) {
+ lo = mid + 1;
+ } else if (x > value) {
+ hi = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+ return lo;
+ }
+
+ /**
+ * Check the values of the pdf function are ok.
+ */
+ private static double[] check(double[] pdf) {
+ double min = Arrays.min(pdf);
+ if (min < 0) {
+ throw new IllegalArgumentException("pdf contains values < 0");
+ }
+
+ double max = Arrays.max(pdf);
+ if (max == 0) {
+ throw new IllegalArgumentException("pdf contains no nonzero values");
+ }
+
+ return pdf;
+ }
+
+ /**
+ * Normalize the pdf function so that it sums to 1.
+ */
+ private static double[] normalize(double[] pdf) {
+ double[] result = new double[pdf.length];
+
+ double sum = Arrays.sum(pdf);
+ for (int i = 0; i < pdf.length; i++) {
+ result[i] = pdf[i] / sum;
+ }
+
+ return result;
+ }
+
+ /**
+ * Calculate the cumulative sum of the array.
+ */
+ private static double[] cumsum(double[] array) {
+ double[] result = new double[array.length];
+
+ double sum = 0.0;
+ for (int i = 0; i < array.length; i++) {
+ sum += array[i];
+ result[i] = sum;
+ }
+
+ return result;
+ }
+
+ public static void main(String[] args) {
+
+ double[] pdf = normal(5); //{1,2,3,4,5,4,5,3,2,1};
+ InversionMethod var = new InversionMethod(pdf);
+ int[] variables = new int[10000];
+ for (int i = 0; i < variables.length; i++) {
+ variables[i] = var.random();
+ }
+ plothist("Normal Distribution", variables);
+ }
+
+ public static double[] normal(int n) {
+ double[] result = new double[2*n+1];
+ double sigma = n / 3.0;
+ System.out.println(sigma);
+ double scalef = 1.0 / (Math.sqrt(2.0*Math.PI) * sigma);
+ double denom = 2*sigma*sigma;
+
+ for (int i = -n; i <= n; i++) {
+ result[i+n] = scalef * Math.exp(-(i*i)/denom);
+ }
+ return result;
+ }
+
+
+ private static JFrame plothist(String title, int[] values) {
+ final int[] hist = histogram(values);
+ final Color[] colors = new Color[hist.length];
+ for (int i = 0; i < colors.length; i++) {
+ float r = (float) Math.random();
+ float g = (float) Math.random();
+ float b = (float) Math.random();
+ colors[i] = new Color(r,g,b);
+ }
+
+
+ JFrame frame = new JFrame(title);
+ JPanel panel = new JPanel() {
+ private static final long serialVersionUID = 1L;
+ public void paint(Graphics graphics) {
+ Graphics2D g = (Graphics2D) graphics;
+ FontMetrics fontMetrics = g.getFontMetrics();
+
+ double w = getWidth();
+ double h = getHeight();
+ double spaceAtBottom = fontMetrics.getHeight() + 4;
+ double spaceAtTop = 3;
+ double spaceBetweenBars = 5;
+
+ g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
+ g.setRenderingHint(KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON);
+
+ g.setColor(Color.white);
+ g.fill(new Rectangle2D.Double(0,0,w,h));
+ g.setColor(Color.black);
+ g.draw(new Line2D.Double(0,h-spaceAtBottom,w,h-spaceAtBottom));
+
+ double hmax = Arrays.max(hist);
+ double hcount = hist.length;
+ double space = w / hcount;
+
+ Rectangle2D.Double bar = new Rectangle2D.Double(0,0,0,0);
+
+ for (int i = 0; i < hist.length; i++) {
+ bar.x = i * space + spaceBetweenBars / 2;
+ bar.width = space - spaceBetweenBars;
+ bar.height = (h-(spaceAtBottom+spaceAtTop)) * (hist[i] / hmax);
+ bar.y = h - bar.height - spaceAtBottom;
+ g.setColor(colors[i]);
+ g.fill(bar);
+ g.setColor(Color.black);
+ g.draw(bar);
+
+ String text = String.valueOf(hist[i]);
+ Rectangle2D bounds = fontMetrics.getStringBounds(text, g);
+ double tx = bar.getCenterX() - bounds.getWidth() / 2.0;
+ double ty = bar.getMinY() + bounds.getHeight() + 1;
+ if (ty > h - spaceAtBottom - 1) {
+ // place over the bar
+ ty = bar.getMinY() - 2;
+ }
+ g.drawString(text, (int) tx, (int) ty);
+
+ text = String.valueOf(i);
+ bounds = fontMetrics.getStringBounds(text, g);
+ tx = bar.getCenterX() - bounds.getWidth() / 2.0;
+ ty = bar.getMaxY() + bounds.getHeight();
+ g.drawString(text, (int) tx, (int) ty);
+ }
+ }
+
+ public Dimension getPreferredSize() {
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ int w = 50 * hist.length;
+ int h = 400;
+ w = Math.min(Math.max(w, 100), screenSize.width);
+ h = Math.min(h, screenSize.height);
+ return new Dimension(w,h);
+ }
+ };
+
+ frame.setLayout(new BorderLayout());
+ frame.add(panel);
+ frame.pack();
+ frame.setLocationRelativeTo(frame);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+ return frame;
+ }
+
+ private static int[] histogram(int[] values) {
+ int[] bins = new int[Arrays.max(values)+1];
+ java.util.Arrays.fill(bins, 0);
+ for (int i = 0; i < values.length; i++) {
+ bins[values[i]]++;
+ }
+ return bins;
+ }
+}
--- /dev/null
+package ie.dcu.swt;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.internal.C;
+import org.eclipse.swt.internal.Callback;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Listener;
+
+/**
+ * Provide a hook to connecting the Preferences, About and Quit menu items of
+ * the Mac OS X Application menu when using the SWT Cocoa bindings.
+ * <p>
+ * This code does not require the Cocoa SWT JAR in order to be compiled as it
+ * uses reflection to access the Cocoa specific API methods. It does, however,
+ * depend on JFace (for IAction), but you could easily modify the code to use
+ * SWT Listeners instead in order to use this class in SWT only applications.
+ * </p>
+ */
+public class CocoaUIEnhancer {
+
+ private static final int kAboutMenuItem = 0;
+ private static final int kPreferencesMenuItem = 2;
+ private static final int kQuitMenuItem = 10;
+
+ static long sel_toolbarButtonClicked_;
+ static long sel_preferencesMenuItemSelected_;
+ static long sel_aboutMenuItemSelected_;
+
+ static Callback proc3Args;
+
+ private final String appName;
+
+ /**
+ * Construct a new CocoaUIEnhancer.
+ *
+ * @param appName
+ * The name of the application. It will be used to customize the
+ * About and Quit menu items. If you do not wish to customize the
+ * About and Quit menu items, just pass <tt>null</tt> here.
+ */
+ public CocoaUIEnhancer(String appName) {
+ this.appName = appName;
+ }
+
+ /**
+ * Hook the given Listener to the Mac OS X application Quit menu and the
+ * IActions to the About and Preferences menus.
+ *
+ * @param display
+ * The Display to use.
+ * @param quitListener
+ * The listener to invoke when the Quit menu is invoked.
+ * @param aboutAction
+ * The action to run when the About menu is invoked.
+ * @param preferencesAction
+ * The action to run when the Preferences menu is invoked.
+ */
+ public void hookApplicationMenu(Display display, Listener quitListener,
+ final IAction aboutAction, final IAction preferencesAction) {
+ Object target = new Object() {
+
+ // For the 32 bit JVM
+ @SuppressWarnings("unused")
+ int actionProc(int id, int sel, int arg) {
+ return (int) actionProc((long) id, (long) sel, (long) arg);
+ }
+
+ // For the 64 bit JVM
+ long actionProc(long id, long sel, long arg) {
+ if (sel == sel_aboutMenuItemSelected_) {
+ aboutAction.run();
+ } else if (sel == sel_preferencesMenuItemSelected_) {
+ preferencesAction.run();
+ }
+ return 99;
+ }
+ };
+
+ try {
+ // Initialize the menuItems.
+ initialize(target);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+
+ // Connect the quit/exit menu.
+ if (!display.isDisposed()) {
+ display.addListener(SWT.Close, quitListener);
+ }
+
+ // Schedule disposal of callback object
+ display.disposeExec(new Runnable() {
+ public void run() {
+ invoke(proc3Args, "dispose");
+ }
+ });
+ }
+
+ private void initialize(Object callbackObject) throws Exception {
+
+ Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS");
+
+ // Register names in objective-c.
+ if (sel_toolbarButtonClicked_ == 0) {
+ // sel_toolbarButtonClicked_ = registerName( osCls, "toolbarButtonClicked:" ); //$NON-NLS-1$
+ sel_preferencesMenuItemSelected_ = registerName(osCls,
+ "preferencesMenuItemSelected:"); //$NON-NLS-1$
+ sel_aboutMenuItemSelected_ = registerName(osCls,
+ "aboutMenuItemSelected:"); //$NON-NLS-1$
+ }
+
+ // Create an SWT Callback object that will invoke the actionProc method
+ // of our internal callbackObject.
+ proc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$
+ Method getAddress = Callback.class
+ .getMethod("getAddress", new Class[0]);
+ Object object = getAddress.invoke(proc3Args, (Object[]) null);
+ long proc3 = convertToLong(object);
+ if (proc3 == 0) {
+ SWT.error(SWT.ERROR_NO_MORE_CALLBACKS);
+ }
+
+ Class<?> nsmenuCls = classForName("org.eclipse.swt.internal.cocoa.NSMenu");
+ Class<?> nsmenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem");
+ Class<?> nsstringCls = classForName("org.eclipse.swt.internal.cocoa.NSString");
+ Class<?> nsapplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication");
+
+ // Instead of creating a new delegate class in objective-c,
+ // just use the current SWTApplicationDelegate. An instance of this
+ // is a field of the Cocoa Display object and is already the target
+ // for the menuItems. So just get this class and add the new methods
+ // to it.
+ object = invoke(osCls, "objc_lookUpClass",
+ new Object[] { "SWTApplicationDelegate" });
+ long cls = convertToLong(object);
+
+ // Add the action callbacks for Preferences and About menu items.
+ invoke(osCls, "class_addMethod", new Object[] { wrapPointer(cls),
+ wrapPointer(sel_preferencesMenuItemSelected_), wrapPointer(proc3),
+ "@:@" }); //$NON-NLS-1$
+ invoke(osCls, "class_addMethod",
+ new Object[] { wrapPointer(cls),
+ wrapPointer(sel_aboutMenuItemSelected_), wrapPointer(proc3),
+ "@:@" }); //$NON-NLS-1$
+
+ // Get the Mac OS X Application menu.
+ Object sharedApplication = invoke(nsapplicationCls, "sharedApplication");
+ Object mainMenu = invoke(sharedApplication, "mainMenu");
+ Object mainMenuItem = invoke(nsmenuCls, mainMenu, "itemAtIndex",
+ new Object[] { wrapPointer(0) });
+ Object appMenu = invoke(mainMenuItem, "submenu");
+
+ // Create the About <application-name> menu command
+ Object aboutMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex",
+ new Object[] { wrapPointer(kAboutMenuItem) });
+ if (appName != null) {
+ Object nsStr = invoke(nsstringCls, "stringWith",
+ new Object[] { "About " + appName });
+ invoke(nsmenuitemCls, aboutMenuItem, "setTitle",
+ new Object[] { nsStr });
+ }
+ // Rename the quit action.
+ if (appName != null) {
+ Object quitMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex",
+ new Object[] { wrapPointer(kQuitMenuItem) });
+ Object nsStr = invoke(nsstringCls, "stringWith",
+ new Object[] { "Quit " + appName });
+ invoke(nsmenuitemCls, quitMenuItem, "setTitle",
+ new Object[] { nsStr });
+ }
+
+ // Enable the Preferences menuItem.
+ Object prefMenuItem = invoke(nsmenuCls, appMenu, "itemAtIndex",
+ new Object[] { wrapPointer(kPreferencesMenuItem) });
+ invoke(nsmenuitemCls, prefMenuItem, "setEnabled", new Object[] { true });
+
+ // Set the action to execute when the About or Preferences menuItem is
+ // invoked.
+ //
+ // We don't need to set the target here as the current target is the
+ // SWTApplicationDelegate
+ // and we have registerd the new selectors on it. So just set the new
+ // action to invoke the
+ // selector.
+ invoke(nsmenuitemCls, prefMenuItem, "setAction",
+ new Object[] { wrapPointer(sel_preferencesMenuItemSelected_) });
+ invoke(nsmenuitemCls, aboutMenuItem, "setAction",
+ new Object[] { wrapPointer(sel_aboutMenuItemSelected_) });
+ }
+
+ private long registerName(Class<?> osCls, String name)
+ throws IllegalArgumentException, SecurityException,
+ IllegalAccessException, InvocationTargetException,
+ NoSuchMethodException {
+ Object object = invoke(osCls, "sel_registerName", new Object[] { name });
+ return convertToLong(object);
+ }
+
+ private long convertToLong(Object object) {
+ if (object instanceof Integer) {
+ Integer i = (Integer) object;
+ return i.longValue();
+ }
+ if (object instanceof Long) {
+ Long l = (Long) object;
+ return l.longValue();
+ }
+ return 0;
+ }
+
+ private static Object wrapPointer(long value) {
+ Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class;
+ if (PTR_CLASS == long.class)
+ return new Long(value);
+ else
+ return new Integer((int) value);
+ }
+
+ private static Object invoke(Class<?> clazz, String methodName,
+ Object[] args) {
+ return invoke(clazz, null, methodName, args);
+ }
+
+ private static Object invoke(Class<?> clazz, Object target,
+ String methodName, Object[] args) {
+ try {
+ Class<?>[] signature = new Class<?>[args.length];
+ for (int i = 0; i < args.length; i++) {
+ Class<?> thisClass = args[i].getClass();
+ if (thisClass == Integer.class)
+ signature[i] = int.class;
+ else if (thisClass == Long.class)
+ signature[i] = long.class;
+ else if (thisClass == Byte.class)
+ signature[i] = byte.class;
+ else if (thisClass == Boolean.class)
+ signature[i] = boolean.class;
+ else
+ signature[i] = thisClass;
+ }
+ Method method = clazz.getMethod(methodName, signature);
+ return method.invoke(target, args);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private Class<?> classForName(String classname) {
+ try {
+ Class<?> cls = Class.forName(classname);
+ return cls;
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private Object invoke(Class<?> cls, String methodName) {
+ return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null);
+ }
+
+ private Object invoke(Class<?> cls, String methodName,
+ Class<?>[] paramTypes, Object... arguments) {
+ try {
+ Method m = cls.getDeclaredMethod(methodName, paramTypes);
+ return m.invoke(null, arguments);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private Object invoke(Object obj, String methodName) {
+ return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null);
+ }
+
+ private Object invoke(Object obj, String methodName, Class<?>[] paramTypes,
+ Object... arguments) {
+ try {
+ Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes);
+ return m.invoke(obj, arguments);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.swt;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Create cursors.
+ *
+ * @author Kevin McGuinness
+ */
+public class CursorFactory {
+
+ /**
+ * Create a cross-hair cursor.
+ *
+ * @return The newly created cursor.
+ */
+ public static Cursor createCrosshairCursor() {
+
+ // Size of cursor
+ final int s = 24;
+
+ // Number of points in cursor
+ final int n = s*s;
+
+ // Midpoint of cursor
+ final int m = s/2;
+
+ // Color indices
+ final byte black = 1;
+ final byte light = 2;
+ final byte trans = 0;
+
+ // Cursor array
+ byte[] cursor = new byte[n];
+
+ // Flood with transparency
+ Arrays.fill(cursor, (byte) 0);
+
+ // Set crosshair pixels
+ for (int i = 0; i < s; i++) {
+
+ // Choose color
+ byte color = (i == m-2 || i == m+2) ? light : black;
+
+ // Set pixels
+ if (i < m-1 || i > m+1) {
+ int idx1 = i+m*s;
+ int idx2 = m+i*s;
+ cursor[idx1] = (byte) color;
+ cursor[idx2] = (byte) color;
+ }
+ }
+
+ // Set center pixel
+ cursor[m+m*s] = black;
+
+ // Create colors
+ RGB[] colors = new RGB[] {
+ new RGB(255,255,255),
+ new RGB(0,0,0),
+ new RGB(128,128,128)
+ };
+
+ // Create palette
+ PaletteData palette = new PaletteData(colors);
+
+ // Create image
+ ImageData image = new ImageData(s, s, 4, palette);
+ image.transparentPixel = trans;
+ image.setPixels(0, 0, n, cursor, 0);
+
+ // Create cursor
+ return new Cursor(Display.getCurrent(), image, s/2, s/2);
+ }
+}
--- /dev/null
+package ie.dcu.swt;
+
+import java.io.Serializable;
+import java.lang.reflect.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Dispatch simple method calls by reflection.
+ *
+ * @author Kevin McGuinness
+ */
+public class Dispatcher implements Serializable, Listener {
+
+ /**
+ * Serialization UID
+ */
+ private static final long serialVersionUID = -8518627659417915880L;
+
+ /**
+ * Logger used to report errors.
+ */
+ private static final Logger logger = Logger.getLogger("Dispatcher");
+
+ /**
+ * The target object.
+ */
+ public final Object object;
+
+ /**
+ * The target method.
+ */
+ public final Method method;
+
+ /**
+ * Create a dispatcher that calls the given method on the given object. The
+ * method should have no parameters. If a SecurityException or a
+ * NoSuchMethodException occurs when getting the Method object, then it is
+ * logged and wrapped in a RuntimeException and re-thrown.
+ *
+ * @param object
+ * The target object.
+ * @param method
+ * The method name.
+ * @throws RuntimeException
+ * If a SecurityException or NoSuchMethodException occurs.
+ */
+ public Dispatcher(Object object, String method)
+ throws RuntimeException
+ {
+ try {
+ this.object = object;
+ this.method = object.getClass().getMethod(method);
+ } catch (SecurityException e) {
+ logger.log(Level.SEVERE, "Cannot get method by reflection", e);
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ logger.log(Level.SEVERE, "Method not found: " + method, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Invoke the method on the object by reflection. If any exception occurs in
+ * the invocation, it is logged, then wrapped in a RuntimeException and
+ * re-thrown.
+ *
+ * @return The result of the method invocation, or null if the method is
+ * void.
+ *
+ * @throws RuntimeException
+ * If any exception occurs in the invocation, it is wrapped in a
+ * RuntimeException and re-thrown.
+ */
+ public Object invoke() {
+ try {
+ return method.invoke(object);
+ } catch (IllegalArgumentException e) {
+ logger.log(Level.SEVERE, "Error invoking method", e);
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Error invoking method", e);
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ logger.log(Level.WARNING, "Invocation target error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Allows the dispatcher to be used as an SWT listener. The event and
+ * return value are ignored.
+ */
+ public void handleEvent(Event event) {
+ invoke();
+ }
+}
--- /dev/null
+package ie.dcu.swt;
+
+
+import java.io.Serializable;
+import java.lang.reflect.*;
+import java.util.logging.*;
+
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Dispatches method calls that take an Event parameter by reflection.
+ *
+ * @author Kevin McGuinness
+ */
+public class EventDispatcher implements Serializable, Listener {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = 7238671421599697388L;
+
+ /**
+ * Logger used to report errors.
+ */
+ private static final Logger logger = Logger.getLogger("Dispatcher");
+
+ /**
+ * The target object.
+ */
+ public final Object object;
+
+ /**
+ * The target method.
+ */
+ public final Method method;
+
+ /**
+ * Create an event dispatcher that calls the given method on the given
+ * object. The method must take a single Event parameter. If a
+ * SecurityException or a NoSuchMethodException occurs when getting the
+ * Method object, then it is logged and wrapped in a RuntimeException and
+ * re-thrown.
+ *
+ * @param object
+ * The target object.
+ * @param method
+ * The method name.
+ * @throws RuntimeException
+ * If a SecurityException or NoSuchMethodException occurs.
+ */
+ public EventDispatcher(Object object, String method)
+ throws RuntimeException
+ {
+ try {
+ this.object = object;
+ this.method = object.getClass().getMethod(method, Event.class);
+ } catch (SecurityException e) {
+ logger.log(Level.SEVERE, "Cannot get method by reflection", e);
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ logger.log(Level.SEVERE, "Method not found: " + method, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Invoke the method on the object by reflection. If any exception occurs in
+ * the invocation, it is logged, then wrapped in a RuntimeException and
+ * re-thrown.
+ *
+ * @param event
+ * The event to pass to the method invocation.
+ *
+ * @return The result of the method invocation, or null if the method is
+ * void.
+ *
+ * @throws RuntimeException
+ * If any exception occurs in the invocation, it is wrapped in a
+ * RuntimeException and re-thrown.
+ */
+ public Object invoke(Event event) throws RuntimeException {
+ try {
+ return method.invoke(object, event);
+ } catch (IllegalArgumentException e) {
+ logger.log(Level.SEVERE, "Error invoking method", e);
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Error invoking method", e);
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ logger.log(Level.WARNING, "Invocation target error", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Allows the dispatcher to be used as an SWT listener. The event is
+ * passed to the method and the return value is ignored.
+ */
+ public void handleEvent(Event event) {
+ invoke(event);
+ }
+
+}
--- /dev/null
+package ie.dcu.swt;
+
+
+import ie.dcu.swt.event.*;
+
+import java.util.*;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+public class ImageControl extends Composite {
+
+ /**
+ * Minimum allowed zoom level (5% of original).
+ */
+ public static final float MIN_ZOOM = 0.05f;
+
+
+ /**
+ * Maximum allowed zoom level (1000% of original)
+ */
+ public static final float MAX_ZOOM = 10.f;
+
+ /**
+ * Default zoom in/out step (10%)
+ */
+ public static final float DEFAULT_ZOOM_STEP = 0.1f;
+
+
+ /**
+ * Default zoom level (100%)
+ */
+ public static final float DEFAULT_ZOOM = 1.0f;
+
+
+ /**
+ * Canvas where the image is drawn.
+ */
+ private final Canvas canvas;
+
+
+ /**
+ * Scroll bars.
+ */
+ private final ScrolledComposite scroller;
+
+
+ /**
+ * The images bounding rectangle (after scaling + translation).
+ */
+ private final Rectangle bounds;
+
+
+ /**
+ * Listeners for zoom events.
+ */
+ private final List<ZoomListener> listeners;
+
+
+ /**
+ * The original image.
+ */
+ private ObservableImage image;
+
+
+ /**
+ * The scaled image instance.
+ */
+ private Image scaled;
+
+
+ /**
+ * Current zoom level.
+ */
+ private float scale;
+
+
+ /**
+ * Current zoom in/out step.
+ */
+ private float scaleStep;
+
+
+ /**
+ * Flag to indicate that a black line border around the image
+ * should be painted. Default is <code>true</code>.
+ */
+ private boolean drawBorder;
+
+
+ /**
+ * Cached original image bounding rectangle.
+ */
+ private transient Rectangle cachedImageBounds;
+
+
+ /**
+ * Construct an image view. The style can be any style that a Composite can
+ * have. The parent may not be {@code null}. Although this class extends
+ * Composite, it does not make sense to set a layout on it or to add other
+ * widgets to it.
+ *
+ * @param parent
+ * The parent Composite.
+ * @param style
+ * Style constants.
+ */
+ public ImageControl(Composite parent, int style) {
+ super(parent, style);
+
+ // Fill entire composite
+ setLayout(new FillLayout());
+
+ // Create scroll bars (always shown)
+ scroller = new ScrolledComposite(this, SWT.H_SCROLL | SWT.V_SCROLL);
+
+ // Create canvas (set no background for efficiency, we'll draw it ourselves)
+ canvas = new Canvas(scroller, SWT.NO_BACKGROUND);
+
+ // Initial bounds is an empty rectangle
+ bounds = new Rectangle(0, 0, 0, 0);
+
+ // Zoom listeners
+ listeners = new ArrayList<ZoomListener>(2);
+
+ // Setup scroll bars
+ scroller.setExpandHorizontal(true);
+ scroller.setExpandVertical(true);
+ scroller.setContent(canvas);
+
+ // Listen for paint events and repaint canvas
+ canvas.addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent e) {
+ paintCanvas(e.gc);
+ }
+ });
+
+ // Listen for resizes and handle appropriately
+ scroller.addControlListener(new ControlAdapter() {
+ public void controlResized(ControlEvent e) {
+ handleResize();
+ }
+ });
+
+ // Listen for dispose event
+ this.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ handleDispose();
+ }
+ });
+
+ // Set defaults
+ scale = DEFAULT_ZOOM;
+ scaleStep = DEFAULT_ZOOM_STEP;
+ drawBorder = true;
+ }
+
+
+ /**
+ * Called when the scroll area is resized
+ */
+ protected void handleResize() {
+ repaint();
+ }
+
+
+ /**
+ * Handle a dispose event.
+ */
+ protected void handleDispose() {
+ // Throw away the scaled image
+ disposeScaledImage();
+ }
+
+
+ /**
+ * Called when the canvas needs to be painted.
+ *
+ * @param gc
+ * The paint graphics context.
+ */
+ protected void paintCanvas(GC gc) {
+
+ // Determine the clip (visible canvas area)
+ Rectangle clip = getVisibleCanvasArea();
+
+ if (!hasImage()) {
+ // Fill background
+ gc.fillRectangle(clip);
+
+ return;
+ }
+
+ if (!hasScaledImage()) {
+ // Create a scaled version of the image
+ createScaledImage();
+ }
+
+ // Set the clip
+ gc.setClipping(clip);
+
+ // Draw the scaled image
+ gc.drawImage(scaled, bounds.x, bounds.y);
+
+ // Draw background
+ int tGap = bounds.y - clip.y;
+ int bGap = bounds.y + bounds.height - clip.y + clip.height;
+ int lGap = bounds.x - clip.x;
+ int rGap = bounds.x + bounds.width - clip.x + clip.width;
+
+ if (bounds.height < clip.height) {
+ gc.fillRectangle(clip.x, clip.y, clip.width, tGap);
+ gc.fillRectangle(clip.x, bounds.y + bounds.height, clip.width, bGap);
+ }
+
+ if (bounds.width < clip.width) {
+ gc.fillRectangle(clip.x, clip.y + tGap, lGap, bounds.height);
+ gc.fillRectangle(bounds.x + bounds.width, clip.y + tGap,
+ rGap, bounds.height);
+ }
+
+ // Draw a border around the image
+ if (drawBorder) {
+ SwtUtils.setForeground(gc, SWT.COLOR_BLACK);
+ gc.drawRectangle(bounds.x-1, bounds.y-1, bounds.width+1, bounds.height+1);
+ }
+ }
+
+
+ /**
+ * Handles changes in the image data.
+ */
+ protected void handleImageChanged(ImageEvent e) {
+
+ // If dimensions changed, re-constructed entire scaled image
+ if (e.dimensionsChanged()) {
+ cachedImageBounds = null;
+ createScaledImage();
+ return;
+ }
+
+
+ // If no scaled image available, construct one
+ if (!hasScaledImage()) {
+ recomputeBounds();
+
+ // Construct scaled image
+ scaled = new Image(getDisplay(),
+ bounds.width, bounds.height);
+ }
+
+ Rectangle modified =
+ e.modified.intersection(getImageBounds());
+
+ // Scale modified rectangle
+ Rectangle target =
+ SwtUtils.scale(modified, scale);
+
+ // Create graphics context
+ GC gc = new GC(scaled);
+
+ // Set clip
+ gc.setClipping(target);
+
+ // Redraw modified part
+ gc.drawImage(
+ image.getImage(),
+ modified.x,
+ modified.y,
+ modified.width,
+ modified.height,
+ target.x,
+ target.y,
+ target.width,
+ target.height
+ );
+
+ // Dispose graphics context
+ gc.dispose();
+
+ // Repaint canvas
+ repaint(modified);
+ }
+
+
+ /**
+ * Returns the properly scaled version of the current image, after zooming has
+ * been performed. Returns <code>null</code> if there is no current image.
+ *
+ * @return The scaled image, or <code>null</code>.
+ */
+ public Image getScaledImage() {
+ if (!hasScaledImage()) {
+ createScaledImage();
+ }
+ return scaled;
+ }
+
+
+ /**
+ * Get the current image. Returns {@code null} if there is none.
+ *
+ * @return The image, or <code>null</code>.
+ */
+ public ObservableImage getImage() {
+ return image;
+ }
+
+
+ /**
+ * Set the currently displayed image. This method has no effect if the given
+ * image is the same as the currently displayed one. Otherwise, the image is
+ * set to the new one, the zoom is reset to 1.0 and the control is repainted.
+ * The given image may also be {@code null} to clear the display to its
+ * background color.
+ *
+ * <p><b>Note:</b> The old image is <i>not</i> disposed of.
+ *
+ * @param image
+ * The image, or <code>null</code>.
+ */
+ public void setImage(ObservableImage image) {
+ if (this.image != image) {
+ // Dispose of scaled image
+ disposeScaledImage();
+
+ // Remove old listener
+ if (this.image != null) {
+ this.image.removeImageListener(imageListener);
+ }
+
+ // Set image
+ this.image = image;
+
+ // Add new listener
+ if (this.image != null) {
+ this.image.addImageListener(imageListener);
+ }
+
+ // Invalidate cached bounds
+ this.cachedImageBounds = null;
+
+ // Reset scale
+ this.scale = 1.0f;
+
+ // Repaint
+ repaint();
+ }
+ }
+
+
+ /**
+ * Set flag to indicate that a black line border around the image will be
+ * drawn. Will repaint if changed.
+ *
+ * @param drawBorder
+ * <code>true</code> to draw a border.
+ */
+ public void setDrawBorder(boolean drawBorder) {
+ if (this.drawBorder != drawBorder) {
+ this.drawBorder = drawBorder;
+ repaint();
+ }
+ }
+
+
+ /**
+ * Returns <code>true</code> if the drawBorder flag is set.
+ *
+ * @return <code>true</code> if a black border around the image will be
+ * drawn.
+ * @see ImageControl#setDrawBorder(boolean)
+ */
+ public boolean getDrawBorder() {
+ return drawBorder;
+ }
+
+
+
+ /**
+ * Returns {@code true} if the view has an image and it has not been disposed.
+ *
+ * @return <code>true</code> if view has an image
+ */
+ public boolean hasImage() {
+ return image != null && !image.isDisposed();
+ }
+
+
+ /**
+ * Set the currently displayed image. This method has no effect if the given
+ * image is the same as the currently displayed one. Otherwise, the image is
+ * set to the new one, the zoom is reset to 1.0 and the control is repainted.
+ * The given image may also be {@code null} to clear the display to its
+ * background color.
+ *
+ * @param image
+ * The image, or <code>null</code>.
+ * @param disposeOld
+ * If <code>true</code> the old image is disposed of before
+ * changing. But only if the passed image is not the same as the old
+ * one.
+ */
+ public void setImage(ObservableImage image, boolean disposeOld) {
+ if (this.image != image) {
+ if (this.image != null && disposeOld) {
+ this.image.dispose();
+ }
+ setImage(image);
+ }
+ }
+
+
+ /**
+ * Returns the current zoom level, which will always be between MIN_ZOOM and
+ * MAX_ZOOM. A zoom level of 1.0 is the original size.
+ *
+ * @return The zoom level.
+ */
+ public float getZoom() {
+ return scale;
+ }
+
+
+ /**
+ * Set the zoom level. The value given must be between MIN_ZOOM and MAX_ZOOM.
+ * If the zoom level is different from the current zoom level, the zoom level
+ * is updated and the image is repainted at the new zoom level.
+ *
+ * @param zoom
+ * The zoom level to set.
+ */
+ public void setZoom(float zoom) {
+ if (this.scale != zoom) {
+ if (zoom < MIN_ZOOM) {
+ throw new IllegalArgumentException("zoom < MIN_ZOOM");
+ }
+
+ if (zoom > MAX_ZOOM) {
+ throw new IllegalArgumentException("zoom > MAX_ZOOM");
+ }
+
+ disposeScaledImage();
+ this.scale = zoom;
+
+ repaint();
+
+ // Notify listeners
+ fireZoomChanged();
+ }
+ }
+
+
+ /**
+ * Returns the current zoom step, i.e. the amount of zoom change that will be
+ * caused by a zoom in or zoom out operation.
+ *
+ * @return The current zoom step.
+ */
+ public float getZoomStep() {
+ return scaleStep;
+ }
+
+
+ /**
+ * Set the current zoom step, i.e. the amount of zoom change that will be
+ * caused by a zoom in or zoom out operation.
+ *
+ * @param zoomStep
+ * The new zoom step (must be {@code > 0}).
+ */
+ public void setZoomStep(float zoomStep) {
+ if (zoomStep < 0) {
+ throw new IllegalArgumentException("zoomStep < 0");
+ }
+
+ this.scaleStep = zoomStep;
+ }
+
+
+ /**
+ * Zoom in using the current zoom step. This method will never allow the zoom
+ * to be greater than MAX_ZOOM.
+ */
+ public void zoomIn() {
+ if (image != null) {
+ float zoom = Math.min(MAX_ZOOM, scale + scaleStep);
+ setZoom(zoom);
+ }
+ }
+
+
+ /**
+ * Zoom in using the current zoom step. This method will never allow the zoom
+ * to be less than MIN_ZOOM.
+ */
+ public void zoomOut() {
+ if (image != null) {
+ float zoom = Math.max(MIN_ZOOM, scale - scaleStep);
+ setZoom(zoom);
+ }
+ }
+
+
+ /**
+ * Zoom the image back to it's original size.
+ */
+ public void zoomOriginal() {
+ setZoom(1.0f);
+ }
+
+
+ /**
+ * Zoom the image to it's best fit inside the currently visible scroll area.
+ * The aspect ratio is maintained.
+ */
+ public void zoomBestFit() {
+ if (image != null) {
+ float zoom = getBestFitScale();
+ setZoom(zoom);
+ }
+ }
+
+
+ /**
+ * Returns <code>true</code> if calling {@link ImageControl#zoomIn()} will have
+ * an effect.
+ *
+ * @return <code>true</code> if zooming in is possible.
+ */
+ public boolean canZoomIn() {
+ if (image != null) {
+ float zoom = Math.min(MAX_ZOOM, scale + scaleStep);
+ return zoom != scale;
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns <code>true</code> if calling {@link ImageControl#zoomOut()} will
+ * have an effect.
+ *
+ * @return <code>true</code> if zooming out is possible.
+ */
+ public boolean canZoomOut() {
+ if (image != null) {
+ float zoom = Math.max(MIN_ZOOM, scale - scaleStep);
+ return zoom != scale;
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns <code>true</code> if calling {@link ImageControl#zoomOriginal()}
+ * will have an effect.
+ *
+ * @return <code>true</code> if zooming to the original size will change the
+ * zoom level.
+ */
+ public boolean canZoomOriginal() {
+ if (image != null) {
+ return scale != 1.0;
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns <code>true</code> if calling {@link ImageControl#zoomBestFit()} will
+ * have an effect.
+ *
+ * @return <code>true</code> if zooming to the "best-fit" will change the
+ * zoom level.
+ */
+ public boolean canZoomBestFit() {
+ if (image != null) {
+ return scale != getBestFitScale();
+ }
+ return false;
+ }
+
+
+ /**
+ * Force the visible canvas area to repaint itself.
+ */
+ public void repaint() {
+ recomputeBounds();
+ Rectangle r = getVisibleCanvasArea();
+ canvas.redraw(r.x, r.y, r.width, r.height, false);
+ }
+
+
+ /**
+ * Force the given part of the image to repaint itself. The image rectangle is
+ * automatically scaled and translated to canvas coordinates. If the image is
+ * <code>null</code>, then the function does nothing.
+ *
+ * @param part
+ * The part of the image repaint.
+ */
+ public void repaint(Rectangle part) {
+ if (image != null) {
+ recomputeBounds();
+ Rectangle canv = imageToCanvas(part);
+ Rectangle clip = getVisibleCanvasArea().intersection(canv);
+ canvas.redraw(clip.x, clip.y, clip.width, clip.height, false);
+ }
+ }
+
+
+ /**
+ * Returns {@code true} if the given point on the canvas is contained within
+ * the area covered by the image displayed on the canvas. If there is no
+ * image, returns {@code false}
+ *
+ * @param canvasPt
+ * A point in canvas coordinates.
+ * @return <code>true</code> if contained in the image.
+ */
+ public boolean imageContains(Point canvasPt) {
+ if (image != null) {
+ return bounds.contains(canvasPt);
+ }
+ return false;
+ }
+
+
+
+ /**
+ * Translate a point on the canvas to it's corresponding point on the image
+ * currently that is displayed on the canvas. If there is no current image
+ * set, an {@code IllegalStateException} is thrown. The returned point is at
+ * the original image's scale.
+ *
+ * @param pt
+ * The point to translate.
+ * @return The corresponding point on the image.
+ */
+ public Point canvasToImage(Point pt) {
+ checkHaveImage();
+
+ // translate to image (0,0)
+ int x = pt.x - bounds.x;
+ int y = pt.y - bounds.y;
+
+ // scale
+ x = (int) Math.floor(x / scale);
+ y = (int) Math.floor(y / scale);
+
+ return new Point(x, y);
+ }
+
+
+ /**
+ * Translate a point on the displayed image (at it's original scale) to the
+ * corresponding point on the canvas. If there is no current image set, an
+ * {@code IllegalStateException} is thrown.
+ *
+ * @param pt
+ * The point on the canvas.
+ * @return The corresponding point on the image.
+ */
+ public Point imageToCanvas(Point pt) {
+ checkHaveImage();
+
+ // scale
+ int x = (int) Math.floor(pt.x * scale);
+ int y = (int) Math.floor(pt.y * scale);
+
+ // translate
+ x += bounds.x;
+ y += bounds.y;
+
+ return new Point(x, y);
+ }
+
+
+ /**
+ * Translates a rectangle on the image to a rectangle on the canvas. If there
+ * is no current image set, an {@code IllegalStateException} is thrown. If the
+ * given rectangle lies outside the image, only the part that lies inside the
+ * canvas image bounds is returned (the intersection). If it is completely
+ * outside the canvas image bounds, an empty rectangle is returned.
+ *
+ * @param rect
+ * The rectangle to translate.
+ * @return The translated rectangle.
+ */
+ public Rectangle imageToCanvas(Rectangle rect) {
+ checkHaveImage();
+
+ int x = (int) Math.floor(rect.x * scale) + bounds.x;
+ int y = (int) Math.floor(rect.y * scale) + bounds.y;
+ int w = (int) Math.floor(rect.width * scale);
+ int h = (int) Math.floor(rect.height * scale);
+
+ return new Rectangle(x, y, w, h).intersection(bounds);
+ }
+
+
+ /**
+ * Translate a rectangle on the canvas to a rectangle on the image. If there
+ * is no current image set, an {@code IllegalStateException} is thrown.
+ *
+ * @param rect
+ * The rectangle to translate.
+ * @return The translated rectangle.
+ */
+ public Rectangle canvasToImage(Rectangle rect) {
+ checkHaveImage();
+
+ int x = (int) Math.floor((rect.x - bounds.x) / scale);
+ int y = (int) Math.floor((rect.y - bounds.y) / scale);
+ int w = (int) Math.floor(rect.width / scale);
+ int h = (int) Math.floor(rect.height / scale);
+
+ return new Rectangle(x, y, w, h);
+ }
+
+
+ /**
+ * Get the canvas that the image is drawn on.
+ *
+ * @return The canvas.
+ */
+ public Canvas getCanvas() {
+ return canvas;
+ }
+
+
+ /**
+ * Convenience method to set the canvas background color.
+ *
+ * @param color
+ * The color to set.
+ */
+ public void setCanvasBackground(Color color) {
+ canvas.setBackground(color);
+ }
+
+
+
+ /**
+ * Returns the bounds of the original image, or <code>null</code> if there
+ * is no image.
+ *
+ * @return The bounding rectangle of the image, or <code>null</code>.
+ */
+ public Rectangle getImageBounds() {
+
+ if (cachedImageBounds == null) {
+ if (image != null) {
+ cachedImageBounds = image.getBounds();
+ }
+ }
+ return cachedImageBounds;
+ }
+
+
+ /**
+ * Returns the bounds of the image, as currently displayed on the canvas. This
+ * is the original image bounds, translated and scaled onto the image canvas.
+ * Returns <code>null</code> if there is no set image.
+ *
+ * @return The canvas image bounds, or <code>null</code>.
+ */
+ public Rectangle getCanvasImageBounds() {
+ return bounds;
+ }
+
+
+ /**
+ * Add a zoom listener.
+ *
+ * @param listener
+ * The listener to add.
+ */
+ public void addZoomListener(ZoomListener listener) {
+ listeners.add(listener);
+ }
+
+
+ /**
+ * Remove a zoom listener.
+ *
+ * @param listener
+ * The listener to remove.
+ */
+ public void removeZoomListener(ZoomListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ /**
+ * Dispose of the old scaled image (hence invalidating it).
+ */
+ public void disposeScaledImage() {
+ if (scaled != null) {
+ if (!scaled.isDisposed()) {
+ scaled.dispose();
+ }
+ scaled = null;
+ }
+ }
+
+
+
+ /**
+ * Returns the scale of the "best-fit" for the currently visible scroll area,
+ * maintaining the aspect ratio and staying within the MAX_ZOOM and MIN_ZOOM
+ * bounds.
+ *
+ * @return The best fit scale.
+ */
+ private float getBestFitScale() {
+ Rectangle client = scroller.getClientArea();
+ Rectangle imrect = getImageBounds();
+
+ float sx = client.width / (float) imrect.width;
+ float sy = client.height / (float) imrect.height;
+
+ float zoom = Math.min(sx, sy);
+ if (zoom > MAX_ZOOM) zoom = MAX_ZOOM;
+ if (zoom < MIN_ZOOM) zoom = MIN_ZOOM;
+ return zoom;
+ }
+
+
+ /**
+ * Disposes of any old scaled image and constructs a new one.
+ */
+ private void createScaledImage() {
+ disposeScaledImage();
+
+ if (hasImage()) {
+ recomputeBounds();
+
+ scaled = new Image(getDisplay(), bounds.width, bounds.height);
+
+ GC gc = new GC(scaled);
+
+ Rectangle r = getImageBounds();
+ gc.drawImage(image.getImage(),
+ 0, 0, r.width, r.height,
+ 0, 0, bounds.width, bounds.height
+ );
+
+ gc.dispose();
+ }
+ }
+
+
+ /**
+ * Computes and returns the region on the canvas that is currently visible
+ * in the scroll pane.
+ *
+ * @return The visible canvas area.
+ */
+ private Rectangle getVisibleCanvasArea() {
+ Rectangle clip = scroller.getClientArea();
+ Point scrollPos = getScrollbarPosition();
+ clip.x += scrollPos.x;
+ clip.y += scrollPos.y;
+ return clip;
+ }
+
+
+ /**
+ * Get the horizontal and vertical scroll-bar positions.
+ *
+ * @return the scroll-bar position.
+ */
+ private Point getScrollbarPosition() {
+ int sx = scroller.getHorizontalBar().getSelection();
+ int sy = scroller.getVerticalBar().getSelection();
+ return new Point(sx, sy);
+ }
+
+
+ /**
+ * Recompute the canvas image bounds and update the scroll bars.
+ */
+ private void recomputeBounds() {
+
+ if (image == null) {
+ // No image
+ scroller.setMinSize(0,0);
+ bounds.x = bounds.y = 0;
+ bounds.width = bounds.height = 0;
+ return;
+ }
+
+ Rectangle imrect = getImageBounds();
+ Rectangle area = scroller.getClientArea();
+
+ // Scale image width and height
+ bounds.width = (int) (imrect.width * scale);
+ bounds.height = (int) (imrect.height * scale);
+
+ // Center image horizontally
+ if (bounds.width < area.width) {
+ int dx = area.width - bounds.width;
+ bounds.x = dx / 2;
+ } else {
+ bounds.x = 0;
+ }
+
+ // Center image vertically
+ if (bounds.height < area.height) {
+ int dy = area.height - bounds.height;
+ bounds.y = dy / 2;
+ } else {
+ bounds.y = 0;
+ }
+
+ // Configure scroll bars
+ scroller.setMinSize(bounds.width, bounds.height);
+ }
+
+
+ /**
+ * Returns <code>true</code> if we have an up to date copy of a properly
+ * scaled version of the image.
+ *
+ * @return <code>true</code> if a scaled image is available.
+ */
+ private boolean hasScaledImage() {
+ return scaled != null && !scaled.isDisposed();
+ }
+
+
+ /**
+ * Throws an {@code IllegalStateException} if there is no image.
+ */
+ private void checkHaveImage() {
+ if (image == null) {
+ throw new IllegalStateException("image == null");
+ }
+ }
+
+
+ /**
+ * Fire a zoom changed event.
+ */
+ private void fireZoomChanged() {
+ ZoomEvent evt = null;
+ for (ZoomListener z : listeners) {
+ if (evt == null) {
+ evt = new ZoomEvent(this);
+ }
+ z.zoomChanged(evt);
+ }
+ }
+
+
+ private ImageListener imageListener = new ImageListener() {
+ public void imageChanged(ImageEvent e) {
+ handleImageChanged(e);
+ }
+ };
+
+}
--- /dev/null
+package ie.dcu.swt;
+
+import java.awt.image.*;
+
+import org.eclipse.swt.graphics.*;
+
+/**
+ * Converts SWT ImageData objects to BufferedImage objects and vice-versa.
+ *
+ * @author Kevin McGuinness
+ */
+public class ImageConverter {
+
+ public static BufferedImage convert(ImageData data) {
+ ColorModel cm = null;
+ PaletteData palette = data.palette;
+
+ if (palette.isDirect) {
+ cm = new DirectColorModel(
+ data.depth,
+ palette.redMask,
+ palette.greenMask,
+ palette.blueMask
+ );
+
+ WritableRaster raster = cm.createCompatibleWritableRaster(
+ data.width, data.height);
+
+ BufferedImage im = new BufferedImage(cm, raster, false, null);
+
+ int[] pixels = new int[3];
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ int pixel = data.getPixel(x, y);
+ RGB rgb = palette.getRGB(pixel);
+ pixels[0] = rgb.red;
+ pixels[1] = rgb.green;
+ pixels[2] = rgb.blue;
+ raster.setPixels(x, y, 1, 1, pixels);
+ }
+ }
+ return im;
+
+ } else {
+
+ RGB[] rgbs = palette.getRGBs();
+ byte[] red = new byte[rgbs.length];
+ byte[] green = new byte[rgbs.length];
+ byte[] blue = new byte[rgbs.length];
+
+ for (int i = 0; i < rgbs.length; i++) {
+ RGB rgb = rgbs[i];
+ red[i] = (byte) rgb.red;
+ green[i] = (byte) rgb.green;
+ blue[i] = (byte) rgb.blue;
+ }
+
+ if (data.transparentPixel != -1) {
+ cm = new IndexColorModel(
+ data.depth,
+ rgbs.length,
+ red, green, blue,
+ data.transparentPixel
+ );
+
+ } else {
+ cm = new IndexColorModel(
+ data.depth,
+ rgbs.length,
+ red, green, blue
+ );
+ }
+
+ WritableRaster raster = cm.createCompatibleWritableRaster(
+ data.width, data.height);
+
+ BufferedImage im = new BufferedImage(cm,
+ raster, false, null);
+
+ int[] pixels = new int[1];
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ int pixel = data.getPixel(x, y);
+ pixels[0] = pixel;
+ raster.setPixel(x, y, pixels);
+ }
+ }
+
+ return im;
+ }
+ }
+
+
+ public static ImageData convertToSWT(BufferedImage im) {
+
+ ColorModel cm = im.getColorModel();
+
+ if (cm instanceof DirectColorModel) {
+ DirectColorModel dcm = (DirectColorModel) cm;
+
+ PaletteData palette = new PaletteData(
+ dcm.getRedMask(),
+ dcm.getGreenMask(),
+ dcm.getBlueMask()
+ );
+
+ ImageData data = new ImageData(
+ im.getWidth(), im.getHeight(),
+ dcm.getPixelSize(), palette
+ );
+
+ WritableRaster raster = im.getRaster();
+
+ int[] pixels = new int[3];
+ RGB rgb = new RGB(0,0,0);
+
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ raster.getPixel(x, y, pixels);
+ rgb.red = pixels[0];
+ rgb.green = pixels[1];
+ rgb.blue = pixels[2];
+ int pixel = palette.getPixel(rgb);
+ data.setPixel(x, y, pixel);
+ }
+ }
+
+ return data;
+
+
+ } else if (cm instanceof IndexColorModel) {
+ IndexColorModel icm = (IndexColorModel) cm;
+
+ int size = icm.getMapSize();
+ byte[] reds = new byte[size];
+ byte[] greens = new byte[size];
+ byte[] blues = new byte[size];
+
+ icm.getReds(reds);
+ icm.getGreens(greens);
+ icm.getBlues(blues);
+
+ RGB[] rgbs = new RGB[size];
+ for (int i = 0; i < rgbs.length; i++) {
+ rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
+ }
+
+ PaletteData palette = new PaletteData(rgbs);
+
+ ImageData data = new ImageData(
+ im.getWidth(), im.getHeight(),
+ icm.getPixelSize(), palette
+ );
+
+
+ data.transparentPixel = icm.getTransparentPixel();
+ WritableRaster raster = im.getRaster();
+
+ int[] pixels = new int[1];
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ raster.getPixel(x, y, pixels);
+ data.setPixel(x, y, pixels[0]);
+ }
+ }
+
+ return data;
+ }
+
+ return null;
+ }
+}
--- /dev/null
+package ie.dcu.swt;
+
+import ie.dcu.image.colormap.*;
+import ie.dcu.matrix.*;
+
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+
+/**
+ * Visualize matrices using color maps.
+ *
+ * @author Kevin McGuinness
+ */
+public class MatrixVisualizer {
+
+ private ColorMap colorMap;
+
+ public MatrixVisualizer() {
+ this(ColorMaps.hot());
+ }
+
+ public MatrixVisualizer(ColorMap map) {
+ setColorMap(map);
+ }
+
+ public ColorMap getColorMap() {
+ return colorMap;
+ }
+
+ public void setColorMap(ColorMap map) {
+ if (map == null) {
+ throw new IllegalArgumentException("colorMap == null");
+ }
+ this.colorMap = map;
+ }
+
+ public ImageData createVisualization(Matrix matrix) {
+ return visualize(matrix.toDoubleMatrix());
+ }
+
+ private ImageData visualize(DoubleMatrix matrix) {
+ rescale(matrix);
+ PaletteData palette = colorMap.createPaletteData();
+ ImageData image = new ImageData(matrix.cols, matrix.rows, 8, palette);
+
+ int n = colorMap.size();
+
+ for (int i = 0; i < matrix.rows; i++) {
+ int k = i * matrix.cols;
+
+ for (int j = 0; j < matrix.cols; j++, k++) {
+ int value = (int) (matrix.values[k] * (n-1));
+ image.setPixel(j, i, value);
+ }
+ }
+
+ return image;
+ }
+
+ private void rescale(DoubleMatrix matrix) {
+
+ double min = matrix.minValue();
+ double max = matrix.maxValue();
+ double range = max - min;
+
+ if (range == 0) {
+ range = 1;
+ }
+
+ for (int i = 0; i < matrix.size; i++) {
+ matrix.values[i] = (matrix.values[i] + min) / range;
+ }
+ }
+}
--- /dev/null
+package ie.dcu.swt;
+
+import ie.dcu.swt.event.*;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.*;
+
+public class ObservableImage {
+ private final List<ImageListener> listeners;
+ private Image image;
+ private ImageData data;
+ private Rectangle modified;
+ private GC gc;
+ private int refCount;
+ private boolean suspendNotifications;
+
+
+ public ObservableImage(Image image) {
+ this.listeners = new ArrayList<ImageListener>(2);
+ this.image = image;
+ }
+
+
+ public GC beginPaint() {
+ if (refCount > 0) {
+ refCount++;
+ return gc;
+ } else {
+ modified = null;
+ refCount = 1;
+ return (gc = new GC(image));
+ }
+ }
+
+
+ public void endPaint() {
+ endPaint(null);
+ }
+
+
+ public void endPaint(Rectangle modified) {
+ refCount--;
+ if (refCount < 0) {
+ throw new IllegalStateException();
+ }
+
+ updateModified(modified);
+
+ if (refCount == 0) {
+ data = null;
+ gc.dispose();
+ gc = null;
+ fireImageChanged();
+ }
+ }
+
+
+ public Image getImage() {
+ return image;
+ }
+
+
+ public ImageData getImageData() {
+ if (data == null) {
+ data = image.getImageData();
+ }
+ return data;
+ }
+
+
+ public void setImage(Image image, boolean disposeOld) {
+ if (gc != null) {
+ throw new IllegalStateException();
+ }
+
+ if (image == null) {
+ throw new NullPointerException();
+ }
+
+ boolean dimensionsChanged = false;
+ if (this.image != null && !this.image.isDisposed()) {
+ Rectangle oldBounds = this.image.getBounds();
+ Rectangle newBounds = image.getBounds();
+
+ dimensionsChanged = !oldBounds.equals(newBounds);
+ } else {
+ dimensionsChanged = true;
+ }
+
+ if (disposeOld) {
+ dispose();
+ }
+
+ this.image = image;
+
+ // Invalidate cached image data
+ this.data = null;
+ fireImageChanged(dimensionsChanged);
+ }
+
+
+
+ public void setSuspendNotifications(boolean suspend) {
+ this.suspendNotifications = suspend;
+ }
+
+
+ public boolean isDisposed() {
+ if (image != null) {
+ return image.isDisposed();
+ }
+ return false;
+ }
+
+
+ public void dispose() {
+ if (image != null && !image.isDisposed()) {
+ image.dispose();
+ }
+ data = null;
+ }
+
+
+ public Rectangle getBounds() {
+ return image.getBounds();
+ }
+
+
+ public Rectangle getModifiedArea() {
+ return modified;
+ }
+
+
+ public void addImageListener(ImageListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void removeImageListener(ImageListener listener) {
+ listeners.remove(listener);
+ }
+
+
+ public void fireImageChanged() {
+ fireImageChanged(getModifiedArea(), false);
+ }
+
+
+ public void fireImageChanged(Rectangle modified) {
+ fireImageChanged(modified, false);
+ }
+
+
+ private void fireImageChanged(boolean dimensionsChanged) {
+ fireImageChanged(getModifiedArea(), dimensionsChanged);
+ }
+
+
+ private void fireImageChanged(Rectangle modified, boolean dimensionsChanged) {
+ if (suspendNotifications) {
+ return;
+ }
+
+ ImageEvent e = null;
+ for (ImageListener i : listeners) {
+ if (e == null) {
+ e = new ImageEvent(this, modified, dimensionsChanged);
+ }
+ i.imageChanged(e);
+ }
+ }
+
+
+ private void updateModified(Rectangle m) {
+ if (m == null) {
+ this.modified = image.getBounds();
+ } else {
+ if (this.modified != null) {
+ this.modified = this.modified.union(m);
+ } else {
+ this.modified = SwtUtils.clone(m);
+ }
+ }
+ }
+}
+
--- /dev/null
+package ie.dcu.swt;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+
+/**
+ * Class for displaying a pop-up control in its own shell. The control behaves
+ * similar to a pop-up menu, but is a composite so can contain arbitrary
+ * controls.
+ *
+ * <p>
+ * <b>Sample Usage:</b>
+ *
+ * <pre>
+ * PopupComposite popup = new PopupComposite(getShell());
+ * Text text = new Text(popup, SWT.BORDER);
+ * popup.pack();
+ * popup.show(shell.toDisplay(new Point(10, 10)));
+ * </pre>
+ *
+ * @author Kevin McGuinness
+ */
+public class PopupComposite extends Composite {
+
+
+ /**
+ * Style of the shell that will house the composite
+ */
+ private static final int SHELL_STYLE =
+ SWT.MODELESS | SWT.NO_TRIM | SWT.ON_TOP | SWT.BORDER;
+
+
+ /**
+ * Shell that will house the composite
+ */
+ private final Shell shell;
+
+
+ /**
+ * Create a Pop-up composite with the default {@link SWT#BORDER} style.
+ *
+ * @param parent
+ * The parent shell.
+ */
+ public PopupComposite(Shell parent) {
+ this(parent, SWT.BORDER);
+ }
+
+
+ /**
+ * Create a Pop-up composite. The default layout is a fill layout.
+ *
+ * @param parent
+ * The parent shell.
+ * @param style
+ * The composite style.
+ */
+ public PopupComposite(Shell parent, int style) {
+ super(new Shell(parent, SHELL_STYLE), style);
+ shell = getShell();
+ shell.setLayout(new FillLayout());;
+ shell.addShellListener(new ActivationListener());
+ setLayout(createLayout());
+ }
+
+
+ /**
+ * Display the composite below the given tool item. The item will be sized
+ * such that it's width is at least the width of the given tool item.
+ *
+ * @param bar
+ * The tool bar.
+ * @param item
+ * The tool item.
+ */
+ public void showBelow(ToolBar bar, ToolItem item) {
+ Rectangle r = item.getBounds();
+ Point p = bar.toDisplay(new Point(r.x, r.y + r.height));
+ setSize(computeSize(item));
+ show(p);
+ }
+
+
+ /**
+ * Display the composite in its own shell at the given point.
+ *
+ * @param pt
+ * The point where the pop-up should appear.
+ */
+ public void show(Point pt) {
+ // Match shell and component sizes
+ shell.setSize(getSize());
+
+ if (pt != null) {
+ shell.setLocation(pt);
+ }
+
+ shell.open();
+ }
+
+
+ /**
+ * Display the pop-up where it was last displayed.
+ */
+ public void show() {
+ show(null);
+ }
+
+
+ /**
+ * Hide the pop-up.
+ */
+ public void hide() {
+ shell.setVisible(false);
+ }
+
+
+ /**
+ * Returns <code>true</code> if the shell is currently activated.
+ *
+ * @return <code>true</code> if the shell is visible.
+ */
+ public boolean isDisplayed() {
+ return shell.isVisible();
+ }
+
+
+ /**
+ * Creates the default layout for the composite.
+ *
+ * @return the default layout.
+ */
+ private FillLayout createLayout() {
+ FillLayout layout = new FillLayout();
+ layout.marginWidth = 5;
+ layout.marginHeight = 5;
+ return layout;
+ }
+
+
+ /**
+ * Computes the optimal size with respect to the given tool item.
+ *
+ * @param item
+ * The tool item.
+ * @return The optimal size.
+ */
+ private Point computeSize(ToolItem item) {
+ Point s2 = computeSize(item.getWidth(), SWT.DEFAULT);
+ Point s1 = computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ return s1.x > s2.x ? s1 : s2;
+ }
+
+
+ /**
+ * Class that handles shell appearance and disappearance appropriately.
+ * Specifically, it hides the shell when it becomes de-activated (for example,
+ * when the user clicks on the parent shell). Also, there is a minimum delay
+ * which is enforced between showing and hiding the pop-up, to prevent
+ * undesirable behavior such as hiding and immediately re-displaying the
+ * pop-up when the user selects a button responsible for showing the tool
+ * item.
+ */
+ private final class ActivationListener extends ShellAdapter {
+ private static final int TIMEOUT = 500;
+ private long time = -1;
+
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ // Record time of event
+ time = (e.time & 0xFFFFFFFFL);
+
+ // Hide
+ hide();
+ }
+
+
+ @Override
+ public void shellActivated(ShellEvent e) {
+ if (time > 0) {
+ // Find elapsed time
+ long elapsed = ((e.time & 0xFFFFFFFFL) - time);
+
+ // If less than a timeout, don't activate
+ if (elapsed < TIMEOUT) {
+ hide();
+
+ // Next activation event is fine
+ time = -1;
+ }
+ }
+ }
+ };
+
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.swt;
+
+import ie.dcu.swt.event.*;
+import ie.dcu.util.FileUtils;
+
+import java.io.*;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.dnd.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Miscellaneous SWT Utility functions.
+ *
+ * @author Kevin McGuinness
+ */
+public class SwtUtils {
+
+ /**
+ * Out-code TOP.
+ */
+ public static final int TOP = java.awt.Rectangle.OUT_TOP;
+
+ /**
+ * Out-code LEFT.
+ */
+ public static final int LEFT = java.awt.Rectangle.OUT_LEFT;
+
+ /**
+ * Out-code BOTTOM.
+ */
+ public static final int BOTTOM = java.awt.Rectangle.OUT_BOTTOM;
+
+ /**
+ * Out-code RIGHT.
+ */
+ public static final int RIGHT = java.awt.Rectangle.OUT_RIGHT;
+
+ /**
+ * Clone a rectangle.
+ */
+ public static Rectangle clone(Rectangle r) {
+ return new Rectangle(r.x, r.y, r.width, r.height);
+ }
+
+ /**
+ * Clone a point.
+ */
+ public static Point clone(Point p) {
+ return new Point(p.x, p.y);
+ }
+
+
+ /**
+ * Scale a rectangle.
+ *
+ * @param r
+ * The rectangle.
+ * @param scale
+ * The scale.
+ * @return A scaled rectangle.
+ */
+ public static Rectangle scale(Rectangle r, float scale) {
+ return new Rectangle(
+ (int) Math.floor(r.x * scale),
+ (int) Math.floor(r.y * scale),
+ (int) Math.floor(r.width * scale),
+ (int) Math.floor(r.height * scale)
+ );
+ }
+
+ /**
+ * Convert a SWT rectangle to an AWT rectangle.
+ *
+ * @param r
+ * An SWT Rectangle.
+ * @return An AWT Rectangle.
+ */
+ public static java.awt.Rectangle convert(Rectangle r) {
+ return new java.awt.Rectangle(r.x, r.y, r.width, r.height);
+ }
+
+ /**
+ * Determines where the specified coordinates lie with respect to this
+ * <code>Rectangle</code>. This method computes a binary OR of the
+ * appropriate mask values indicating, for each side of this
+ * <code>Rectangle</code>, whether or not the specified coordinates are on
+ * the same side of the edge as the rest of this <code>Rectangle</code>.
+ *
+ * @param r
+ * A rectangle.
+ * @param p
+ * A point.
+ * @return the logical OR of all appropriate out codes.
+ * @see #LEFT
+ * @see #TOP
+ * @see #RIGHT
+ * @see #BOTTOM
+ */
+ public static int outcode(Rectangle r, Point p) {
+ return convert(r).outcode(p.x, p.y);
+ }
+
+ /**
+ * Determines where the specified coordinates lie with respect to this
+ * <code>Rectangle</code>. This method computes a binary OR of the
+ * appropriate mask values indicating, for each side of this
+ * <code>Rectangle</code>, whether or not the specified coordinates are on
+ * the same side of the edge as the rest of this <code>Rectangle</code>.
+ *
+ * @param r
+ * A rectangle.
+ * @param x
+ * the specified x coordinate
+ * @param y
+ * the specified y coordinate
+ * @return the logical OR of all appropriate out codes.
+ *
+ * @see #LEFT
+ * @see #TOP
+ * @see #RIGHT
+ * @see #BOTTOM
+ */
+ public static int outcode(Rectangle r, float x, float y){
+ return convert(r).outcode(x, y);
+ }
+
+
+ // Point class for Liang-Barsky algorithm
+ private static class Pt {
+ float x, y;
+
+ Pt(float x, float y) {
+ this.x = x; this.y = y;
+ }
+ }
+
+
+ // Rectangle class for Liang-Barsky algorithm
+ private static class Rect {
+ float ymin, ymax, xmin, xmax;
+
+ Rect(float x, float y, float w, float h) {
+ // max needs -1 otherwise its outside the rectangle!
+ ymin = y; ymax = y + h - 1;
+ xmin = x; xmax = x + w - 1;
+ }
+
+ boolean contains(Pt p) {
+ return p.x >= xmin && p.x <= xmax && p.y >= ymin && p.y <= ymax;
+ }
+ }
+
+
+ // Liang-Barsky clip test method
+ private static boolean clipt(float y, float x, Pt p) {
+ if (y > 0) {
+ float t = x / y;
+ if (t > p.y) {
+ return false;
+ } else if (t > p.x) {
+ p.x = t;
+ }
+ } else if (y < 0) {
+ float t = x / y;
+ if (t < p.x) {
+ return false;
+ } else if (t < p.y) {
+ p.y = t;
+ }
+ } else if (x > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ // Liang-barsky line clipping algorithm
+ private static boolean liangBarskyClip(Rect r, Pt p, Pt q) {
+ float dx = q.x - p.x;
+ float dy = q.y - p.y;
+
+ if (dx == 0 && dy == 0 && r.contains(p)) {
+ return true;
+ }
+
+ Pt t = new Pt(0, 1);
+ if (clipt(dx, r.xmin - p.x, t)) {
+ if (clipt(-dx, p.x - r.xmax, t)) {
+ if (clipt(dy, r.ymin - p.y, t)) {
+ if (clipt(-dy, p.y - r.ymax, t)) {
+ if (t.y < 1) {
+ q.x = p.x + t.y * dx;
+ q.y = p.y + t.y * dy;
+ }
+ if (t.x > 0) {
+ p.x += t.x * dx;
+ p.y += t.x * dy;
+ }
+
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Clip a line using the Liang-Barsky line clipping algorithm.
+ *
+ * @param r
+ * The clipping rectangle.
+ * @param p
+ * First point on the line segment. If the point lies outside the
+ * clipping rectangle it will be clipped to the nearest point on the
+ * line inside the clipping rectangle on exit.
+ * @param q
+ * Second point on the line segment. If the point lies outside the
+ * clipping rectangle it will be clipped to the nearest point on the
+ * line inside the clipping rectangle on exit.
+ *
+ * @return <code>true</code> if the line has a visible segment inside the
+ * clipping rectangle, <code>false</code> if it is completely
+ * outside the clipping window.
+ */
+ public static boolean clip(Rectangle r, Point p, Point q) {
+ Rect r1 = new Rect(r.x, r.y, r.width, r.height);
+ Pt p1 = new Pt(p.x, p.y);
+ Pt p2 = new Pt(q.x, q.y);
+ boolean result = liangBarskyClip(r1, p1, p2);
+ p.x = (int) p1.x;
+ p.y = (int) p1.y;
+ q.x = (int) p2.x;
+ q.y = (int) p2.y;
+ return result;
+ }
+
+ /**
+ * Clone the given SWT image.
+ *
+ * @param image
+ * An SWT Image.
+ * @return A newly created SWT image that is a copy of the given image.
+ */
+ public static Image clone(Image image) {
+ return new Image(Display.getCurrent(), image, SWT.IMAGE_COPY);
+ }
+
+ /**
+ * Apply the given SWT system color as the foreground color in the given
+ * graphics context.
+ *
+ * @param gc
+ * The graphics context.
+ * @param swtColor
+ * An SWT system color.
+ */
+ public static void setForeground(GC gc, int swtColor) {
+ gc.setForeground(Display.getCurrent().getSystemColor(swtColor));
+ }
+
+ /**
+ * Apply the given SWT system color as the background color in the given
+ * graphics context.
+ *
+ * @param gc
+ * The graphics context.
+ * @param swtColor
+ * An SWT system color.
+ */
+ public static void setBackground(GC gc, int swtColor) {
+ gc.setBackground(Display.getCurrent().getSystemColor(swtColor));
+ }
+
+ /**
+ * Returns a standard RGB palette.
+ *
+ * @return An RGB palette.
+ */
+ public static PaletteData getRgbPalette() {
+ return new PaletteData(0xff, 0xff00, 0xff0000);
+ }
+
+ /**
+ * Create an RGB image with a transparent component, and fill the image with
+ * the transparent value.
+ *
+ * @param width
+ * The image width.
+ * @param height
+ * The image height.
+ * @return A newly created Image object.
+ */
+ public static Image createTransparentImage(int width, int height) {
+ PaletteData palette = getRgbPalette();
+
+ // Use a bit depth of 32 to allow transparent pixel outside RGB space
+ ImageData data = new ImageData(width, height, 32, palette);
+
+ // Create a transparent pixel outside the RGB color space
+ int transparent = palette.getPixel(new RGB(255,255,255)) + 2;
+ data.transparentPixel = transparent;
+
+ // Create a row of transparent pixels
+ int [] pixels = new int[data.width];
+ for (int i = 0; i < pixels.length; i++) {
+ pixels[i] = transparent;
+ }
+
+ // Copy pixels to entire image
+ for (int y = 0; y < data.height; y++) {
+ data.setPixels(0, y, pixels.length, pixels, 0);
+ }
+
+ // Create and return the image
+ return new Image(Display.getCurrent(), data);
+ }
+
+ /**
+ * Create a standard image for the current display with the given width and
+ * height.
+ *
+ * @param w
+ * The image width.
+ * @param h
+ * The image height.
+ * @return A newly created Image object.
+ */
+ public static Image createImage(int w, int h) {
+ return new Image(Display.getCurrent(), w, h);
+ }
+
+ /**
+ * Create a new image with for the current display with the size given by
+ * the width and height of the specified bounds object.
+ *
+ * @param bounds
+ * The image bounds.
+ * @return A newly created Image object.
+ */
+ public static Image createImage(Rectangle bounds) {
+ return createImage(bounds.width, bounds.height);
+ }
+
+ /**
+ * Scale the given image to fit inside the specified Rectangle.
+ *
+ * @param image
+ * An image
+ * @param rect
+ * The rectangle to fit to
+ * @param maintainAspectRatio
+ * if <code>true</code> the aspect ratio of the image is
+ * maintained.
+ * @return A newly created scaled image.
+ */
+ public static Image scaleImageToFit(Image image,
+ Rectangle rect, boolean maintainAspectRatio)
+ {
+ return scaleImageToFit(image, rect.width, rect.height,
+ maintainAspectRatio);
+ }
+
+ /**
+ * Scale the given image to fit inside the specified height and width.
+ *
+ * @param image
+ * An image
+ * @param width
+ * The width
+ * @param height
+ * The height
+ * @param maintainAspectRatio
+ * if <code>true</code> the aspect ratio of the image is
+ * maintained.
+ * @return A newly created scaled image.
+ */
+ public static Image scaleImageToFit(Image image,
+ int width, int height, boolean maintainAspectRatio)
+ {
+ ImageData data = image.getImageData();
+ data = scaleImageDataToFit(data, width, height, maintainAspectRatio);
+ return new Image(image.getDevice(), data);
+ }
+
+ /**
+ * Scale the given ImageData to fit inside the specified Rectangle.
+ *
+ * @param data
+ * A non-null ImageData object
+ * @param rect
+ * The rectangle to fit to
+ * @param maintainAspectRatio
+ * if <code>true</code> the aspect ratio of the image is
+ * maintained.
+ * @return A scaled ImageData object.
+ */
+ public static ImageData scaleImageDataToFit(ImageData data,
+ Rectangle rect, boolean maintainAspectRatio)
+ {
+ return scaleImageDataToFit(data, rect.width, rect.height,
+ maintainAspectRatio);
+ }
+
+ /**
+ * Scale the given ImageData to fit inside the specified height and width.
+ *
+ * @param data
+ * A non-null ImageData object
+ * @param width
+ * The width to fit to
+ * @param height
+ * The height to fit to
+ * @param maintainAspectRatio
+ * if <code>true</code> the aspect ratio of the image is
+ * maintained.
+ * @return A scaled ImageData object.
+ */
+ public static ImageData scaleImageDataToFit(ImageData data,
+ int width, int height, boolean maintainAspectRatio)
+ {
+ if (width <= 0) {
+ throw new IllegalArgumentException("width <= 0");
+ }
+
+ if (height <= 0) {
+ throw new IllegalArgumentException("height <= 0");
+ }
+
+ if (data == null) {
+ throw new IllegalArgumentException("data == null");
+ }
+
+ if (!maintainAspectRatio) {
+ // Direct scale
+ data = data.scaledTo(width, height);
+
+ } else {
+ // Compute scale
+ double sx = width / (double) data.width;
+ double sy = height / (double) data.height;
+ double scale = Math.min(sx, sy);
+
+ // Compute new dimensions
+ int newWidth = (int) (data.width * scale);
+ int newHeight = (int) (data.height * scale);
+
+ // Clamp dimensions
+ newWidth = Math.max(Math.min(newWidth, width), 1);
+ newHeight = Math.max(Math.min(newHeight, height), 1);
+
+ // Scale
+ data = data.scaledTo(newWidth, newHeight);
+ }
+
+ return data;
+ }
+
+ /**
+ * Load an image for the current display from the given file.
+ *
+ * @param file
+ * A file.
+ * @return A newly created image.
+ * @throws IOException
+ * If an error occurs loading the image.
+ */
+ public static Image loadImage(File file)
+ throws IOException
+ {
+ try {
+ return new Image(Display.getCurrent(), file.getAbsolutePath());
+ } catch (SWTException e) {
+ // Translate to checked exception!
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Load an image for the current display from the given InputStream.
+ *
+ * @param in
+ * An input stream.
+ * @return A newly created image.
+ * @throws IOException
+ * If an error occurs loading the image.
+ */
+ public static Image loadImage(InputStream in)
+ throws IOException
+ {
+ try {
+ return new Image(Display.getCurrent(), in);
+ } catch (SWTException e) {
+ // Translate to checked exception!
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Load the ImageData object from the given file.
+ *
+ * @param file
+ * A file.
+ * @return A newly created image.
+ * @throws IOException
+ * If an error occurs loading the image.
+ */
+ public static ImageData loadImageData(File file)
+ throws IOException
+ {
+ try {
+ return new ImageData(file.getAbsolutePath());
+ } catch (SWTException e) {
+ // Translate to checked exception!
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Load the ImageData object from the given InputStream.
+ *
+ * @param in
+ * An input stream.
+ * @return A newly created image.
+ * @throws IOException
+ * If an error occurs loading the image.
+ */
+ public static ImageData loadImageData(InputStream in)
+ throws IOException
+ {
+ try {
+ return new ImageData(in);
+ } catch (SWTException e) {
+ // Translate to checked exception!
+ throw new IOException("SWTException: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Add a label to the given ToolBar. The label will be aligned to the
+ * vertical center of the ToolBar.
+ *
+ * @param bar
+ * A ToolBar.
+ * @param text
+ * The label text.
+ */
+ public static void addLabel(ToolBar bar, String text) {
+ Composite box = new Composite(bar, SWT.NONE);
+ box.setLayout(new GridLayout());
+
+ // Create label
+ Label label = new Label(box, SWT.NONE);
+ label.setText(text);
+
+ // Layout in center of toolbar
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = true;
+ data.verticalAlignment = SWT.CENTER;
+ label.setLayoutData(data);
+
+ // Set item size
+ Point sz = box.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ ToolItem item = new ToolItem(bar, SWT.SEPARATOR);
+ item.setWidth(sz.x);
+
+ // Set control
+ item.setControl(box);
+ }
+
+ /**
+ * Add a separator to the given ToolBar.
+ *
+ * @param bar
+ * A ToolBar.
+ */
+ public static void addSeparator(ToolBar bar) {
+ new ToolItem(bar, SWT.SEPARATOR);
+ }
+
+ /**
+ * Add a Combo box to the given ToolBar.
+ *
+ * @param bar
+ * A ToolBar.
+ * @param width
+ * The horizontal width to make the ToolBar.
+ * @param style
+ * The Combo box style.
+ */
+ public static Combo addCombo(ToolBar bar, int width, int style) {
+ ToolItem item = new ToolItem(bar, SWT.SEPARATOR);
+ Combo combo = new Combo(bar, style);
+ item.setWidth(width);
+ item.setControl(combo);
+ return combo;
+ }
+
+ /**
+ * Center the given shell on the Display.
+ *
+ * @param shell
+ * A shell.
+ */
+ public static void center(Shell shell) {
+ Point p = shell.getSize();
+ Rectangle area = shell.getDisplay().getClientArea();
+
+ int x = area.width / 2 - p.x / 2 + area.x;
+ int y = area.height / 2 - p.y / 2 + area.y;
+
+ shell.setLocation(x, y);
+ }
+
+ /**
+ * Center the given shell with respect to the given parent shell.
+ *
+ * @param parent
+ * A shell.
+ * @param shell
+ * The shell to center.
+ */
+ public static void center(Shell parent, Shell shell) {
+ Point p = shell.getSize();
+ Rectangle area = parent.getBounds();
+
+ int x = area.width / 2 - p.x / 2 + area.x;
+ int y = area.height / 2 - p.y / 2 + area.y;
+
+ shell.setLocation(x, y);
+ }
+
+ /**
+ * Returns the SWT image format constant for the given by examining
+ * the file extension of the file.
+ *
+ * @param file
+ * A file.
+ * @return The SWT image format constant.
+ */
+ public static int getImageFormat(File file) {
+ String ext = FileUtils.getExtension(file);
+
+ if (ext != null) {
+ if (ext.equals("bmp")) {
+ return SWT.IMAGE_BMP;
+ } else if (ext.equals("jpg") || ext.equals("jpeg")) {
+ return SWT.IMAGE_JPEG;
+ } else if (ext.equals("png")) {
+ return SWT.IMAGE_PNG;
+ } else if (ext.equals("gif")) {
+ return SWT.IMAGE_GIF;
+ } else if (ext.equals("ico")) {
+ return SWT.IMAGE_ICO;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Save the given SWT image to the given file. The format will be determined
+ * from the file extension.
+ *
+ * @param im
+ * An image object.
+ * @param file
+ * The destination file.
+ * @throws IOException
+ * If there is an error saving the file or the format cannot be
+ * determined from the filename.
+ */
+ public static void saveImage(Image im, File file)
+ throws IOException {
+
+ saveImage(im.getImageData(), file);
+ }
+
+ /**
+ * Save the given SWT ImageData to the given file. The format will be
+ * determined from the file extension.
+ *
+ * @param im
+ * An ImageData object.
+ * @param file
+ * The destination file.
+ * @throws IOException
+ * If there is an error saving the file or the format cannot be
+ * determined from the filename.
+ */
+ public static void saveImage(ImageData im, File file)
+ throws IOException {
+
+ // Check if file is a directory
+ if (file.isDirectory()) {
+ throw new FileNotFoundException(String.format("%s is a directory", file));
+ }
+
+ // Check if we can write to file
+ if (file.exists()) {
+ if (!file.canWrite()) {
+ throw new IOException(String.format("Cannot write to %s", file));
+ }
+ }
+
+ // Get image format
+ int format = getImageFormat(file);
+
+ // Check for unrecognized format
+ if (format < 0) {
+ String ext = FileUtils.getExtension(file);
+ throw new IOException(String.format("Unknown format \"%s\"", ext));
+ }
+
+ // Create image loader
+ ImageLoader loader = new ImageLoader();
+ loader.data = new ImageData[] { im };
+
+ try {
+ // Save image
+ loader.save(file.getAbsolutePath(), format);
+
+ } catch (SWTException e) {
+ // Change unchecked exception to checked one
+ throw new IOException("SWT Exception: " + e.getMessage());
+ }
+
+ // Done
+ return;
+ }
+
+ /**
+ * Save the given SWT image to the given OutputStream.
+ *
+ * @param im
+ * An image object.
+ * @param out
+ * The destination output stream.
+ * @param format
+ * The SWT format constant.
+ * @throws IOException
+ * If there is an error saving the to the output stream.
+ */
+ public static void saveImage(Image im, OutputStream out, int format)
+ throws IOException
+ {
+ saveImage(im.getImageData(), out, format);
+ }
+
+ /**
+ * Save the given SWT ImageData object to the given OutputStream.
+ *
+ * @param im
+ * An ImageData object.
+ * @param out
+ * The destination output stream.
+ * @param format
+ * The SWT format constant.
+ * @throws IOException
+ * If there is an error saving the to the output stream.
+ */
+ public static void saveImage(ImageData im, OutputStream out, int format)
+ throws IOException
+ {
+ ImageLoader loader = new ImageLoader();
+ loader.data = new ImageData[] { im };
+
+ try {
+ // Save image
+ loader.save(out, format);
+
+ } catch (SWTException e) {
+ // Change unchecked exception to checked one
+ throw new IOException("SWT Exception: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Create a horizontal separator for the given parent.
+ *
+ * @param parent
+ * A composite.
+ * @return The created separator control.
+ */
+ public static Control createSeparator(Composite parent) {
+ return new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
+ }
+
+ /**
+ * Turn a SWT control into a file drop target.
+ *
+ * @param control
+ * An SWT control (not <code>null</code>).
+ * @param listener
+ * A file drop listener (not <code>null</code>).
+ */
+ public static void createFileDropTarget(
+ final Control control,
+ final FileDropListener listener)
+ {
+ // Create target
+ DropTarget target = new DropTarget(
+ control, DND.DROP_DEFAULT | DND.DROP_MOVE
+ );
+
+ // Create transfer
+ final FileTransfer transfer = FileTransfer.getInstance();
+ target.setTransfer(new Transfer[] { transfer });
+
+ // Add listener
+ target.addDropListener(new DropTargetAdapter() {
+ public void drop(final DropTargetEvent event) {
+
+
+ if (transfer.isSupportedType(event.currentDataType)) {
+ String[] files = (String[]) event.data;
+
+ // Send event
+ listener.drop(new FileDropEvent(control, files));
+ }
+ }
+ });
+
+ return;
+ }
+
+
+ /**
+ * Compress and RGB object into a ARGB integer.
+ *
+ * @param rgb
+ * RGB object.
+ * @return An ARGB integer.
+ */
+ public static int rgb2int(RGB rgb) {
+ return (rgb.red & 0xff) << 16 |
+ (rgb.green & 0xff) << 8 |
+ (rgb.blue & 0xff);
+ }
+
+ /**
+ * De-compress an ARGB integer into an RGB object.
+ *
+ * @param v
+ * an ARGB integer.
+ * @return An RGB object.
+ */
+ public static RGB int2rgb(int v) {
+ return new RGB((v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff);
+ }
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A file drop event.
+ *
+ * @author Kevin McGuinness
+ */
+public class FileDropEvent extends EventObject {
+
+ /**
+ * Serialization UID.
+ */
+ private static final long serialVersionUID = -7379037769871853671L;
+
+ /**
+ * The files.
+ */
+ private final ArrayList<File> files;
+
+ /**
+ * Create a file drop event.
+ *
+ * @param source
+ * The source of the file drop.
+ * @param files
+ * A list of absolute file paths.
+ */
+ public FileDropEvent(Object source, String[] files) {
+ super(source);
+ this.files = new ArrayList<File>(files.length);
+ for (String file : files) {
+ this.files.add(new File(file));
+ }
+ }
+
+ /**
+ * Returns an immutable collection of the files for the drop.
+ */
+ public Collection<File> files() {
+ return Collections.unmodifiableCollection(files);
+ }
+
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import java.util.*;
+
+/**
+ * A listener for FileDropEvents.
+ *
+ * @author Kevin McGuinness
+ */
+public interface FileDropListener extends EventListener {
+
+ /**
+ * Called when a drop occurs.
+ *
+ * @param evt
+ * The FileDropEvent object.
+ */
+ public void drop(FileDropEvent evt);
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import ie.dcu.swt.ObservableImage;
+
+import java.util.EventObject;
+
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * An image event.
+ *
+ * @see ObservableImage
+ * @see ImageListener
+ * @author Kevin McGuinness
+ *
+ */
+public class ImageEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The image the fired the event.
+ */
+ public final ObservableImage image;
+
+ /**
+ * The rectangle that was modified.
+ */
+ public final Rectangle modified;
+
+ /**
+ * Flag to indicate that the image dimensions changed.
+ */
+ private final boolean dimensionsChanged;
+
+ public ImageEvent(
+ ObservableImage image,
+ Rectangle modified,
+ boolean dimensionsChanged
+ ) {
+
+ super(image);
+ this.image = image;
+ this.dimensionsChanged = dimensionsChanged;
+
+ if (modified == null) {
+ modified = image.getBounds();
+ }
+
+ this.modified = modified;
+ }
+
+ public boolean dimensionsChanged() {
+ return dimensionsChanged;
+ }
+
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import java.util.*;
+
+/**
+ * Interface for image listeners.
+ *
+ * @see ie.dcu.swt.ObservableImage
+ * @author Kevin McGuinness
+ */
+public interface ImageListener extends EventListener {
+
+ /**
+ * Called when the image is changed.
+ *
+ * @param e
+ * The image event object.
+ */
+ public void imageChanged(ImageEvent e);
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import ie.dcu.swt.ImageControl;
+
+import java.util.EventObject;
+
+public class ZoomEvent extends EventObject {
+ private static final long serialVersionUID = 1L;
+
+ public final ImageControl view;
+ public final float scale;
+
+ public ZoomEvent(ImageControl view) {
+ super(view);
+ this.view = view;
+ this.scale = view.getZoom();
+ }
+}
--- /dev/null
+package ie.dcu.swt.event;
+
+import java.util.*;
+
+public interface ZoomListener extends EventListener {
+ public void zoomChanged(ZoomEvent e);
+}
--- /dev/null
+package ie.dcu.swt.layout;
+
+public enum BorderData {
+ North, South, Center, East, West, Ignore;
+
+ public final int index;
+
+ private BorderData() {
+ index = ordinal();
+ }
+};
--- /dev/null
+package ie.dcu.swt.layout;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Port of the AWT BorderLayout.
+ *
+ * @author Kevin McGuinness
+ */
+public class BorderLayout extends Layout {
+
+ // Typedef indices
+ private static final int N = BorderData.North.index;
+ private static final int S = BorderData.South.index;
+ private static final int C = BorderData.Center.index;
+ private static final int E = BorderData.East.index;
+ private static final int W = BorderData.West.index;
+
+ // Controls in all the regions.
+ private Control[] controls = new Control[5];
+
+ // Cached sizes.
+ private Point[] sizes;
+
+ // Preferred width and height
+ int width, height;
+
+ // Spacing between components
+ int hgap, vgap;
+
+ public BorderLayout() {
+ this(0);
+ }
+
+ public BorderLayout(int space) {
+ hgap = vgap = space;
+ }
+
+ public BorderLayout(int hgap, int vgap) {
+ this.hgap = hgap;
+ this.vgap = vgap;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.swt.widgets.Layout#computeSize(org.eclipse.swt.widgets.Composite
+ * , int, int, boolean)
+ */
+ protected Point computeSize(Composite composite, int wHint, int hHint,
+ boolean flushCache) {
+
+ if (sizes == null || flushCache == true) {
+ refreshSizes(composite.getChildren());
+ }
+
+ int w = wHint;
+ int h = hHint;
+
+ if (w == SWT.DEFAULT)
+ w = width;
+ if (h == SWT.DEFAULT)
+ h = height;
+
+ return new Point(w, h);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.eclipse.swt.widgets.Layout#layout(org.eclipse.swt.widgets.Composite,
+ * boolean)
+ */
+ protected void layout(Composite composite, boolean flushCache) {
+
+ if (flushCache || sizes == null) {
+ refreshSizes(composite.getChildren());
+ }
+
+ Rectangle r = composite.getClientArea();
+
+ int tg = 0;
+ int bg = 0;
+ int lg = 0;
+ int rg = 0;
+
+ if (controls[N] != null) {
+ int x = r.x;
+ int y = r.y;
+ int w = r.width;
+ int h = sizes[N].y;
+
+ controls[N].setBounds(x, y, w, h);
+
+ tg += hgap;
+ }
+
+ if (controls[S] != null) {
+ int x = r.x;
+ int y = r.y + r.height - sizes[S].y;
+ int w = r.width;
+ int h = sizes[S].y;
+
+ controls[S].setBounds(x, y, w, h);
+
+ bg += hgap;
+ }
+
+ if (controls[W] != null) {
+ int x = r.x;
+ int y = r.y + sizes[N].y + tg;
+ int w = sizes[W].x;
+ int h = r.height - sizes[N].y - sizes[S].y - (tg + bg);
+
+ controls[W].setBounds(x, y, w, h);
+
+ lg += vgap;
+ }
+
+ if (controls[E] != null) {
+ int x = r.x + r.width - sizes[E].x;
+ int y = r.y + sizes[N].y + tg;
+ int w = sizes[E].x;
+ int h = r.height - sizes[N].y - sizes[S].y - (tg + bg);
+
+ controls[E].setBounds(x, y, w, h);
+
+ rg += vgap;
+ }
+
+ if (controls[C] != null) {
+ int x = r.x + sizes[W].x + lg;
+ int y = r.y + sizes[N].y + tg;
+ int w = r.width - sizes[W].x - sizes[E].x - (lg + rg);
+ int h = r.height - sizes[N].y - sizes[S].y - (tg + bg);
+
+ controls[C].setBounds(x, y, w, h);
+ }
+
+ return;
+ }
+
+ private void refreshSizes(Control[] children) {
+ for (Control c : children) {
+ Object o = c.getLayoutData();
+
+ if (o instanceof BorderData) {
+ BorderData data = (BorderData) o;
+ if (data != BorderData.Ignore) {
+ controls[data.index] = c;
+ }
+ } else {
+ // Default
+ controls[C] = c;
+ }
+ }
+
+ width = height = 0;
+
+ if (sizes == null) {
+ sizes = new Point[5];
+ }
+
+ for (int i = 0; i < controls.length; i++) {
+ Control control = controls[i];
+
+ if (control == null) {
+ sizes[i] = new Point(0, 0);
+ } else {
+ sizes[i] = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+ }
+ }
+
+ width = Math.max(width, sizes[N].x);
+ width = Math.max(width, sizes[W].x + sizes[C].x + sizes[E].x);
+ width = Math.max(width, sizes[S].x);
+
+ height = Math.max(Math.max(sizes[W].y, sizes[E].y), sizes[C].y)
+ + sizes[N].y + sizes[S].y;
+
+ return;
+ }
+}
\ No newline at end of file
--- /dev/null
+package ie.dcu.swt.layout;
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.layout.*;
+
+/**
+ * Factory for layouts and layout data objects.
+ *
+ * @author Kevin McGuinness
+ */
+public class LayoutFactory {
+
+ public static GridLayout createGridLayout(
+ int margin, int spacing, int ncols, boolean equal
+ ) {
+ GridLayout layout = createGridLayout(margin, spacing);
+ layout.numColumns = ncols;
+ layout.makeColumnsEqualWidth = equal;
+ return layout;
+ }
+
+
+ public static GridLayout createGridLayout(int margin, int spacing) {
+ GridLayout layout = new GridLayout();
+ layout.marginWidth = margin;
+ layout.marginHeight = margin;
+ layout.horizontalSpacing = spacing;
+ layout.verticalSpacing = spacing;
+ return layout;
+ }
+
+
+ public static GridData createGridData() {
+ return new GridData(SWT.FILL, SWT.FILL, true, true);
+ }
+}
--- /dev/null
+package ie.dcu.util;
+
+import java.util.*;
+
+/**
+ * Utilities for arrays.
+ *
+ * @author Kevin McGuinness
+ */
+public class ArrayUtils {
+
+ /**
+ * Remove the given indices from the given collection.
+ *
+ * @param c
+ * The collection.
+ * @param indices
+ * A list of indices (will be sorted).
+ */
+ public static <T> void remove(Collection<T> c, int[] indices) {
+ Arrays.sort(indices);
+
+ Iterator<T> iterator = c.iterator();
+ for (int i = 0; iterator.hasNext(); i++) {
+ iterator.next();
+
+ if (Arrays.binarySearch(indices, i) >= 0) {
+ iterator.remove();
+ }
+ }
+
+ return;
+ }
+
+
+ public static void arrayCopy(Object src, int srcPos, Object dst,
+ int dstPos, int length)
+ {
+ if (dst.getClass().equals(src.getClass())) {
+
+ System.arraycopy(src, srcPos, dst, dstPos, length);
+
+ } else {
+
+ // Try for arrays of different primitive types
+
+ if (src instanceof byte[] && dst instanceof short[]) {
+
+ // Copy from an array of bytes to array of shorts
+ arrayCopy((byte[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof int[]) {
+
+ // Copy from an array of bytes to array of ints
+ arrayCopy((byte[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof long[]) {
+
+ // Copy from an array of bytes to array of longs
+ arrayCopy((byte[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof float[]) {
+
+ // Copy from an array of bytes to array of floats
+ arrayCopy((byte[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof byte[] && dst instanceof double[]) {
+
+ // Copy from an array of bytes to array of doubles
+ arrayCopy((byte[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof byte[]) {
+
+ // Copy from an array of shorts to array of bytes
+ arrayCopy((short[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof int[]) {
+
+ // Copy from an array of shorts to array of ints
+ arrayCopy((short[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof long[]) {
+
+ // Copy from an array of shorts to array of longs
+ arrayCopy((short[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof float[]) {
+
+ // Copy from an array of shorts to array of floats
+ arrayCopy((short[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof short[] && dst instanceof double[]) {
+
+ // Copy from an array of shorts to array of doubles
+ arrayCopy((short[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof byte[]) {
+
+ // Copy from an array of ints to array of bytes
+ arrayCopy((int[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof short[]) {
+
+ // Copy from an array of ints to array of shorts
+ arrayCopy((int[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof long[]) {
+
+ // Copy from an array of ints to array of longs
+ arrayCopy((int[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof float[]) {
+
+ // Copy from an array of ints to array of floats
+ arrayCopy((int[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof int[] && dst instanceof double[]) {
+
+ // Copy from an array of ints to array of doubles
+ arrayCopy((int[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof byte[]) {
+
+ // Copy from an array of longs to array of bytes
+ arrayCopy((long[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof short[]) {
+
+ // Copy from an array of longs to array of shorts
+ arrayCopy((long[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof int[]) {
+
+ // Copy from an array of longs to array of ints
+ arrayCopy((long[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof float[]) {
+
+ // Copy from an array of longs to array of floats
+ arrayCopy((long[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else if (src instanceof long[] && dst instanceof double[]) {
+
+ // Copy from an array of longs to array of doubles
+ arrayCopy((long[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof byte[]) {
+
+ // Copy from an array of floats to array of bytes
+ arrayCopy((float[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof short[]) {
+
+ // Copy from an array of floats to array of shorts
+ arrayCopy((float[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof int[]) {
+
+ // Copy from an array of floats to array of ints
+ arrayCopy((float[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof long[]) {
+
+ // Copy from an array of floats to array of longs
+ arrayCopy((float[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof float[] && dst instanceof double[]) {
+
+ // Copy from an array of floats to array of doubles
+ arrayCopy((float[]) src, srcPos, (double[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof byte[]) {
+
+ // Copy from an array of doubles to array of bytes
+ arrayCopy((double[]) src, srcPos, (byte[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof short[]) {
+
+ // Copy from an array of doubles to array of shorts
+ arrayCopy((double[]) src, srcPos, (short[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof int[]) {
+
+ // Copy from an array of doubles to array of ints
+ arrayCopy((double[]) src, srcPos, (int[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof long[]) {
+
+ // Copy from an array of doubles to array of longs
+ arrayCopy((double[]) src, srcPos, (long[]) dst, dstPos, length);
+
+ } else if (src instanceof double[] && dst instanceof float[]) {
+
+ // Copy from an array of doubles to array of floats
+ arrayCopy((double[]) src, srcPos, (float[]) dst, dstPos, length);
+
+ } else {
+
+ throw new ArrayStoreException();
+ }
+ }
+ }
+
+
+ public static void arrayCopy(byte[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(byte[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(byte[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(byte[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(byte[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(short[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(short[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(short[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(short[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(short[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(int[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(int[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(int[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(int[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(int[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(long[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(long[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(long[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(long[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(long[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(float[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(float[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(float[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(float[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(float[] src, int srcPos,
+ double[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (double) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(double[] src, int srcPos,
+ byte[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (byte) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(double[] src, int srcPos,
+ short[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (short) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(double[] src, int srcPos,
+ int[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (int) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(double[] src, int srcPos,
+ long[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (long) src[srcPos++];
+ }
+ }
+
+ public static void arrayCopy(double[] src, int srcPos,
+ float[] dst, int dstPos, int length)
+ {
+ checkCopy(src.length, srcPos, dst.length, dstPos, length);
+
+ for (int i = 0; i < length; i++) {
+ dst[dstPos++] = (float) src[srcPos++];
+ }
+ }
+
+ private static void checkCopy(int srcLen, int srcPos,
+ int dstLen, int dstPos, int length)
+ {
+ if (srcPos < 0) {
+ throw new IllegalArgumentException("srcPos < 0");
+ }
+
+ if (dstPos < 0) {
+ throw new IllegalArgumentException("dstPos < 0");
+ }
+
+ if (length < 0) {
+ throw new IllegalArgumentException("length < 0");
+ }
+
+ if (srcPos + length > srcLen) {
+ throw new IllegalArgumentException("srcPos + length > src.length");
+ }
+
+ if (dstPos + length > dstLen) {
+ throw new IllegalArgumentException("dstPos + length > dst.length");
+ }
+ }
+
+ public static void main(String[] args) {
+ int[] srcArray = {
+ 1, 2, 3, 4, 5
+ };
+
+ short[] dstArray = new short[srcArray.length];
+
+ arrayCopy(srcArray, 0, dstArray, 0, srcArray.length);
+
+ Object srcObj = srcArray;
+ Object dstObj = dstArray;
+
+ arrayCopy(srcObj, 0, dstObj, 0, srcArray.length);
+ }
+}
--- /dev/null
+package ie.dcu.util;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * Utility functions for file and filename manipulation.
+ *
+ * @author Kevin McGuinness
+ */
+public class FileUtils {
+
+ /**
+ * Size of the copy file buffer.
+ */
+ private static final int BUFFER_SIZE = 8092;
+
+
+ /**
+ * Returns the file extension of the file. This method returns {@code null} if
+ * the file has no extension. The method also returns {@code null}:
+ *
+ * <ul>
+ * <li>If the filename contains a period as it's last character</li>
+ * <li>If the filename contains a single period as its first character</li>
+ * </ul>
+ *
+ * The returned extension is always lowercase.
+ *
+ * @param file
+ * The file.
+ * @param includePeriod
+ * Set to {@code true} to include the period in the returned
+ * extension.
+ * @return The file extension, or {@code null}
+ */
+ public static final String getExtension(File file, boolean includePeriod) {
+ String filename = file.getName();
+ int idx = filename.lastIndexOf('.');
+
+ // If the period is not the first character or the last
+ if (idx > 0 && idx < filename.length() - 1) {
+
+ if (!includePeriod) {
+ idx++;
+ }
+
+ return filename.substring(idx).toLowerCase();
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Returns the file extension of the file. This method returns {@code null} if
+ * the file has no extension. The method also returns {@code null}:
+ *
+ * <ul>
+ * <li>If the filename contains a period as it's last character</li>
+ * <li>If the filename contains a single period as its first character</li>
+ * </ul>
+ *
+ * This method does not include the period in the file extension. The returned
+ * extension is always lower-case.
+ *
+ * @param file
+ * The file.
+ * @return The file extension, or {@code null}
+ */
+ public static final String getExtension(File file) {
+ return getExtension(file, false);
+ }
+
+
+ /**
+ * Remove the file extension from the filename, if it has one.
+ *
+ * @param filename
+ * The filename.
+ * @return The filename with the extension removed.
+ */
+ public static final String removeExtension(String filename) {
+ int idx = filename.lastIndexOf('.');
+ if (idx > 0 && idx < filename.length() - 1) {
+ return filename.substring(0, idx);
+ }
+ return filename;
+ }
+
+
+ /**
+ * Replace the file extension with another. If the file has no existing
+ * extension, the new extension will be appended. If the new extension is not
+ * prefixed with a period, one will be added.
+ *
+ * @param filename
+ * The filename (not a file path).
+ * @param extension
+ * The new extension.
+ * @return The filename with the extension replaced.
+ */
+ public static final String replaceExtension(String filename, String extension) {
+ boolean period = extension.startsWith(".");
+
+ int idx = filename.lastIndexOf('.');
+
+ if (idx > 0 && idx < filename.length() - 1) {
+ // Strip and append new extension
+ if (period) {
+ return filename.substring(0, idx) + extension;
+ } else {
+ return filename.substring(0, idx) + "." + extension;
+ }
+ }
+
+ if (period) {
+ return filename + extension;
+ }
+
+ return filename + "." + extension;
+ }
+
+
+ /**
+ * Returns true if the filename has an extension.
+ *
+ * @param filename
+ * The file name (not a file path).
+ * @return {@code true} if it has an extension.
+ */
+ public static boolean hasExtension(String filename) {
+ int idx = filename.lastIndexOf('.');
+ return (idx > 0 && idx < filename.length() - 1);
+ }
+
+
+ /**
+ * Returns true if the filename has an extension.
+ *
+ * @param file
+ * The file.
+ * @return {@code true} if it has an extension.
+ */
+ public static boolean hasExtension(File file) {
+ return hasExtension(file.getName());
+ }
+
+
+ /**
+ * Close the stream. Does nothing if it is already closed and ignores any I/O
+ * exception that is thrown.
+ *
+ * @param c
+ * The stream, or {@code null}
+ */
+ public static void close(Closeable c) {
+ if (c == null) {
+ return;
+ }
+
+ try {
+ c.close();
+ } catch (IOException e) {
+ // Intentionally empty
+ }
+ }
+
+
+ /**
+ * Copy a source file to a destination. If the destination file exists it is
+ * overwritten. If the destination file is a directory, the file is copied to
+ * that directory.
+ *
+ * @param src
+ * The source file.
+ * @param dst
+ * The target file.
+ * @throws IOException
+ * If there is a problem copying the file.
+ */
+ public static void copy(File src, File dst) throws IOException {
+
+ // If directory, copy to file of the same name in that directory
+ if (dst.isDirectory()) {
+ dst = new File(dst, src.getName());
+ }
+
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ in = new FileInputStream(src);
+ out = new FileOutputStream(dst);
+
+ // Copy streams
+ copy(in, out);
+
+ } finally {
+ // Close streams
+
+ if (in != null) {
+ in.close();
+ }
+
+ if (out != null) {
+ out.close();
+ }
+ }
+
+ return;
+ }
+
+
+ /**
+ * Copy the data from the input stream to the output stream. The streams are
+ * not closed after the copy.
+ *
+ * @param in
+ * The input stream.
+ * @param out
+ * The output stream.
+ * @throws IOException
+ * If there is a problem performing the copy.
+ */
+ public static void copy(InputStream in, OutputStream out) throws IOException {
+ byte[] buff = new byte[BUFFER_SIZE];
+
+ int len;
+ while ((len = in.read(buff)) > 0) {
+ out.write(buff, 0, len);
+ }
+ }
+
+
+ /**
+ * Move the src file to dst. Semantics are the same as copy except that
+ * the source file is deleted after the operation.
+ *
+ * @param src
+ * The source file.
+ * @param dst
+ * The destination.
+ * @return {@code true} if the move is sucessful, {@code false} if the source
+ * file cannot be removed.
+ * @throws IOException
+ */
+ public static boolean move(File src, File dst) throws IOException {
+ copy(src, dst);
+ return src.delete();
+ }
+
+
+ /**
+ * Throws an {@link FileNotFoundException} if the file does not exist.
+ *
+ * @param file
+ * The file to check.
+ * @throws FileNotFoundException
+ * The exception thrown if the file doesn't exist.
+ */
+ public static void checkExists(File file) throws FileNotFoundException {
+ if (!file.exists()) {
+ throw new FileNotFoundException("File: " +
+ file.getAbsolutePath() + " does not exist");
+ }
+ }
+
+
+ /**
+ * Throws an {@link FileNotFoundException} if any of the files do not exist.
+ *
+ * @param files
+ * The files to check.
+ * @throws FileNotFoundException
+ * The exception thrown if any file doesn't exist.
+ */
+ public static void checkExists(File ... files) throws FileNotFoundException {
+ for (File f : files) {
+ checkExists(f);
+ }
+ }
+
+
+ /**
+ * Makes the directory if it doesn't already exist. Throws an exception if
+ * there is an error creating the directory.
+ *
+ * @param dir
+ * The directory to create.
+ * @throws IOException
+ * If there is an error making the directory.
+ */
+ public static void mkdir(File dir) throws IOException {
+ if (dir.isDirectory()) {
+ return;
+ }
+
+ if (!dir.mkdir()) {
+ throw new IOException("Unable to create directory");
+ }
+ }
+
+ /**
+ * Makes the directory if it doesn't already exist. Throws an exception if
+ * there is an error creating the directory. All intermediate subdirectories
+ * are also created.
+ *
+ * @param dir
+ * The directory to create.
+ * @throws IOException
+ * If there is an error making the directory.
+ */
+ public static void mkdirs(File dir) throws IOException {
+ if (dir.isDirectory()) {
+ return;
+ }
+
+ if (!dir.mkdirs()) {
+ throw new IOException("Unable to create directory");
+ }
+ }
+
+
+ /**
+ * Create an absolute url for a file.
+ *
+ * @param file
+ * The file.
+ * @return The url
+ */
+ public static URL createAbsoluteURL(File file) {
+ File absolute = file.getAbsoluteFile();
+ try {
+ return absolute.toURI().toURL();
+ } catch (MalformedURLException e) {
+ // Shouldn't happen (url cant be malformed, can it?)
+ return null;
+ }
+ }
+
+ /**
+ * Join a set of path components using the path separator
+ *
+ * @param parts
+ * The path components
+ * @return The joined path
+ */
+ public static String pathJoin(String ... parts) {
+ StringBuilder sb = new StringBuilder();
+ if (parts.length > 0 && parts[0].startsWith(File.separator)) {
+ // Assume an absolute path
+ sb.append(File.separator);
+ }
+
+ for (String part : parts) {
+ sb.append(normalizePathComponent(part));
+ if (part != parts[parts.length-1]) {
+ sb.append(File.separatorChar);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Removes leading and trailing path separators from the path component
+ *
+ * @param component
+ * A path component
+ * @return A stripped string
+ */
+ private static String normalizePathComponent(String component) {
+ if (component.endsWith(File.separator)) {
+ component = component.substring(0, File.separator.length());
+ } else if (component.startsWith(File.separator)) {
+ component = component.substring(File.separator.length());
+ } else {
+ return component;
+ }
+ return normalizePathComponent(component);
+ }
+}
--- /dev/null
+package ie.dcu.util;
+
+/**
+ * Utilities for identifying the operating system.
+ *
+ * @author Kevin McGuinness
+ */
+public class OsUtils {
+ private static final String OS_NAME = "os.name";
+
+ /**
+ * Returns <code>true</code> if the is Mac OS or Mac OS X.
+ */
+ public static boolean isMacOS() {
+ String os = System.getProperty("os.name");
+ os = os.toLowerCase();
+ return os.startsWith("mac os");
+ }
+
+ /**
+ * Returns <code>true</code> if the OS is Mac OS X.
+ */
+ public static boolean isMacOSX() {
+ String os = System.getProperty(OS_NAME);
+ os = os.toLowerCase();
+ return os.startsWith("mac os x");
+ }
+
+ /**
+ * Returns <code>true</code> if the OS is an MS Windows variant.
+ */
+ public static boolean isWindows() {
+ String os = System.getProperty(OS_NAME);
+ return os.startsWith("Windows");
+ }
+
+ /**
+ * Returns <code>true</code> if the OS is a Linux variant.
+ */
+ public static boolean isLinux() {
+ String os = System.getProperty(OS_NAME);
+ return os.startsWith("Linux");
+ }
+
+ /**
+ * Returns the users home folder.
+ */
+ public static String userHome() {
+ return System.getProperty("user.home");
+ }
+
+ /**
+ * Returns true if we are on Linux and on a 64 bit Java VM.
+ *
+ * Note: this is basically guess-work and depends on the VM vendor.
+ * Should work for Sun Java though.
+ */
+ public static boolean isLinux64() {
+ return isLinux() && System.getProperty("os.arch").endsWith("64");
+ }
+}
--- /dev/null
+package ie.dcu.util;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Utility functions for <code>.properties</code> files.
+ *
+ * @author Kevin McGuinness
+ */
+public class PropsUtils {
+
+ /**
+ * Save the properties object to a file.
+ *
+ * @param props
+ * The properties.
+ * @param file
+ * The file to save it to.
+ * @throws IOException
+ * If there is an error saving.
+ */
+ public static void save(Properties props, File file) throws IOException {
+ OutputStream out = null;
+
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(file));
+ props.store(out, "Automatically generated properties file");
+
+ } finally {
+
+ if (out != null) out.close();
+ }
+ }
+
+
+ /**
+ * Load the properties object from a file.
+ *
+ * @param file
+ * The file.
+ * @return A new properties object.
+ * @throws IOException
+ * If an error occurs loading the properties.
+ */
+ public static Properties load(File file) throws IOException {
+ InputStream in = null;
+ Properties props = null;
+
+ try {
+ in = new BufferedInputStream(new FileInputStream(file));
+ props = new Properties();
+ props.load(in);
+
+ } finally {
+
+ if (in != null) in.close();
+ }
+
+ return props;
+ }
+
+
+ public static File getFile(Properties props, String key) {
+ String val = props.getProperty(key);
+ if (val != null) {
+ return new File(val);
+ }
+ return null;
+ }
+}
--- /dev/null
+version 3.550
\ No newline at end of file