diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/LICENSE b/LICENSE index e06d208..6108327 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,15 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - +Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory All Rights Reserved. +Contact the JHU/APL Office of Technology Transfer for any additional rights. +www.jhuapl.edu/ott + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 0c76c6d..c35294b 100644 --- a/README.md +++ b/README.md @@ -65,22 +65,22 @@ Typically you can place the API in a shared directory, so all compute nodes have - Open MATLAB, create a SemaphoreTool object, and execute the "configure" method: ```matlab - s = SemaphoreTool; - s.configure; + s = SemaphoreTool; + s.configure; ``` - Set the desired values (50 read, 50 write is a decent place to start depending on your code) - This will then configure the server so any API client that connects will get its semaphore configured from the redis server. To use the semaphore, create an OCP object like this: - ```matlab - oo = OCP('semaphore'); - ``` + ``` + oo = OCP('semaphore'); + ``` The distributed semaphore will be ignored if you do omit the 'semaphore' tag: - ```matlab - oo = OCP() - ``` + ``` + oo = OCP() + ``` - Configuring LONI Pipeline (loni pipeline url: - If you are using LONI Pipeline, you need to add CAJAL3D as a package diff --git a/Release Notes.txt b/Release Notes.txt new file mode 100644 index 0000000..1928a65 --- /dev/null +++ b/Release Notes.txt @@ -0,0 +1,520 @@ +########################################################################### +######### CAJAL3D API Release Notes ######### +########################################################################### + +### 2014-02-6 ### v1.7 + +Release Notes: + - Added propagation service support + - Added image data upload support + +Updates: + +Bug Fixes: + +Known Issues: + - channel names with hyphens break hdf5 parsing and are temporarily patched. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2014-12-20 ### v1.6 + +Release Notes: + - Updated OCP.m and OCPHdf.m to meet new OCP service interfaces + +Updates: + +Bug Fixes: + +Known Issues: + - channel names with hyphens break hdf5 parsing and are temporarily patched. + - block ID reservation service still drops 1 ID on the floor after each call. + This causes the unit test to currently fail, but is still usable. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2014-12-06 ### v1.5 + +Release Notes: + +Updates: + - Main updates to I2G software (to be migrated to separate repo) + +Bug Fixes: + - Minor bug fixes + +Known Issues: + - channel names with hyphens break hdf5 parsing and are temporarily patched. + - block ID reservation service still drops 1 ID on the floor after each call. + This causes the unit test to currently fail, but is still usable. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2014-10-13 ### v1.4 + +Release Notes: + +Updates: + - Added MatlabMonitor class. This is used to monitor status from inside + applications running in LONI Pipeline + - Added OCP.getPublicTokens(). Returns cell array of public tokens for the + current server. + - Added multichannel data support + - OCP.getChannelList returns cell array of channels in the current image + database, based on the image token + - OCPQuery.setChannels lets you set the channels to query. Use + OCPQueryType.imageDense and set channels as a cell array + - OCP.query will return a cell array of RAMONVolume objects. The name + field will be set to the channel name, and be in the same order as + the channels listed in the query. + - The viewer will support displaying a single 16-bit channel as an + individual grayscale image by calling image() on the RAMONVolume + - NOTE: currently there is a temporary patch due to the names of certain + channels. If the channel name includes a hyphen, when the API queries + the db for channel names it will replace hyphens with two underscores. + You will notice this in getPublicTokens. If the API detects a channel + name with two underscores in it, it will automatically replace with a + hyphen when quering for data. This is a temporary patch due to a + MATLAB issue that will be fixed in the future. + +Bug Fixes: + - Added temporary fix to require a default resolution when merging annotations + - Fixed bug in cubeCutout* that caused 'remainder' blocks to skip 1 pixel + - Fixed x/y bounds flip in block upload url. Only effected edges of datasets + +Known Issues: + - channel names with hyphens break hdf5 parsing and are temporarily patched. + - block ID reservation service still drops 1 ID on the floor after each call. + This causes the unit test to currently fail, but is still usable. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + +### 2014-08-25 ### v1.3 + +Release Notes: + - The enumeration eRAMONUploadDataType has been depricated and should no longer + be used. It has been replaced by eRAMONDataType + - When downloading data, the dataType property of the RAMON object will indicate + the type of data stored. This is always based off the token used to download + the data. When uploading, if the datatype is not specified, the project info + based on the appropriate annotation/image token will be used. + +Updates: + - Added block ID reservation service. Useful for writing data in chunks and + pre-allocating IDs. + - Added RGBA32 data format support + - Updated built-in viewer to support RGBA32 data + - Changed eRAMONUploadDataType to eRAMONDataType to support new and future + features + + +Bug Fixes: + - Fixed backwards compatibility issue with "frameworkVersion" in cajal3d.m + - Fixed bug in bugReport method + - Changed URL checking to use www.google.com and fixed associated bug in + cajal3d.jar + - Fixed bug in HDF5 implementation to properly chunk and compress voxel data + - Adjusted "maximum annotation size", which is used to control auto annotation + chunking and the "large upload" warning to match server performance. Set to + roughly 60MB uncompressed. + + + +Known Issues: + - block ID reservation service still drops 1 ID on the floor after each call. + This causes the unit test to currently fail, but is still usable. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2014-05-29 ### v1.2 + +Release Notes: + +Updates: + - Added RAMON merge interface + - Updated overlay service to support alpha level control + - Added performance enhancements and new features to the basic data viewer + including the ability to export animations + - Added probability map uploading + + +Bug Fixes: + - Fixed bug in SemaphoreTool where flushAll cleared all databases instead + of just the current database + - Fixed bug that resulted in invalid URL generation when trying to cut out + probability maps as input data + - Added autosuppression of MATLAB:Java:DuplicateClass warning during loading + of the API. + - Fixed bug that made the OCP service URL sensitive to trailing slashes + - Fixed bug that was causing java heap overflows on large uploads again + + + +Known Issues: + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2014-03-11 ### v1.1 + +Release Notes: + +Updates: + - Changed URLs to satisfy new interface based on /ocp/ca/ root service. + - Removed requirement to edit static class path for jar file includes. + Should work with most versions of MATLAB but has only been tested on R2012a+ + - Updated setupEnvironment tool to only edit the startup.m file + - Updated SemaphoreTool to use server-side configuration, cleaned up resets, + and added some new helper methods + - Updated OCP and OCPNetwork to optionally use distributed semaphore + configuration from the redis server. All that is required is the server + hostname and port be saved in /api/matlab/ocp/semaphore_settings.mat. + /api/matlab/ocp/semaphore_settings_example.mat is provided as a starting + point for new installs. OCP('semaphore') will trigger config and enable + the distributed semaphore. + - Added Cube Cutout Preprocess Cuboid Aligned which computes subcubes that + respect database cuboid boundaries for efficiency. Also adds ability to + compute "merge" region blocks which are 2 voxel blocks around each + interface plane + + +Bug Fixes: + - Fixed bug caused by class type checking in OCPQuery + - Fixed bug in java network library that resulted in crushing the heap on + big uploads. + + +Known Issues: + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + +### 2013-11-13 ### v1.0.1 + +Release Notes: + +Updates: + - Increased OcpUrl library read timeout to 5 minutes + - Added retry on network connectivity check when creating a OCPNetwork object + - Added increasing delay on write retries + +Bug Fixes: + - Fixed error in OCP class unit test that gave false failures for slice cutouts + when multiple users run the tests at the same time + - Fixed bug in presever write option test that gave false errors if run + multiple times + - Added check to add 'http://' if url string has it missing when setting server + location. + +Known Issues: + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2013-08-20 ### v1.0.0 + +Release Notes: First Public Release + +Updates: + +Bug Fixes: + +Known Issues: + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + +### 2013-07-16 ### v0.9.4 + +Release Notes: + +Updates: + - Replaced Hazelcast library with a custom distributed semaphore service + based around a centralized Redis database. This should increase the + scalability and stability of cluster usage. + - Added /tools/distributed_semaphore/SemaphoreTool to configure, monitor, + and reset the distributed semaphore + - Updated Transverse Synapse Detector (essentially a simple PSD detector) + to increase detection rate with some increase in false positive errors + - Added package to import and refine an externally generated soma mask + - Added modules to transverse synapse detector package to use the soma mask + and generate engineering data products + - Added toImageDenseQuery and toAnnoDenseQuery to RAMONVolume object to get + an OCPQuery object that will download a matching image or annotation volume + respectively + - Increased java interface library read timeout to 10min to support very + large writes at load + - Added ability to save RAMONVolume objects as HDF5 files to OCPHdf + - Changed default OCP server location to braingraphdev1.cs.jhu.edu for + APL integration purposes(should be openconnecto.me in released software) + - Updated CubeCutout module to support hdf5 output + - Added .voxelCount method for RAMONVolume to return number of voxels + - Added the ability to convert RAMONVolume or subclass voxel representations + between cutout and voxel list + - Major update to OCP class to support more advanced uploading + - Batch interface supported on create and update + - Very large annotations are now chunked into multiple uploads + - Updated RAMON object .clone method to support a "voxeless" deep copy by + providing the 'novoxels' option + - Updated API to handle removal of the default resolution as part of the + service. Concept still maintained internally to the OCP class to aid + developers + - Added the skeleton for a basic, generic random forest classifier package + - Updated API to handle new id query interface. Query ids by status, type, + and confidence. + - Added 'reduce' overwrite option - use this to delete or shave voxels from + existing annotations + - Added get/set field interface to existing annotations. You can now get + most fields and set single value field without downloading the annotation + or posting a new annotation. + - Added idListLimit property to OCPQuery. This can be used in conjunction + with RAMON Id List Queries to limit the number of objects returned. This + is useful in large databases when you only want a few things back. + - Added support for upload/download and get/set of RAMONOrganelle objects + - Added support for filtering annotation cutouts and slices by id + +Bug Fixes: + - Fixed bug in Cube cutout unit test. + - Fixed bug in testOCPQuery caused by previous bug fix + - Updated framework to be able to be run from a different working directory + than the cajal 3d framwork path, as long as the framework is on the matlab + search path (run cajal3d.setSearchPath) + - Fixed bug in RAMONVolumeObject.local2Global/global2Local to properly support an array + of points to convert + - Fixed bug in transverseSynapseDetector when no synapses are detected + - Fixed bug in RAMON object .clone method that can sometimes result in voxel + data getting incorrectly duplicated + - Fixed bug in HDF5 group indexing order that resulted in the returned + database ids not matching up to the correct annotation + - Fixed bug in VolumeImageBox that prevented RAMONVolume object based annotations + from being displayed properly in overlay mode + - Fixed bug in OCPHdf cause by hdf5 library changes in MATLAB R2013 + - Broke out distributed semaphore tests if installing software on a computer that + doesn't use the distributed semaphore + - Added missing centroid field in RAMONOrganelle + - Fixed bug that caused objects to be colored improperly using the data viewer + - Fixed bug in cajal3d.jar that prevented API from working with main OCP domain + +Known Issues: + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data to a bounding box + is not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS/RHEL + + + +### 2012-12-21 ### v0.9.3 + +Release Notes: + +Updates: + - Created CubeCutoutPreprocess2 that dices a volume into blocks based on a + specified size and then computes the remainder sized blocks. It is fully + compatible witht he CubeCutout Module for actually downloading data + - Added ability to specify the temp directory to be used by setting the + environment variable "PIPELINE_TEMP_DIR". If the variable does not exist + the default temp location will be used + - Implemented a distributed semaphore to manage OCP server resources using + the Hazelcast library. Set the env var "HAZELCAST_XML" to the location of + the hazelcast.xml config file or default will be used. The number of permits + available to read/write are set in the hazelcast.xml file.g + - Added an absolute approximate centriod to RAMONSynapse metadata in + transverseSynDetect + - Added a python wrapper to group cutout and transverse syn detect into + a single module for distributed processing + +Bug Fixes: + - Fixed CLI bug in throttler and server daemon that resulted in validation + sometimes failing server side. + - Increased cajal3d networking library to have 180 sec read timeout due to + server latency when lots of workers are requesting data. + - Fixed bug that caused some metadata fields to get written improperly, + corrupting the data without an exception + - Implemented some temporary workarounds to help with OCP server loading + - Added .clone method to all RAMON objects. This performs a deep copy on + the object giving you a new handle to a new object. + - Fixed bug in RAMONSynapse.setSegments + - Fixed bug in RAMONVolume.setRAMONVolumeProperties + - Fixed bug in OCPQuery that prevented you from cutting out data at the dataset + boundary + - Fixed bug in cubeCutoutPreProcess2 that resulted in incorrect cube coordinates + for types II-V + - Fixed OCPHdf to throw warning when voxel data is not return when expected + for all types of RAMON queries + - Fixed bugs in hazelcast implementation that resulted in an increasing number + of permits to be generated + +Known Issues: + - Some parts of the API still require you to have the MATLAB current folder + set to the framework root directory. This will be fixed in future releases. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data between cutout + format and voxel list format not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS + + +### 2012-11-13 ### Beta Release 3 ### v0.9.2 + +Release Notes: + - Released to collaborators + +Updates: + - Added RAMONOrganelle object + - Added scripts for developer team to help build and maintain releases + - Added error messages for Windows users trying to use unsupported local database + interface and matlab wrapper m files. + - Updated cajal3d class to set the dynamic java class path on Windows systems + - Added capability to set write conflict options on creation of an annotation + - Created 'throttler' module to temporarily work around server load issues + +Bug Fixes: + - Adjusted cajal3d networking library to have 60 sec read timeout + - Fixed bug due to windows file seperators being interpreted as bad escape characters + in OCPNetwork and cubeCutoutPreprocess + - Updated RUN_TESTS to filter out unsupported tests on Windows + - Updated setupEnvironment to work with Windows and create work around script if + user has older version of MATLAB that has unreliable dynamic java class path + performance + - Fixed issue with data not getting properly inserted into the hdf5 format + which resulted in rotated annotations in the database. + +Known Issues: + - Some parts of the API still require you to have the MATLAB current folder + set to the framework root directory. This will be fixed in future releases. + - RAMONNonNeuron and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data between cutout + format and voxel list format not yet supported. + - Local database resource only available to Linux and OSX users currently + - Matlab wrapper only supported by Linux/OSX. This is OK for now since it is + intended for use with the LONI Pipeline server which should be running on CentOS + + +### 2012-10-22 ### Beta Release 2 ### v0.9.1 + +Release Notes: + - + +Updates: + - Added ability to query for a list of all ids (no predicates) + - Added bounding box query + - Added Cube Cutout package for batch sub-cube download from a larger volume + - Added voxel ID based on XYZ coordinate query + - Added ability to load token files from a file + - Changed isCutout property to dataFormat in RAMONVolume. This enables + proper support of bounding box queries and can potentially effect existing + software that used the old convention. + - Changed startCAJAL3D script to more useful cajal3d utility class + - Updated reference pipeline stubs and server daemon modules to latest + framework version. + - Refactored some classes to enhance usability + - eRAMONAnnotationType -> eRAMONAnnoType + - eRAMONAnnotationStatus -> eRAMONAnnoStatus + - eRAMONAnnoType.RAMON* -> eRAMONAnnoType.* + - eConflictOption -> eOCPConflictOption + - ePredicates -> eOCPPredicate + - eQueryType -> eOCPQueryType + - eSlicePlane -> eOCPSlicePlane + - Added scripts for developer team to help build and maintain releases + +Bug Fixes: + - Fixed error when doing a tight cutout of an object that does not contain voxel + data. + - Added getters for tokens in OCP class so tokens are not automatically + printed to the command window. + - Fixed multiple issues with install script 'setupEnvironment' + - Fixed query validation to issue warning when cutout args are populated in a + voxel list query. + - Fixed problem with restricting RAMON dense queries to a cutout + +Known Issues: + - Possible issue with annotations getting rotated somewhere between upload/download + - RAMONOrganelle and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data between cutout + format and voxel list format not yet supported. + - Database write exceptions not supported yet. All writes default to overwrite + mode. + + + + + +### 2012-09-25 ### Beta Release 1 ### v0.9.0 + +Release Notes: + - This is the first Beta Release of the CAJAL3D API. Only MATLAB API and + associated resources are included. No Python API, packages, or library + items are included. + +Updates: + - None: Initial release + +Bug Fixes: + - None: Initial release + +Known Issues: + - Querying a cutout or voxel list on an annotation that does not have voxel + data throws a server 500 error. This will be fixed and a warning will be + issued in next release. + - Query to get voxel ID based on X,Y, Z location not implemented in the + API (still available manually server side). + - Ability to load tokens from file not yet supported (coming soon with + online db management) + - RAMONOrganelle and RAMONAttributedRegion not yet supported in API or + server side. + - Converting a RAMONVolume (and subclasses) voxel data between cutout + format and voxel list format not yet supported. \ No newline at end of file diff --git a/api/matlab/ocp/OCP.m b/api/matlab/ocp/OCP.m new file mode 100644 index 0000000..74defe1 --- /dev/null +++ b/api/matlab/ocp/OCP.m @@ -0,0 +1,2315 @@ +classdef OCP < handle + %OCP ************************************************ + % Provides easy access to the Open Connectome Project database services + % + % Usage: + % + % oo = OCP(); Creates object without distributed semaphore (typical) + % + % Distributed Semaphore enabled (see OCPNetwork for details) with config + % loaded from redis server. See README for setup details. + % You only need this when running on a large cluster. + % + % oo = OCP('semaphore'); + % + % Distributed Semaphore enabled with explicit config (see OCPNetwork for details) + % You only need this when running on a large cluster. + % + % oo = OCP("myserver.mysite.edu",3679,"readQ",10,0,"writeQ",20,100); + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + %% Properties - Server Info + %Url to server root + serverLocation = 'http://www.openconnecto.me' + + batchSize = 50; % Number of annotations to group in a batch upload + maxAnnoSize = 16777216; % Maximum size of annotation in voxels to be uploaded in a single RESTful query + % This is 2048x2048x4 which equates to + % about 60MB uncompressed. This is a + % reliable size to use across varying + % internet speeds + + %% Properties - Common Public + defaultResolution = [] % Default resolution of the annotation DB + + %% Properties - DB Info + imageInfo = [] + annoInfo = [] + + end + + properties(SetAccess = 'private', GetAccess = 'private') + %% Properties - Private + lastQuery = []; + lastUrl = [] + lastHdfFile = []; + + %Token for Image Database + imageToken = [] + %Token for Annotation Database + annoToken = [] + + %% Properties - Utility Classes + net = []; + end + + methods( Access = public ) + + %% Methods - General - Constructor + function this = OCP(varargin) + % No Semaphore to control OCP server resource access (default case) + % this = OCP() + % + % Distributed Semaphore (see OCPNetwork for details). Config + % Loaded from redis server. See README for setup details. + % You only need this when running on a large cluster. + % this = OCP('semaphore'); + % + % Distributed Semaphore Explicit Config(see OCPNetwork for details) + % You only need this when running on a large cluster. + % this = OCP("darkhelmet.jhuapl.edu",3679,"readQ",10,0,"writeQ",20,100); + + + % Set up network interface class + switch nargin + case 0 + % No semaphore + this.net = OCPNetwork(); + + case 1 + % Use network semaphore config + if strcmpi(varargin{1},'semaphore') + this.net = OCPNetwork(varargin{1}); + else + error('OCP:UnsupportedOption','Invalid constructor flag'); + end + + case 8 + % Distributed semaphore + this.net = OCPNetwork(varargin{1},varargin{2},... + varargin{3},varargin{4},varargin{5},varargin{6},... + varargin{7},varargin{8}); + otherwise + ex = MException('OCP:InvalidConstructor','Invalid params to the constructor.'); + throw(ex); + end + + % Verify server url is valid by setting to self + this.setServerLocation(this.serverLocation); + end + + %% Methods - Semaphore + function num = numReadPermits(this) + num = this.net.numReadPermits(); + end + function num = numWritePermits(this) + num = this.net.numWritePermits(); + end + + % Select non-default database index if desired. This lets you work + % with different semaphores, allowing unit testing/development to + % occur without conflicting with actual processing. + % Run this AFTER creating object but BEFORE a reset or lock. + % Note: Uses OCP class uses redis DB 0 by default. Main distributed + % semaphore is stored in db 0. + function selectDatabaseIndex(this,index) + this.net.selectDatabaseIndex(index) + end + + %% Methods - General - Setters/Getters + function channel_cell_array = getChannelList(this) + % If the image token is a multichannel database then return + % a cell array listing the available channels + if (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels16) || ... + (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels8) + channel_cell_array = fieldnames(this.imageInfo.CHANNELS); + else + % not multichannel + channel_cell_array = []; + end + end + + function tokens = getPublicTokens(this) + url = sprintf('%socp/ca/public_tokens/',... + this.serverLocation); + this.lastUrl = url; + response = this.net.read(url); + % parse + inds = strfind(response,'"'); + cnt = 1; + for ii = 1:2:length(inds) + tokens{cnt} = response(inds(ii)+1:inds(ii+1)-1); %#ok + cnt = cnt + 1; + end + end + + function this = setErrorPageLocation(this,location) + % This method sets the path to save server errors pages to + this.net.setErrorPageLocation(location); + end + function location = getErrorPageLocation(this) + % This method gets the path to save server errors pages to + location = this.net.errorPageLocation; + end + + function this = setDefaultResolution(this,val) + % This method sets the default resolution to use if a + % resolution is not specified. If it is empty and no + % resolution is provided, an exception will occur + validateattributes(val,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + this.defaultResolution = val; + end + + function this = setBatchSize(this,size) + % This method sets the batch size. This is how many + % annotations will be grouped into a single batch when a cell + % array of annotations is provided + validateattributes(size,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + this.batchSize = size; + end + + function this = setMaxAnnoSize(this,val) + % This method sets the maximum size an annotation can be until + % auto-chunking will occur + validateattributes(val,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + this.maxAnnoSize = val; + end + + function this = setServerLocation(this,val) + % Verify you can resolve server. + try + %Make sure http:// is on the server url + ind = strfind(val, 'http://'); + if isempty(ind) + val = sprintf('http://%s',val); + end + %Make sure url ends with / + if strcmpi(val(end),'/') == 0 + val = sprintf('%s/',val); + end + + this.net.testUrl(val); + %urlread(val); + catch err2 + + if strcmpi(err2.identifier,'MATLAB:Java:GenericException') == 1 + rethrow(err2); + end + + ex = MException('OCP:ServerConnFail','Failed to resolve server. Check server url'); + throw(ex); + end + + this.serverLocation = val; + end + + function this = setImageToken(this,token) + % This method sets the image database token to be used + + % Get DB Info + this.imageInfo = this.queryDBInfo(token); + + this.imageToken = token; + + end + function token = getImageToken(this) + % This method gets the image database token to be used + + token = this.imageToken; + end + + function this = setAnnoToken(this,token) + % This method sets the annotation database token to be used + % Get DB Info + this.annoInfo = this.queryDBInfo(token); + + % Verify it is a writable DB + if this.annoInfo.PROJECT.READONLY == 1 + warning('OCP:ReadOnlyAnno','The current Annotation DB Token is for a READ ONLY database.'); + end + + this.annoToken = token; + end + function token = getAnnoToken(this) + % This method gets the anno database token to be used + + token = this.annoToken; + end + + function this = setImageTokenFile(this,file) + % This method loads a token file and sets the image token + + if ~exist('file','var') + [filename, pathname, ~] = uigetfile( ... + { '*.token','Token (*.token)'}, ... + 'Pick a Token File', ... + 'MultiSelect', 'off'); + + if isequal(filename,0) + warning('OCP:FileSelectionCancel','No file was selected. Token not opened.'); + return; + end + + file = fullfile(pathname,filename); + end + + % Read file + fid = fopen(file,'r'); + tline = fgetl(fid); + fclose(fid); + + % Set Token + this.setImageToken(tline); + end + + function this = setAnnoTokenFile(this,file) + % This method loads a token file and sets the anno token + + if ~exist('file','var') + [filename, pathname, ~] = uigetfile( ... + { '*.token','Token (*.token)'}, ... + 'Pick a Token File', ... + 'MultiSelect', 'off'); + + if isequal(filename,0) + warning('OCP:FileSelectionCancel','No file was selected. Token not opened.'); + return; + end + + file = fullfile(pathname,filename); + end + + % Read file + fid = fopen(file,'r'); + tline = fgetl(fid); + fclose(fid); + + % Set Token + this.setAnnoToken(tline); + end + + function url = getLastUrl(this) + % Method to retreive the last URL that was used. Useful for + % developers. + url = this.lastUrl; + end + + function file = getLastHDF5(this) + % Method to retreive the last HDF5 file that was used + file = this.lastHdfFile; + end + + %% Methods - Query + function response = query(this, qObj) + % This method builds a query and executes it based on the + % supplied queryObj + + if nargin ~= 2 + ex = MException('OCP:ArgError','Incorrect Number of Arguments'); + throw(ex); + end + + + % Based on Query Type get what yo need! + switch qObj.type + case eOCPQueryType.imageDense + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.imageToken) + ex = MException('OCP:MissingImageToken',... + 'You must specify the image database to read from by setting the "imageToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.imageInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + + % Build URL + url = this.buildCutoutUrl(qObj); + this.lastUrl = url; + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.readCached(url),qObj); + this.lastHdfFile = hdfFile.filename; + % Convert to RAMONVolume + response = hdfFile.toRAMONVolume(); + % Set dataType + if isa(response, 'cell') + % multichannel cutout + for kk = 1:length(response) + response{kk}.setDataType(this.imageInfo.PROJECT.TYPE); + end + else + % normal + response.setDataType(this.imageInfo.PROJECT.TYPE); + end + + case eOCPQueryType.imageSlice + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.imageToken) + ex = MException('OCP:MissingImageToken',... + 'You must specify the image database to read from by setting the "imageToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.imageInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildSliceUrl(qObj); + % Query DB and get png File back + response = this.net.queryImage(url); + + + case {eOCPQueryType.annoDense, eOCPQueryType.probDense} + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set see if image token is + % there + if isempty(this.annoToken) + if ~isempty(this.imageToken) + % Warn and use the image token for the anno + % token + warning('OCP:MissingAnnoToken','The Annotation Token has not been set. Using Image Token as Annotation Token.'); + this.setAnnoToken(this.getImageToken()); + + else + % There isn't a token. + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property.'); + throw(ex); + end + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildCutoutUrl(qObj); + this.lastUrl = url; + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url),qObj); + this.lastHdfFile = hdfFile.filename; + % Convert to RAMONVolume + response = hdfFile.toRAMONVolume(); + % Set dataType + response.setDataType(this.annoInfo.PROJECT.TYPE); + + + case eOCPQueryType.annoSlice + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildSliceUrl(qObj); + this.lastUrl = url; + % Query DB and get png File back + response = this.net.queryImage(url); + + case eOCPQueryType.overlaySlice + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildOverlayUrl(qObj); + this.lastUrl = url; + % Query DB and get png File back + response = this.net.queryImage(url); + + case {eOCPQueryType.RAMONDense,eOCPQueryType.RAMONVoxelList} + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildRAMONUrl(qObj); + this.lastUrl = url; + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url)); + this.lastHdfFile = hdfFile.filename; + % Convert to RAMON Object based on what comes back + response = hdfFile.toRAMONObject(qObj); + % Set dataType (support batch interface) + if iscell(response) == 1 + for ii = 1:length(response) + response{ii}.setDataType(this.annoInfo.PROJECT.TYPE); + end + else + response.setDataType(this.annoInfo.PROJECT.TYPE); + end + + case eOCPQueryType.RAMONMetaOnly + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Build URL + url = this.buildRAMONUrl(qObj); + this.lastUrl = url; + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url)); + this.lastHdfFile = hdfFile.filename; + % Convert to RAMON Object based on what comes back + response = hdfFile.toRAMONObject(qObj); + + + case eOCPQueryType.RAMONBoundingBox + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + + % Build URL + url = this.buildRAMONUrl(qObj); + this.lastUrl = url; + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url)); + this.lastHdfFile = hdfFile.filename; + % Convert to RAMON Object based on what comes back + response = hdfFile.toRAMONObject(qObj); + + + case eOCPQueryType.RAMONIdList + + % If doing a cutout and resolution isn't set Set Default and warn. + if ~isempty(qObj.xRange) && ~isempty(qObj.yRange) && ~isempty(qObj.zRange) + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildRAMONIdUrl(qObj); + this.lastUrl = url; + + % Build HDF5 if needed + if isempty(qObj.xRange) && isempty(qObj.yRange) && isempty(qObj.zRange) + % no cutout restriction + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url)); + this.lastHdfFile = hdfFile.filename; + + elseif ~isempty(qObj.xRange) && ~isempty(qObj.yRange)... + && ~isempty(qObj.zRange) && ~isempty(qObj.resolution) + % cutout Restriction + hdfCutoutFile = OCPHdf(qObj); + hdfCutoutFile.toCutoutHDF(); + + % Query DB and get HDF5 File + hdfFile = OCPHdf(this.net.read(url,hdfCutoutFile.filename)); + this.lastHdfFile = hdfFile.filename; + + else + ex = MException('OCP:MalformedQuery','Cannot build URL. Either set or clear cutout args.'); + throw(ex); + end + + % Convert to a vector + response = hdfFile.toMatrix(); + + case eOCPQueryType.voxelId + % If resolution isn't set Set Default and warn. + if isempty(qObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided query is missing the resolution property. The default resolution has not been set in this OCP object. Cannot Query Database.'); + end + + qObj.setResolution(this.defaultResolution); + warning('OCP:QueryResolutionEmpty',... + 'Resolution empty in query. Default value of %d used. Turn off "OCP:QueryResolutionEmpty" to suppress',this.defaultResolution); + end + + % Verify query + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to read from by setting the "annoToken" property. '); + throw(ex); + end + + % Build URL + url = this.buildXyzVoxelIdUrl(qObj); + this.lastUrl = url; + + % Query + hdfResponse = OCPHdf(this.net.read(url)); + response = str2double(hdfResponse.filename); + + otherwise + ex = MException('OCP:InvalidQuery','Invalid Query Type'); + throw(ex); + end + + % Save query + this.lastQuery = qObj; + end + + %% Methods - Single slice + function slice = nextSlice(this) + % This method increments cIndex by 1 and returns an image + % The last query MUST have been a slice query for this to work + slice = []; + qObj = this.lastQuery; + + if ~isempty(qObj) + if qObj.type == eOCPQueryType.annoSlice || ... + qObj.type == eOCPQueryType.imageSlice || ... + qObj.type == eOCPQueryType.overlaySlice + % You are good to go + + % Verify query + qObj.setCIndex(qObj.cIndex + 1); + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildSliceUrl(qObj); + this.lastUrl = url; + % Query DB and get png File back + slice = this.net.queryImage(url); + else + warning('OCP:MissingInitQuery','You must run a full slice query before using next/previous methods.'); + end + else + warning('OCP:MissingInitQuery','You must run a full slice query before using next/previous methods.'); + end + end + + + function slice = previousSlice(this) + % This method decrements cIndex by 1 and returns an image + % The last query MUST have been a slice query for this to work + slice = []; + qObj = this.lastQuery; + + if ~isempty(qObj) + if qObj.type == eOCPQueryType.annoSlice || ... + qObj.type == eOCPQueryType.imageSlice || ... + qObj.type == eOCPQueryType.overlaySlice + % You are good to go + + % Verify query + qObj.setCIndex(qObj.cIndex - 1); + [tf, msg] = qObj.validate(this.annoInfo); + if tf == 0 + ex = MException('OCP:BadQuery',sprintf('Query Validation Failed: \n%s',msg)); + throw(ex); + end + % Build URL + url = this.buildSliceUrl(qObj); + this.lastUrl = url; + % Query DB and get png File back + slice = this.net.queryImage(url); + else + warning('OCP:MissingInitQuery','You must run a full slice query before using next/previous methods.'); + end + else + warning('OCP:MissingInitQuery','You must run a full slice query before using next/previous methods.'); + end + end + + %% Methods - Image Data Upload + function uploadImageData(this, ramonObj, conflictOption) + % This method writes image data to the database + % specified by imageToken. + % + % You can also optionally specify the conflict option + % (overwrite OR preserve) by passing in your choice + % via the eOCPConflictOption enum. If omitted default is overwrite. + % + % ex: oo.uploadImageData(myData,eOCPConflicOption.preserve); + % + + % TODO: Service should be able to propagate image8 data. +% if (this.getImagePropagateStatus() ~= eOCPPropagateStatus.inconsistent) +% ex = MException('OCP:DbLocked',... +% 'Image DB is locked due to propagation. Must wait for db to be consistent, then make writable'); +% throw(ex); +% end + + % Set default conflict option default (overwrite) if needed + if ~exist('conflictOption','var') + conflictOption = eOCPConflictOption.overwrite; + else + if conflictOption == eOCPConflictOption.exception || ... + conflictOption == eOCPConflictOption.reduce + ex = MException('OCP:InvalidConflictOpt',... + 'When uploading Image Data you must use overwrite or preserve'); + throw(ex); + end + end + + % If imageToken hasn't been set stop + if isempty(this.imageToken) + ex = MException('OCP:MissingImageToken',... + 'You must specify the image database to write to by setting the "imageToken" property. '); + throw(ex); + end + + % Make sure you are writing to a database that is properly + % setup for image upload + if this.imageInfo.PROJECT.EXCEPTIONS == 1 + warning('OCP:ProjectOpts','Exceptions are enabled for the image database. This is non-optimal and unnessary for image data databases'); + end + + % Make sure you are writing to an Image datatype + if this.imageInfo.PROJECT.TYPE ~= 1 && ... + this.imageInfo.PROJECT.TYPE ~= 8 + + ex = MException('OCP:ProjectOpts',... + 'The current imageToken is for a non-grayscale datatype database. Only grayscale 8 or 16bit is supported. Contact OCP support of uploading multichannel data'); + throw(ex); + end + + % Make sure you have a RAMON volume and not something else + if strcmpi(class(ramonObj),'RAMONVolume') == false + ex = MException('OCP:InvalidClassType',... + 'To upload image data, please provide a RAMONVolume object'); + throw(ex); + end + + % Check for chunking + if ramonObj.voxelCount() > this.maxAnnoSize + % Block style upload + if ramonObj.voxelCount() > this.maxAnnoSize * 10 + warning('OCP:LargeUpload','The RAMONVolume is very large and will be chunked. Write may take a long time depending on internet speed.'); + end + + % Set datatype to that of the database + ramonObj.setDataType(eRAMONDataType(oo.imageInfo.PROJECT.TYPE)); + + % Compute chunked blocks by slice to make it easy for now. + % TODO: make this smarter if needed to deal with massive + % single slices + dims = size(ramonObj); + slices_per_upload = floor(this.maxAnnoSize/(dims(1)*dims(2))); + slices_per_upload = max(slices_per_upload,1); + + % Create temp ramon volume for chunks + temp_vol = RAMONVolume(); + temp_vol.setResolution(ramonObj.resolution); + temp_vol.setDataType(ramonObj.dataType); + + zstart = 1; + zend = zstart + slices_per_upload - 1; + + while zend <= dims(3) + % Set data + temp_vol.setCutout(ramonObj.data(:,:,zstart:zend)); + + % Set xyz offset + temp_vol.setXyzOffset([ramonObj.xyzOffset(1),ramonObj.xyzOffset(2),... + ramonObj.xyzOffset(3) + zstart - 1]); + + % post + this.writeBlockImageData(ramonObj, conflictOption) + + zstart = zend + 1; + zend = zstart + slices_per_upload - 1; + end + + % Check if you have "remainder" slices + if zend ~= dims(3) + % Set data + temp_vol.setCutout(ramonObj.data(:,:,zstart:end)); + + % Set xyz offset + temp_vol.setXyzOffset([ramonObj.xyzOffset(1),ramonObj.xyzOffset(2),... + ramonObj.xyzOffset(3) + zstart - 1]); + + % post + this.writeBlockImageData(ramonObj, conflictOption) + end + else + % Set datatype to that of the database + ramonObj.setDataType(eRAMONDataType(this.imageInfo.PROJECT.TYPE)); + + % Block style upload + this.writeBlockImageData(ramonObj, conflictOption) + end + end + + %% Methods - RAMON - Create + function id = createAnnotation(this, ramonObj, conflictOption) + % This method adds a new RAMON annotation to the database + % specified by annoToken. + % + % It supports the batch interface by passing in a cell array of + % RAMON objects instead of a single RAMON Object. + % + % You can also optionally specify the conflict option + % (overwrite, preserve, exception) by passing in your choice + % via the eOCPConflictOption enum. If omitted default is overwrite. + % + % ex: id = createAnnotation(myObjects,eOCPConflicOption.overwrite); + % + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to write to by setting the "annoToken" property. '); + throw(ex); + end + + % Make sure you can write to db + % TODO: For now this is only for anno32 and anno64 type + % databases + if (this.annoInfo.PROJECT.TYPE == eRAMONDataType.anno32 || ... + this.annoInfo.PROJECT.TYPE == eRAMONDataType.anno64) + if (this.getAnnoPropagateStatus() ~= eOCPPropagateStatus.inconsistent) + ex = MException('OCP:DbLocked',... + 'Annotation DB is locked due to propagation. Must wait for db to be consistent, then make writable'); + throw(ex); + end + end + + % Set default conflict option default (overwrite) if needed + if ~exist('conflictOption','var') + conflictOption = eOCPConflictOption.overwrite; + end + + + % Build upload job based on input. If a cell array of + % RAMONObjects then you must build batches based on + % this.batchSize. If any annotation is larger than + % this.maxAnnoSize then it must be broken up into pieces to + % make http requests resonable in size and safe to pass through most + % networks without trouble + + if isa(ramonObj,'cell') + % Batch upload requested - build batch index while checking + % for big annotations that need chunked + + if strcmpi(class(ramonObj),'RAMONVolume') + error('OCP:NotSupported','Batch uploading of block style uploads is not supported.'); + end + + [batchIndex, chunkIndex] = this.createAnnoBatches(ramonObj); + + % Upload Batches + batchIds = []; + for ii = 1:size(batchIndex,1) + % Set datatype + ramonObj_batch = this.checkSetDataType(ramonObj(batchIndex{ii})); + + batchIds = cat(2,batchIds,... + this.writeRamonObject(ramonObj_batch,false,conflictOption)); + end + + % If required chunk annotations and upload + chunkIds = []; + if ~isempty(chunkIndex) + chunkCollections = this.createAnnoChunks(ramonObj(chunkIndex)); + + % Set datatype + chunkCollections = this.checkSetDataType(chunkCollections); + + chunkIds = this.writeRamonChunks(chunkCollections,false,conflictOption); + end + + % Finalize output + id = zeros(1,length(batchIds) + length(chunkIds)); + batchIndexCat = []; + for ii = 1:size(batchIndex,1) + batchIndexCat = cat(2,batchIndexCat,batchIndex{ii}); + end + id(batchIndexCat) = batchIds; + id(chunkIndex) = chunkIds; + else + % Check for chunking + if ramonObj.voxelCount() > this.maxAnnoSize + if strcmpi(class(ramonObj),'RAMONVolume') + % Block style upload + if ramonObj.voxelCount() > this.maxAnnoSize * 10 + warning('OCP:LargeUpload','The RAMONVolume is VERY large and will be chunked. Write may take a long time.'); + end + + % Set datatype + ramonObj = this.checkSetDataType(ramonObj); + + this.writeBlockData(ramonObj, conflictOption) + id = []; + else + % Chunk it up + chunkCollection = this.createAnnoChunks(ramonObj); + + % Set datatype + chunkCollection = this.checkSetDataType(chunkCollection); + + % Upload the chunked annotation + id = this.writeRamonChunks(chunkCollection,false,conflictOption); + end + else + % Single annotation to upload + if strcmpi(class(ramonObj),'RAMONVolume') + % Set datatype + ramonObj = this.checkSetDataType(ramonObj); + + % Block style upload + this.writeBlockData(ramonObj, conflictOption) + id = []; + else + % Set datatype + ramonObj = this.checkSetDataType(ramonObj); + + % Standard RAMON Object upload + id = this.writeRamonObject(ramonObj, false, conflictOption); + end + end + end + end + + %% Methods - RAMON - Update + function id = updateAnnotation(this, ramonObj, conflictOption) + % This method updates a RAMON annotation to the database + % specified by annoToken. + % + % It supports the batch interface by passing in a cell array of + % RAMON objects instead of a single RAMON Object. + % + % You can also optionally specify the conflict option + % (overwrite, preserve, exception) by passing in your choice + % via the eOCPConflictOption enum. If omitted default is overwrite. + % + + % Set default conflict option default (overwrite) if needed + if ~exist('conflictOption','var') + conflictOption = eOCPConflictOption.overwrite; + end + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to write to by setting the "annoToken" property. '); + throw(ex); + end + + % Build upload job based on input. If a cell array of + % RAMONObjects then you must build batches based on + % this.batchSize. If any annotation is larger than + % this.maxAnnoSize then it must be broken up into pieces to + % make http requests resonable in size and safe to pass through most + % networks without trouble + + if isa(ramonObj,'cell') + % Batch upload requested - build batch index while checking + % for big annotations that need chunked + + if strcmpi(class(ramonObj{1}),'RAMONVolume') + error('OCP:NotSupported','Updating of block style uploads is not supported. Create annotation with the appropriate conflict options'); + end + + [batchIndex, chunkIndex] = this.createAnnoBatches(ramonObj); + + % Upload Batches + batchIds = []; + for ii = 1:size(batchIndex,1) + batchIds = cat(2,batchIds,... + this.writeRamonObject(ramonObj(batchIndex{ii}),true,conflictOption)); + end + + % If required chunk annotations and upload + chunkIds = []; + if ~isempty(chunkIndex) + chunkCollections = this.createAnnoChunks(ramonObj(chunkIndex)); + + for ii = 1:size(chunkCollections) + chunkIds = cat(2, chunkIds,... + this.writeRamonChunks(chunkCollections,true,conflictOption)); + end + end + + % Finalize output + id = cat(2,batchIds,chunkIds); + + else + if strcmpi(class(ramonObj),'RAMONVolume') + error('OCP:NotSupported','Updating of block style uploads is not supported. Create annotation with the appropriate conflict options'); + end + + % Check for chunking + if ramonObj.voxelCount() > this.maxAnnoSize + % Chunk it up + chunkCollection = this.createAnnoChunks(ramonObj); + + % Upload the chunked annotation + id = this.writeRamonChunks(chunkCollection,true,conflictOption); + else + % Single annotation to upload + id = this.writeRamonObject(ramonObj, true, conflictOption); + end + end + end + + %% Methods - RAMON - Delete + function response = deleteAnnotation(this, id) + % This method deletes an exisiting RAMON Annotation in the + % database specified by annotationToken and id + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to write to by setting the "annoToken" property. '); + throw(ex); + end + + % If in batch mode build id string + if length(id) > 1 + idStr = sprintf('%d,',id); + idStr(end) = []; + else + idStr = sprintf('%d',id); + end + + % Send DELETE request + urlStr = sprintf('%s/ocp/ocpca/%s/%s/',this.serverLocation,this.annoToken,idStr); + this.lastUrl = urlStr; + response = this.net.deleteRequest(urlStr); + end + + %% Methods - RAMON - Merge + function response = mergeAnnotation(this, parent, children) + % This method merges an 1xn vector of exisiting RAMON + % Annotations (children) into the parent annotation in the + % database indicated by annoToken AT THE DEFAULT RESOLUTION + + % If annoToken hasn't been set stop + if isempty(this.annoToken) + ex = MException('OCP:MissingAnnoToken',... + 'You must specify the annotation database to write to by setting the "annoToken" property. '); + throw(ex); + end + + % build id string + merge_str = num2str(parent); + merge_str = [merge_str sprintf(',%d',children)]; + + % Send request + urlStr = sprintf('%s/ocp/ca/%s/merge/%s/global/%d/',this.serverLocation,this.annoToken,merge_str,this.defaultResolution); + this.lastUrl = urlStr; + response = this.net.read(urlStr); + end + + %% Methods - RAMON - Get/Set + function setField(this, id, field, value) + % This method sets an individual field of an annotation based + % on the provided annotation id and field name and value. Use OCPFields + % to get the proper translation from RAMON api names to server. + % + % NOTE: Only single value fields are currently supported + % + % ex: f = OCPFields; + % val = oo.setField(2834,f.synapse.synapseType,eRAMONSynapseType.excitatory); + % val = oo.getField(2834,'author','my_username'); + + % Build URL + url = this.buildSetFieldURL(id,field,value); + this.lastUrl = url; + + % Call URL + this.net.read(url); + end + + + function value = getField(this, id, field) + % This method gets an individual field from an annotation based + % on the provided annotation id and field name. Use OCPFields + % to get the proper translation from RAMON api names to server + % names. + % ex: f = OCPFields; + % val = oo.getField(2834,f.synapse.synapseType); + % val = oo.getField(2834,'author'); + + % Build URL + url = sprintf('%s/ocp/ocpca/%s/%d/getField/%s/',this.serverLocation,this.annoToken,id,field); + this.lastUrl = url; + + % Call URL + response = this.net.read(url); + + % Parse response + value = this.getFieldParser(response,field); + end + + + %% Methods - Database Info Query + function info = queryDBInfo(this,token) + % This method queries the "projinfo" service with a token and + % returns information about that database. The return + % structure is: + % + % TODO: document + + url = sprintf('%s/ocp/ocpca/%s/projinfo/',this.serverLocation,token); + this.lastUrl = url; + + % Query DB for hdf5 file + hdfFile = this.net.read(url); + + % Load into struct + ocpH5 = OCPHdf(hdfFile); + info = ocpH5.toStruct(); + end + + %% Methods - ID Reservation + function ids = reserve_ids(this, num_ids) + % This method requests a contiguous batch of IDs from the OCP + % database for use during uploading. You must manually set your + % RAMON object ids, but this helps speed up big batch writes + + % Check num_ids + validateattributes(num_ids,{'numeric'},{'scalar','integer','finite','nonnegative','nonnan','real'}); + + % Build URL + url = sprintf('%socp/ca/%s/reserve/%d/', ... + this.serverLocation,this.annoToken,num_ids); + this.lastUrl = url; + + % Query DB + response = this.net.read(url); + + % Parse and return + response = eval(response); + ids = response(1):response(1) + response(2) - 1; + + end + + + %% Methods - getAnnoPropagateStatus checks the status of the + % annotation database propagation + function status = getAnnoPropagateStatus(this) + % Check status + if ~isempty(this.annoToken) + status = this.getPropagateStatus(this.annoToken); + else + status = []; + end + end + + %% Methods - getImagePropagateStatus checks the status of the + % image database propagation + function status = getImagePropagateStatus(this) + % Check status + if ~isempty(this.annoToken) + status = this.getPropagateStatus(this.imageToken); + else + status = []; + end + end + + + %% Methods - makeAnnoWritable sets the status of the + % annotation database to inconsistant so you can write to it (only + % needed if the database has been propagated and is currently + % consistent. + function makeAnnoWritable(this) + if ~isempty(this.annoToken) + + if (this.getAnnoPropagateStatus() == eOCPPropagateStatus.propagating) + ex = MException('OCP:DbLocked',... + 'Annotation DB is locked due to propagation. Must wait for db to be consistent, before you can make it writable'); + throw(ex); + end + + this.setPropagateStatus(this.annoToken, eOCPPropagateStatus.inconsistent); + else + warning('OCP:MissingToken','You must set the annoToken before you can change propagation state!'); + end + end + + %% Methods - makeImageWritable sets the status of the + % image database to inconsistant so you can write to it (only + % needed if the database has been propagated and is currently + % consistent. + function makeImageWritable(this) + if ~isempty(this.annoToken) + if (this.getImagePropagateStatus() == eOCPPropagateStatus.propagating) + ex = MException('OCP:DbLocked',... + 'Image DB is locked due to propagation. Must wait for db to be consistent, before you can make it writable'); + throw(ex); + end + + this.setPropagateStatus(this.imageToken, eOCPPropagateStatus.inconsistent); + else + warning('OCP:MissingToken','You must set the imageToken before you can change propagation state!'); + end + end + + %% Methods - propagateAnnoDB triggers propagation of the annoToken + % database. Will lock the db until the propagation process has + % completed + function propagateAnnoDB(this) + if ~isempty(this.annoToken) + this.setPropagateStatus(this.annoToken, eOCPPropagateStatus.propagating); + else + warning('OCP:MissingToken','You must set the annoToken before you can propagate it!'); + end + end + + %% Methods - propagateImageDB triggers propagation of the imageToken + % database. Will lock the db until the propagation process has + % completed + function propagateImageDB(this) + if ~isempty(this.imageToken) + this.setPropagateStatus(this.imageToken, eOCPPropagateStatus.propagating); + else + warning('OCP:MissingToken','You must set the imageToken before you can propagate it!'); + end + end + + + end + + methods( Access = private ) + + %% Private Method - calculate batches based on settings and annotation size + function [batchIndex, chunkIndex] = createAnnoBatches(this, ramonObj) + % This method creates an index that breaks the ramonObj cell + % array into batches for upload based on both number of + % annotation and voxel size of annotations + + objectIndex = 1:length(ramonObj); + + % Check for objects that require chunking + count = cellfun(@(x) x.voxelCount,ramonObj); + chunkIndex = find(count > this.maxAnnoSize); + objectIndex(chunkIndex) = []; + + % Build batches + numObj = length(objectIndex); + numGroups = floor(numObj / this.batchSize); + remainGroup = mod(numObj,this.batchSize); + + if remainGroup ~= 0 + batchIndex = cell(numGroups + 1, 1); + else + batchIndex = cell(numGroups, 1); + end + + startInd = 1; + for ii = 1:numGroups + batchIndex(ii) = {objectIndex(startInd:startInd + this.batchSize - 1)}; + startInd = startInd + this.batchSize; + end + + if remainGroup ~= 0 + batchIndex(numGroups + 1) = {objectIndex(startInd:numObj)}; + end + end + + %% Private Method - inforce max annotation size with chunking + function chunkCollections = createAnnoChunks(this, ramonObj) + % This method enforces max annotation size by breaking the + % annotation into chunks that can be uploaded + + if length(ramonObj) == 1 + ramonObj = {ramonObj}; + chunkCollections = cell(1,1); + else + chunkCollections = cell(1,length(ramonObj)); + end + + for ii = 1:length(ramonObj) + clear chunkGroup + + % Convert to voxel list + annoVoxelList = ramonObj{ii}.clone; + annoVoxelList.toVoxelList(); + + % Create update objects + voxCount = annoVoxelList.voxelCount(); + numChunks = floor(voxCount / this.maxAnnoSize); + remainChunk = mod(voxCount,this.maxAnnoSize); + + % Create empty initial annotation + template = annoVoxelList.clone('novoxels'); + template.setVoxelList([]); + if remainChunk ~= 0 + chunkGroup = cell(1,numChunks + 2); + else + chunkGroup = cell(1,numChunks + 1); + end + chunkGroup{1} = template; + + % Create chunks + startInd = 1; + if numChunks ~= 0 + for jj = 2:numChunks+1 + tempObj = template.clone('novoxels'); + tempObj.setVoxelList(annoVoxelList.data(startInd:startInd+this.maxAnnoSize-1,:)); + startInd = startInd+this.maxAnnoSize; + chunkGroup{jj} = tempObj; + end + end + + if remainChunk ~= 0 + tempObj = template.clone('novoxels'); + tempObj.setVoxelList(annoVoxelList.data(startInd:voxCount,:)); + chunkGroup{end} = tempObj; + end + + % Store group into collection + chunkCollections{ii} = chunkGroup; + end + end + + %% Private Method - write annotation chunks to database + function id = writeRamonChunks(this, chunkCollection, updateFlag, conflictOption) + % This method writes a chunked ramon object by creating an + % annotation without voxel data first and then updating it + % repeatedly with the chunked voxel data in list form. + % + % updateFlag is an optional boolean indicating if you want to + % upload the annotation as an update instead of a create. + % It is false by default (create new annotation) + + if ~exist('updateFlag','var') + updateFlag = false; + end + + id = []; + for ii = 1:length(chunkCollection) + chunkGroup = chunkCollection{ii}; + + if updateFlag == false + % Write empty anno and get ID + id = cat(2,id,... + this.writeRamonObject(chunkGroup{1}, 0, conflictOption)); + else + % You are doing a big update so first write is an + % update as well + id = cat(2,id,... + this.writeRamonObject(chunkGroup{1}, 1, conflictOption)); + end + + % Set the ID and update with all voxel chunks + for jj = 2:length(chunkGroup) + chunkGroup{jj}.setId(id(end)); + this.writeRamonObject(chunkGroup{jj}, 1, conflictOption); + end + end + end + + %% Private Method - write annotation to database + function id = writeRamonObject(this, ramonObj, updateFlag, conflictOption) + % This method writes a ramon object to the specified database + % Also supports batch interface so ramonObj can be a cell array. + % + % updateFlag is an optional boolean indicating if you want to + % upload the annotation as an update instead of a create. + % If true all objects must have an ID already assigned + + % If updateFlag is true, enforce ID requirement + if exist('updateFlag','var') + if updateFlag == true + if isa(ramonObj,'cell') + % Batch Upload + ind = cellfun(@(x) isprop(x,'id'),ramonObj); + ind1 = cellfun(@(x) isempty(x.id),ramonObj(ind)); + ind2 = cellfun(@(x) x.id == 0,ramonObj(ind)); + + if sum(ind1) > 0 || sum(ind2) > 0 + error('OCP:IdMissing',... + 'If uploading annotations with the updateFlag = true you must set the ID field in each object first so the correct annotation can be updated!'); + end + else + % Single object + if isempty(ramonObj.id) || ramonObj.id == 0 + error('OCP:IdMissing',... + 'If uploading annotations with the updateFlag = true you must set the ID field in each object first so the correct annotation can be updated!'); + end + end + end + else + updateFlag = false; + end + + + % If resolution isn't set Set Default and warn. + if isa(ramonObj,'cell') + % Batch Upload + + ind = cellfun(@(x) isprop(x,'resolution'),ramonObj); + ind = cellfun(@isempty,ramonObj(ind)); + + if sum(ind) > 0 + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided RAMON Object''s resolution property is empty AND the default resolution in this OCP object is empty. One of these is required to complete operation.'); + end + + cellfun(@(x) x.setResolution(this.defaultResolution),ramonObj(ind)) + warning('OCP:RAMONResolutionEmpty',... + 'Resolution empty in RAMON Object. Default value of %d used. Turn off "OCP:RAMONResolutionEmpty" to suppress',this.defaultResolution); + end + else + % Single object + + if isprop(ramonObj,'resolution') + if isempty(ramonObj.resolution) + if isempty(this.defaultResolution) + error('OCP:MissingDefaultResolution',... + 'The provided RAMON Object''s resolution property is empty AND the default resolution in this OCP object is empty. One of these is required to complete operation.'); + end + ramonObj.setResolution(this.defaultResolution); + warning('OCP:RAMONResolutionEmpty',... + 'Resolution empty in RAMON Object. Default value of %d used. Turn off "OCP:RAMONResolutionEmpty" to suppress',this.defaultResolution); + end + end + end + + % Create HDF5 file + hdfFile = OCPHdf(ramonObj); + + % Build URL + if updateFlag == false + % Create Annotation + urlStr = sprintf('%s/ocp/ocpca/%s/%s/',... + this.serverLocation,... + this.annoToken,... + char(conflictOption)); + else + % Update Annotation + urlStr = sprintf('%s/ocp/ocpca/%s/update/%s/',... + this.serverLocation,... + this.annoToken,... + char(conflictOption)); + end + this.lastUrl = urlStr; + + % Post HDF file to database with retry on failure + cnt = 1; + while cnt <= 5 + try + id = this.net.write(urlStr,hdfFile.filename); + break; + + catch ME + if cnt == 5 + rethrow(ME); + end + warning('OCP:BatchWriteError','DB Write Op Failed. Attempting RETRY: %d\nError: %s',cnt,ME.message); + pause(5*cnt); + cnt = cnt + 1; + end + end + + if isa(ramonObj,'cell') + % Batch Upload + id = eval(['[' id ']']); + else + % Single object + id = str2double(id); + end + end + + %% Private Method - write block style annotation to database + function writeBlockData(this, ramonVol, conflictOption) + % This method writes a ramon RAMONVolume to the specified database + % + % Upload Type must be set in the RAMONVolume so the data can be + % converted to the proper format. + % + % XYZOffset and Resolution must be set so the database can + % locate the data in the volume properly! + % + % Note: Data must match the selected upload type or possible + % loss of data may occur! + % Note: Upload type must match database type or upload will + % fail. + + % If not a RAMONVolume fail + if ~strcmpi(class(ramonVol),'RAMONVolume') + error('OCP:IncorrectObjectTyp',... + 'You can only block style upload RAMONVolume objects.'); + end + + % If resolution isn't set fail + if isempty(ramonVol.resolution) + error('OCP:ResolutionEmpty',... + 'Resolution empty in RAMONVolume. You must set this before uploading block style data!'); + end + + % If xyz offset isn't set fail + if isempty(ramonVol.xyzOffset) + error('OCP:XYZOffsetEmpty',... + 'XYZ Offset empty in RAMONVolume. You must set this before uploading block style data!'); + end + + % If upload type doesn't match database fail + if ((this.annoInfo.PROJECT.TYPE) ~= uint32(ramonVol.dataType)) + error('OCP:DataTypeMismatch',... + 'The RAMONVolume type does not match the database you are trying to upload to. Project: %d - Database: %d',... + this.annoInfo.PROJECT.TYPE,uint32(ramonVol.dataType)); + end + + % Create HDF5 file + hdfFile = OCPHdf(ramonVol); + + + % Build URL + urlStr = sprintf('%socp/ca/%s/hdf5/%d/%d,%d/%d,%d/%d,%d/%s/',... + this.serverLocation,... + this.annoToken,... + ramonVol.resolution,... + ramonVol.xyzOffset(1), ramonVol.xyzOffset(1) + ramonVol.size(2),... + ramonVol.xyzOffset(2),ramonVol.xyzOffset(2) + ramonVol.size(1),... + ramonVol.xyzOffset(3),ramonVol.xyzOffset(3) + ramonVol.size(3),... + char(conflictOption)); + + this.lastUrl = urlStr; + + % Post HDF file to database with retry on failure + cnt = 1; + while cnt <= 5 + try + this.net.write(urlStr,hdfFile.filename); + break; + + catch ME + if cnt == 5 + rethrow(ME); + end + warning('OCP:BlockWriteError','DB Write Op Failed. Attempting RETRY: %d\nError: %s',cnt,ME.message); + pause(5*cnt); + cnt = cnt + 1; + end + end + end + + %% Private Method - write block style annotation to database + function writeBlockImageData(this, ramonVol, conflictOption) + % This method writes a ramon RAMONVolume to the specified database + % + % Upload Type must be set in the RAMONVolume so the data can be + % converted to the proper format. + % + % XYZOffset and Resolution must be set so the database can + % locate the data in the volume properly! + % + % Note: Data must match the selected upload type or possible + % loss of data may occur! + % Note: Upload type must match database type or upload will + % fail. + + % If resolution isn't set fail + if isempty(ramonVol.resolution) + error('OCP:ResolutionEmpty',... + 'Resolution empty in RAMONVolume. You must set this before uploading block style data!'); + end + + % If xyz offset isn't set fail + if isempty(ramonVol.xyzOffset) + error('OCP:XYZOffsetEmpty',... + 'XYZ Offset empty in RAMONVolume. You must set this before uploading block style data!'); + end + + % If upload type doesn't match database fail + if ((this.imageInfo.PROJECT.TYPE) ~= uint32(ramonVol.dataType)) + error('OCP:DataTypeMismatch',... + 'The RAMONVolume type does not match the database you are trying to upload to. Project: %d - Database: %d',... + this.annoInfo.PROJECT.TYPE,uint32(ramonVol.dataType)); + end + + % Create HDF5 file + hdfFile = OCPHdf(ramonVol); + + % Build URL + urlStr = sprintf('%socp/ca/%s/hdf5/%d/%d,%d/%d,%d/%d,%d/%s/',... + this.serverLocation,... + this.imageToken,... + ramonVol.resolution,... + ramonVol.xyzOffset(1), ramonVol.xyzOffset(1) + ramonVol.size(2),... + ramonVol.xyzOffset(2),ramonVol.xyzOffset(2) + ramonVol.size(1),... + ramonVol.xyzOffset(3),ramonVol.xyzOffset(3) + ramonVol.size(3),... + char(conflictOption)); + + this.lastUrl = urlStr; + + % Post HDF file to database with retry on failure + cnt = 1; + while cnt <= 5 + try + this.net.write(urlStr,hdfFile.filename); + break; + + catch ME + if cnt == 5 + rethrow(ME); + end + warning('OCP:BlockWriteError','DB Write Op Failed. Attempting RETRY: %d\nError: %s',cnt,ME.message); + pause(5*cnt); + cnt = cnt + 1; + end + end + end + + %% Private Method - Build URL - Cutout + function url = buildCutoutUrl(this, qObj) + % This method build the url for a cutout type query. It + % selects the correct service and token automatically. + isMulti = false; + switch qObj.type + case eOCPQueryType.imageDense + service = 'ocp/ca'; + token = this.imageToken; + qObj.setFilterIds([]); + if (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels16) || ... + (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels8) + isMulti = true; + end + case eOCPQueryType.annoDense + service = 'ocp/ca'; + token = this.annoToken; + case eOCPQueryType.probDense + service = 'ocp/ca'; + token = this.annoToken; + qObj.setFilterIds([]); + otherwise + ex = MException('OCP:NotCutout','Query is not a cutout type. Cannot build url.'); + throw(ex); + end + + if isMulti == true + % Multichannel Database! + % Build channel string + new_channels = cell(length(qObj.channels),1); + for ii = 1:length(qObj.channels) + new_channels{ii} = strrep(qObj.channels{ii},'__','-'); + end + channel_str = sprintf('%s,',new_channels{:}); + + % build url + url = sprintf('%s%s/%s/hdf5/%s/%d/%d,%d/%d,%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + channel_str(1:end-1),... + qObj.resolution,... + qObj.xRange(1),qObj.xRange(2),... + qObj.yRange(1),qObj.yRange(2),... + qObj.zRange(1),qObj.zRange(2)); + + else + % Don't use channels in normal cutout so clear channels to + % support datatype kludge + qObj.setChannels([]); + + % normal + url = sprintf('%s%s/%s/hdf5/%d/%d,%d/%d,%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + qObj.resolution,... + qObj.xRange(1),qObj.xRange(2),... + qObj.yRange(1),qObj.yRange(2),... + qObj.zRange(1),qObj.zRange(2)); + end + + if ~isempty(qObj.filterIds) + % add filter option + ids2filter = sprintf('%d,',qObj.filterIds); + ids2filter(end) = []; + url = sprintf('%sfilter/%s/',... + url,... + ids2filter); + + end + end + + %% Private Method - Build URL - Slice + function url = buildSliceUrl(this, qObj) + % This method build the url for a slice type query. It + % selects the correct service and token automatically. + isMulti = false; + switch qObj.type + case eOCPQueryType.imageSlice + service = 'ocp/ca'; + token = this.imageToken; + qObj.setFilterIds([]); + if (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels16) || ... + (this.imageInfo.PROJECT.TYPE == eRAMONDataType.channels8) + isMulti = true; + end + case eOCPQueryType.annoSlice + service = 'ocp/ca'; + token = this.annoToken; + otherwise + ex = MException('OCP:NotSlice','Query is not a slice type. Cannot build url.'); + throw(ex); + end + + + if isMulti == true + % Multichannel Database! + % Build channel string + new_channels = cell(length(qObj.channels),1); + for ii = 1:length(qObj.channels) + new_channels{ii} = strrep(qObj.channels{ii},'__','-'); + end + channel_str = sprintf('%s,',new_channels{:}); + + % build url + switch qObj.slicePlane + case eOCPSlicePlane.xy + url = sprintf('%s%s/%s/mcfc/%s/%s/%d/%d,%d/%d,%d/%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + channel_str(1:end-1),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2),... + qObj.cIndex(1)); + + case eOCPSlicePlane.xz + url = sprintf('%s%s/%s/mcfc/%s/%s/%d/%d,%d/%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + channel_str(1:end-1),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.cIndex(1),... + qObj.bRange(1),qObj.bRange(2)); + + case eOCPSlicePlane.yz + url = sprintf('%s%s/%s/mcfc/%s/%s/%d/%d/%d,%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + channel_str(1:end-1),... + qObj.resolution,... + qObj.cIndex(1),... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2)); + end + + else + % Don't use channels in normal cutout so clear channels to + % support datatype kludge + qObj.setChannels([]); + + % normal + switch qObj.slicePlane + case eOCPSlicePlane.xy + url = sprintf('%s%s/%s/%s/%d/%d,%d/%d,%d/%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2),... + qObj.cIndex(1)); + + case eOCPSlicePlane.xz + url = sprintf('%s%s/%s/%s/%d/%d,%d/%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.cIndex(1),... + qObj.bRange(1),qObj.bRange(2)); + + case eOCPSlicePlane.yz + url = sprintf('%s%s/%s/%s/%d/%d/%d,%d/%d,%d/',... + this.serverLocation,... + service,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.cIndex(1),... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2)); + end + end + + if ~isempty(qObj.filterIds) + % add filter option + ids2filter = sprintf('%d,',qObj.filterIds); + ids2filter(end) = []; + url = sprintf('%sfilter/%s/',... + url,... + ids2filter); + + end + end + + %% Private Method - Build URL - Overlay + function url = buildOverlayUrl(this, qObj) + % This method build the url for a overlay type query. + service = 'ocp/overlay'; + token = this.annoToken; + + + switch qObj.slicePlane + case eOCPSlicePlane.xy + url = sprintf('%s%s/%0.1f/%s/%s/%d/%d,%d/%d,%d/%d/',... + this.serverLocation,... + service,... + qObj.overlayAlpha,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2),... + qObj.cIndex(1)); + + case eOCPSlicePlane.xz + url = sprintf('%s%s/%0.1f/%s/%s/%d/%d,%d/%d/%d,%d/',... + this.serverLocation,... + service,... + qObj.overlayAlpha,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.aRange(1),qObj.aRange(2),... + qObj.cIndex(1),... + qObj.bRange(1),qObj.bRange(2)); + + case eOCPSlicePlane.yz + url = sprintf('%s%s/%0.1f/%s/%s/%d/%d/%d,%d/%d,%d/',... + this.serverLocation,... + service,... + qObj.overlayAlpha,... + token,... + char(qObj.slicePlane),... + qObj.resolution,... + qObj.cIndex(1),... + qObj.aRange(1),qObj.aRange(2),... + qObj.bRange(1),qObj.bRange(2)); + end + + if ~isempty(qObj.filterIds) + % add filter option + ids2filter = sprintf('%d,',qObj.filterIds); + ids2filter(end) = []; + url = sprintf('%sfilter/%s/',... + url,... + ids2filter); + + end + end + + %% Private Method - Build URL - RAMON + function url = buildRAMONUrl(this,qObj) + % This method build the url for a RAMON object query. It + % selects the correct service and token automatically. + switch qObj.type + case eOCPQueryType.RAMONDense + option = 'cutout'; + token = this.annoToken; + case eOCPQueryType.RAMONVoxelList + option = 'voxels'; + token = this.annoToken; + case eOCPQueryType.RAMONMetaOnly + option = 'nodata'; + token = this.annoToken; + case eOCPQueryType.RAMONBoundingBox + option = 'boundingbox'; + token = this.annoToken; + otherwise + ex = MException('OCP:NotRAMON','Query is not a RAMON type. Cannot build url.'); + throw(ex); + end + + % If batch mode you need to send csv list of ids in url + if length(qObj.id) > 1 + idStr = sprintf('%d,',qObj.id); + idStr(end) = []; + else + idStr = sprintf('%d',qObj.id); + end + + % If just tacking on the resolution add to URL + % If doing a cutout add all cutout args + % Otherwise don't mess with url + if ~isempty(qObj.resolution) && ... + isempty(qObj.xRange) && ... + isempty(qObj.yRange) && ... + isempty(qObj.zRange) + % Add resolution + url = sprintf('%socp/ca/%s/%s/%s/%d/',... + this.serverLocation,... + token,... + idStr,... + option,... + qObj.resolution); + elseif ~isempty(qObj.resolution) && ... + ~isempty(qObj.xRange) && ... + ~isempty(qObj.yRange) && ... + ~isempty(qObj.zRange) + % Add resolution + url = sprintf('%socp/ca/%s/%s/%s/%d/%d,%d/%d,%d/%d,%d/',... + this.serverLocation,... + token,... + idStr,... + option,... + qObj.resolution,... + qObj.xRange(1),qObj.xRange(2),... + qObj.yRange(1),qObj.yRange(2),... + qObj.zRange(1),qObj.zRange(2)); + else + % Don't + url = sprintf('%socp/ca/%s/%s/%s/',... + this.serverLocation,... + token,... + idStr,... + option); + end + end + + %% Private Method - Build URL - RAMON Id List + function url = buildRAMONIdUrl(this,qObj) + % This method build the url for an RAMON object id predicate + % query + + if ~isempty(qObj.idListPredicates) + % Build predicate string + predicateStr = ''; + keys = qObj.idListPredicates.keys; + for ii = 1:qObj.idListPredicates.Count + switch keys{ii} + case char(eOCPPredicate.status) + predicateStr = sprintf('%s%s/%d/',predicateStr,... + keys{ii},uint32(qObj.idListPredicates(keys{ii}))); + + case char(eOCPPredicate.type) + predicateStr = sprintf('%s%s/%d/',predicateStr,... + keys{ii},uint32(qObj.idListPredicates(keys{ii}))); + + case char(eOCPPredicate.confidence_gt) + predicateStr = sprintf('%s/confidence/gt/%f/',predicateStr,... + qObj.idListPredicates(keys{ii})); + + case char(eOCPPredicate.confidence_lt) + predicateStr = sprintf('%s/confidence/lt/%f/',predicateStr,... + qObj.idListPredicates(keys{ii})); + + otherwise + error('OCP:buildRAMONIdUrl','Unsupported predicate: %s',keys{ii}); + end + end + + % build url + url = sprintf('%socp/ca/%s/query/%s',... + this.serverLocation,... + this.annoToken,... + predicateStr); + else + % no predicates listed so get ids of everything + url = sprintf('%socp/ca/%s/query/',... + this.serverLocation,... + this.annoToken); + end + + % if there is a list limit supplied append it to the URL + if ~isempty(qObj.idListLimit) + url = sprintf('%slimit/%d/',url,qObj.idListLimit); + end + + end + + %% Private Method - Build URL - XYZ Voxel ID + function url = buildXyzVoxelIdUrl(this,qObj) + % This method build the url for an xyz voxel id query + + % Set resolution to default if it isn't set + if isempty(qObj.resolution) + qObj.setResolution(this.defaultResolution); + end + + url = sprintf('%socp/ca/%s/id/%d/%d/%d/%d/',... + this.serverLocation,... + this.getAnnoToken(),... + qObj.resolution,... + qObj.xyzCoord(1),... + qObj.xyzCoord(2),... + qObj.xyzCoord(3)); + + end + + + %% Private Method - Build setField URL + + function url = buildSetFieldURL(this, id, field, value) + % This method builds a REST URL to set a field of an existing + % annotation + + % Handle field value difference and convert to string + switch field + case 'author' + strVal = value; + + case {'status','synapse_type','segmentclass','cubelocation',... + 'organelleclass'} + strVal = num2str(uint32(value)); + + case {'confidence','weight','neuron','parentseed','source','parent'} + strVal = num2str(value); + + case {'seeds','position','synapses','organelles','segments'} + strVal = sprintf('%d,',value); + strVal(end) = []; + + otherwise + % Assume you're adding a custom KV pair + if ~ischar(field) + error('OCP:InvalidCustomKey','Custom Keys must be char strings'); + end + warning('OCP:CustomKVPair','Adding a custom KV pair - Key: %s',field); + + % Convert value into a string that will eval to what it + % should be + if isnumeric(value) + % String-ify so it will eval() back to a numeric + strVal = mat2str(value); + strVal = strrep(strVal,' ',','); + elseif ischar(value) + value = strrep(value,' ','_'); + strVal = value; + else + error('OCP:InvalidValueClass','Custom Values must be numeric or char strings. Class: %s',class(value)); + end + + end + + % Build URL + url = sprintf('%socp/ca/%s/%d/setField/%s/%s/',... + this.serverLocation,this.annoToken,id,field,strVal); + + + end + + %% Private Method - getPropagate checks token propagate status + function status = getPropagateStatus(this, token) + validateattributes(token,{'char'},{'nonempty'}); + + % Build URL + url = sprintf('%socp/ca/%s/getPropagate/',... + this.serverLocation,token); + + % Query DB and get HDF5 File + resp = this.net.read(url); + + % Set result and return + status = eOCPPropagateStatus(str2double(resp)); + end + + %% Private Method - setPropagate sets token propagate status + function setPropagateStatus(this, token, status) + validateattributes(token,{'char'},{'nonempty'}); + validateattributes(status,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + + % Validate status and convert to int + if ~isa(status, 'eOCPPropagateStatus') + try + status = eOCPPropagateStatus(status); + catch ME + rethrow(ME); + end + end + + status = int32(status); + + % Build URL + url = sprintf('%socp/ca/%s/setPropagate/%d/',... + this.serverLocation,token,status); + + % Query DB and get HDF5 File + this.net.read(url); + end + + %% Private Method - getField interface response parser + function value = getFieldParser(this, response, field) + % parse the string response from the get interface and make the + % appropriate type. + switch field + case {'author'} + value = response; + + case 'status' + value = eRAMONAnnoStatus(str2double(response)); + + case {'confidence','weight','neuron',... + 'parentseed','source','parent'} + value = str2double(response); + + case 'synapse_type' + value = eRAMONSynapseType(str2double(response)); + + case 'segmentclass' + value = eRAMONSegmentClass(str2double(response)); + + case 'organelleclass' + value = eRAMONOrganelleClass(str2double(response)); + + case 'cubelocation' + value = eRAMONCubeOrientation(str2double(response)); + + case 'segments' + if ~isempty(strfind(response,'],[')) + modStr = strrep(response,'],[','];['); + value = eval(['[' modStr ']']); + else + value = eval(['[' response ']']); + end + + case {'seeds','synapses','organelles','position'} + value = eval(['[' response ']']); + + otherwise + % Assume custom KVPair + % Eval to make sure numerics go back to numerics + try + if all(ismember(response, '0123456789+-.')) + value = eval(response); + else + value = response; + end + catch ME %#ok + value = response; + end + + end + + end + + %% Private Method - Build set dataType on annotation create + + function data_object = checkSetDataType(this, data_object) + if iscell(data_object) == 1 + % Check all cell objects + for ii = 1:length(data_object) + % Make sure you need to assign data type + classes = strfind(superclasses(data_object{ii}),'RAMONVolume'); + if any([classes{:}]) == 0 + %NOPE! + break + end + + if ~isempty(data_object{ii}.dataType) + % check that types match + if uint32(data_object{ii}.dataType) ~= this.annoInfo.PROJECT.TYPE + error('OCP:CheckSetDataType','Data type mismatch between project (%d) and object (%d)',... + this.annoInfo.PROJECT.TYPE,data_object{ii}.dataType) + end + else + % set data type to db value + data_object{ii}.setDataType(this.annoInfo.PROJECT.TYPE); + end + end + + else + % Single object + classes = strfind(superclasses(data_object),'RAMONVolume'); + if any([classes{:}]) == 1 + if ~isempty(data_object.dataType) + % check that types match + if uint32(data_object.dataType) ~= this.annoInfo.PROJECT.TYPE + error('OCP:CheckSetDataType','Data type mismatch between project (%d) and object (%d)',... + this.annoInfo.PROJECT.TYPE,data_object.dataType) + end + else + % set data type to db value + data_object.setDataType(this.annoInfo.PROJECT.TYPE); + end + end + end + end + + end +end + diff --git a/api/matlab/ocp/OCPFields.m b/api/matlab/ocp/OCPFields.m new file mode 100644 index 0000000..3722291 --- /dev/null +++ b/api/matlab/ocp/OCPFields.m @@ -0,0 +1,86 @@ +classdef OCPFields + %OCPFields Utility Class to convert between API and Server field names + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + properties (SetAccess = 'private', GetAccess = 'public') + generic + seed + synapse + segment + organelle + neuron + end + + methods + function this = OCPFields() + % Generic + this.generic = struct('status','status',... + 'confidence','confidence',... + 'author','author'); + + % seed + this.seed = struct('status','status',... + 'confidence','confidence',... + 'author','author',... + 'cubeOrientation','cubelocation',... + 'parentSeed','parent',... + 'sourceEntity','source',... + 'position','position'); + + % synapse + this.synapse = struct('status','status',... + 'confidence','confidence',... + 'author','author',... + 'synapseType','synapse_type',... + 'weight','weight',... + 'segments','segments',... + 'seeds','seeds'); + + % segment + this.segment = struct('status','status',... + 'confidence','confidence',... + 'author','author',... + 'class','segmentclass',... + 'neuron','neuron',... + 'synapses','synapses',... + 'organelles','organelles',... + 'parentSeed','parentseed'); + + % neuron + this.neuron = struct('status','status',... + 'confidence','confidence',... + 'author','author',... + 'segments','segments'); + + % neuron + this.organelle = struct('status','status',... + 'confidence','confidence',... + 'author','author',... + 'parentSeed','parentseed',... + 'class','organelleclass',... + 'seeds','seeds'); + end + + end + +end + diff --git a/api/matlab/ocp/OCPHdf.m b/api/matlab/ocp/OCPHdf.m new file mode 100644 index 0000000..2450bc9 --- /dev/null +++ b/api/matlab/ocp/OCPHdf.m @@ -0,0 +1,1463 @@ +classdef OCPHdf < handle + %OCPHdf ************************************************ + % Provides advanced hdf file handling and transformation capability for + % use with OCP framework + % + % Usage: + % + % h = OCPHdf(); Creates object + % + % Constructor is "overloaded": + % h = OCPHdf(RAMONobject); Converts RAMON Object to HDF5 File + % h = OCPHdf({RAMONobjects}); Converts cell array of RAMON Objects + % to an HDF5 File + % h = OCPHdf(OCPQuery); Sets query object property + % h = OCPHdf('/path/to/hdf5.h5'); Sets filename property + % h = OCPHdf(RAMONVolume, '/path/to/hdf5.h5'); Convert volume to + % HDF5 specified by filename + % h = OCPHdf(OCPQuery,'/path/to/hdf5.h5'); Creates object based + % on existing HDF5 file and OCPQuery object + % + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties + filename = ''; + query = []; + end + + properties(SetAccess = 'private', GetAccess = 'private') + ramonType = []; + end + + methods( Access = public ) + %% Methods - General + function this = OCPHdf(varargin) + % Constructor + switch nargin + case 0 + case 1 + % Handle "Overloading" + if strcmpi(class(varargin{1}),'RAMONGeneric') ||... + strcmpi(class(varargin{1}),'RAMONSeed') ||... + strcmpi(class(varargin{1}),'RAMONSynapse') ||... + strcmpi(class(varargin{1}),'RAMONSegment') ||... + strcmpi(class(varargin{1}),'RAMONNeuron') ||... + strcmpi(class(varargin{1}),'RAMONOrganelle') ||... + strcmpi(class(varargin{1}),'RAMONVolume') + % Ramon object passed in. Convert to HDF5 and set + % filename. + + this.setFilename(this.Ramon2Hdf(varargin{1})); + + elseif isa(varargin{1},'OCPQuery') + % Just passing in the path to a query object + this.setQuery(varargin{1}); + elseif isa(varargin{1},'char') + % Just passing in the path to an existing hdf5 file + this.setFilename(char(varargin{1})); + elseif isa(varargin{1},'cell') + % Passing in a cell array of RAMON objects that + % need converted + this.setFilename(this.Ramon2Hdf(varargin{1})); + else + ex = MException('OCPHdf:ArgError','Unsupported argument in constructor'); + throw(ex); + end + case 2 + if strcmpi(class(varargin{1}),'RAMONVolume') + % Passing in a RAMONVolume and a path to save the + % hdf5 file. + this.setFilename(this.Ramon2Hdf(varargin{1},varargin{2})); + else + % Just passing in the path to an existing hdf5 file + % along with it's associated OCPQuery object + this.setFilename(char(varargin{1})); + this.setQuery(varargin{2}); + end + otherwise + ex = MException('OCPHdf:ArgError','Invalid number of arguments in constructor'); + throw(ex); + end + end + + function this = setFilename(this, file) + this.filename = file; + end + function this = setQuery(this, q) + this.query = q; + end + + %% Methods - HDF5 to MATLAB Structure + function h5Struct = toStruct(this) + % Method to load an HDF5 file into a matlab structure + % + % ocph5 = OCPhdf('/path/to/file.h5') + % data = ocph5.toStruct(); + % + % Parameters + % ---------- + % + % file + % Name of the file to load data from + % path : optional + % Path to the part of the HDF5 file to load + + + loc = H5F.open(this.filename, 'H5F_ACC_RDONLY', 'H5P_DEFAULT'); + try + h5Struct = this.loadH5Recursive(loc); + H5F.close(loc); + catch exc + H5F.close(loc); + rethrow(exc); + end + + end + + %% Methods - HDF5 to RAMONVolume type + function volumeObj = toRAMONVolume(this) + % Method to take a cutout and put it into a RAMON volume + % object. This is useful for viewing and compatibility between + % programs. Also it provides additional information about the + % cutout compared to a simple MATLAB matrix. + + if isempty(this.filename) + ex = MException('OCPHdf:FilenameMissing','HDF5 file required to create RAMONVolume'); + throw(ex); + end + + if isempty(this.query) + ex = MException('OCPHdf:QueryMissing','Query object required to create RAMONVolume'); + throw(ex); + end + + % Get data type + data_type = h5read(this.filename,'/DATATYPE'); + + % Based on datatype grab the data + switch data_type + % image8,anno32,prob32,bitmask,anno64,image16 + % single channel data + case {1,2,5,6,7,8} + % Create object + volumeObj = RAMONVolume(); + + % Load data + cube = h5read(this.filename,'/CUTOUT'); + cube = permute(cube, [2 1 3]); + n = 'Cutout'; + + % Populate object + volumeObj = volumeObj.setCutout(cube); + volumeObj = volumeObj.setXyzOffset([this.query.xRange(1) this.query.yRange(1) this.query.zRange(1)]); + volumeObj = volumeObj.setResolution(this.query.resolution); + volumeObj = volumeObj.setName(n); + + case {3,4} + % channels16,channels8 + % multichannel data + volumeObj = cell(1,length(this.query.channels)); + + % create cell array of volume objects! + for ii = 1:length(this.query.channels) + v = RAMONVolume(); + + ch = strrep(this.query.channels{ii},'__','-'); + cube = h5read(this.filename,['/CUTOUT/' ch]); + cube = permute(cube, [2 1 3]); + + v = v.setCutout(cube); + v = v.setXyzOffset([this.query.xRange(1) this.query.yRange(1) this.query.zRange(1)]); + v = v.setResolution(this.query.resolution); + v = v.setName(ch); + volumeObj{ii} = v; + end + + case {9,10} + % rgba32,rgba64 + % Create object + volumeObj = RAMONVolume(); + + % Load data + cube = h5read(this.filename,'/CUTOUT'); + + % handle RGBA data + cube = permute(cube, [2 1 3 4]); + n = 'RGBA32 Cutout'; + + % Populate object + volumeObj = volumeObj.setCutout(cube); + volumeObj = volumeObj.setXyzOffset([this.query.xRange(1) this.query.yRange(1) this.query.zRange(1)]); + volumeObj = volumeObj.setResolution(this.query.resolution); + volumeObj = volumeObj.setName(n); + + otherwise + error('OCPHdf:toRAMONVolume','Unsupported datatype: %d',datatype); + end + end + + %% Methods - HDF5 to MATLAB matrix + function data = toMatrix(this) + % Method to take a cutout and put it into a MATLAB matrix. + % Only database cutouts (imageCutout, annoCutout) support this + + if isempty(this.filename) + ex = MException('OCPHdf:FilenameMissing','HDF5 file required to create a matrix'); + throw(ex); + end + + % Get the thing to read + info = h5info(this.filename); + + if length(info.Datasets) == 1 + dname = sprintf('/%s',info.Datasets.Name); + data = h5read(this.filename,dname); + data = permute(data, [2 1 3]); + elseif length(info.Datasets) > 1 + for ii = 1:length(info.Datasets) + dname = sprintf('/%s',info.Datasets.Name); + tdata = h5read(this.filename,dname); + data{ii} = permute(tdata, [2 1 3]); + end + else + data = []; + end + end + + %% Methods - HDF5 to RAMONObject + function ramonObjOut = toRAMONObject(this,qObj) + + % If you pass in a eOCPQueryType instead of an actual query object + % just build what you need. + if ~(strcmpi(this.ramonType,'RAMONSeed') || strcmpi(this.ramonType,'RAMONNeuron') || ... + strcmpi(this.ramonType,'RAMONBase')) + if exist('qObj','var') + if isa(qObj,'eOCPQueryType') + qObj = OCPQuery(qObj); + end + else + ex = MException('OCPHdf:DataFormatMissing',... + 'You must specify the format of the voxel data as an eOCPQueryType - ie. RAMONDense'); + throw(ex); + end + end + + % Get Object Information + info = h5info(this.filename); + numObjects = length(info.Groups); + if numObjects == 0 + ex = MException('OCPHdf:InvalidFormat','HDF5 File format error. Cannot parse object.'); + throw(ex); + end + if numObjects == 1 + ramonObjOut = []; + else + ramonObjOut = cell(1,numObjects); + end + + % Loop on objects in file + for ii = 1:numObjects + % Get ID + rootGroup = info.Groups(ii).Name; + + % Get annotation type + annotationType = h5read(this.filename,sprintf('%s/ANNOTATION_TYPE',rootGroup)); + + switch annotationType + case uint32(eRAMONAnnoType.generic) + % Create Generic + ramonObj = RAMONGeneric(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + % Populate Voxel Data Fields + if qObj.type ~= eOCPQueryType.RAMONMetaOnly; + ramonObj = OCPHdf.getVoxelData(this.filename, ramonObj, rootGroup, qObj); + end + + case uint32(eRAMONAnnoType.volume) + % Create Generic + ramonObj = RAMONVolume(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + % Populate Voxel Data Fields + ramonObj = OCPHdf.getVoxelData(this.filename, ramonObj, rootGroup, qObj); + + case uint32(eRAMONAnnoType.seed) + % Create seed + ramonObj = RAMONSeed(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Position Field + pos = h5read(this.filename,sprintf('%s/METADATA/POSITION',rootGroup)); + ramonObj.setPosition(double(pos')); + + % Populate Cube Location Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/CUBE_LOCATION',rootGroup), eRAMONCubeOrientation.centered); + ramonObj.setCubeOrientation(double(data)); + + % Populate Parent Seed Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/PARENT',rootGroup), []); + ramonObj.setParentSeed(double(data)); + + % Populate Source Entity Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SOURCE',rootGroup), []); + ramonObj.setSourceEntity(double(data)); + + % Populate ID, Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + case uint32(eRAMONAnnoType.synapse) + % Create synapse + ramonObj = RAMONSynapse(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + % Populate Voxel Data Fields + if qObj.type ~= eOCPQueryType.RAMONMetaOnly; + ramonObj = OCPHdf.getVoxelData(this.filename, ramonObj, rootGroup, qObj); + end + + % Populate Synapse Type Field + type = h5read(this.filename,sprintf('%s/METADATA/SYNAPSE_TYPE',rootGroup)); + ramonObj.setSynapseType(double(type)); + + % Populate Seeds Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SEEDS',rootGroup), []); + ramonObj.setSeeds(double(data')); + + % Populate Segments Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SEGMENTS',rootGroup), []); + data = data'; + for gg = 1:size(data,1) + ramonObj.addSegment(data(gg,1),data(gg,2)); + end + + % Populate Weight Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/WEIGHT',rootGroup), []); + ramonObj.setWeight(double(data)); + + case uint32(eRAMONAnnoType.segment) + % Create synapse + ramonObj = RAMONSegment(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Confidence, Status, kv pairs, and author + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + % Populate Voxel Data Fields + if qObj.type ~= eOCPQueryType.RAMONMetaOnly; + ramonObj = OCPHdf.getVoxelData(this.filename, ramonObj, rootGroup, qObj); + end + + % Populate Synapse Type Field + type = h5read(this.filename,sprintf('%s/METADATA/SEGMENTCLASS',rootGroup)); + ramonObj.setClass(double(type)); + + % Populate Seeds Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SYNAPSES',rootGroup), []); + ramonObj.setSynapses(double(data')); + + % Populate Segments Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/ORGANELLES',rootGroup), []); + ramonObj.setOrganelles(double(data')); + + % Populate Neuron Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/NEURON',rootGroup), []); + ramonObj.setNeuron(double(data)); + + % Populate Weight Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/PARENTSEED',rootGroup), []); + ramonObj.setParentSeed(double(data)); + case uint32(eRAMONAnnoType.neuron) + % Create neuron + ramonObj = RAMONNeuron(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Segment Field (Optional) + seg = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SEGMENTS',rootGroup), []); + ramonObj.setSegments(double(seg')); + + % Populate ID, Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + case uint32(eRAMONAnnoType.organelle) + % Create synapse + ramonObj = RAMONOrganelle(); + + % Set ID + ramonObj.setId(str2double(rootGroup(2:end))); + + % Populate Confidence, Status, and kv pairs + ramonObj = OCPHdf.getCommonMetadata(this.filename,ramonObj,rootGroup); + + % Populate Voxel Data Fields + if qObj.type ~= eOCPQueryType.RAMONMetaOnly; + ramonObj = OCPHdf.getVoxelData(this.filename, ramonObj, rootGroup, qObj); + end + + % Populate Organelle Class Field + type = h5read(this.filename,sprintf('%s/METADATA/ORGANELLECLASS',rootGroup)); + ramonObj.setClass(double(type)); + + % Populate Seeds Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/SEEDS',rootGroup), []); + ramonObj.setSeeds(double(data')); + + % Populate Parent Seed Field + data = OCPHdf.getOptionalField(this.filename, sprintf('%s/METADATA/PARENTSEED',rootGroup), []); + ramonObj.setParentSeed(double(data)); + + + otherwise + % Not a supported RAMON type. + ex = MException('OCPHdf:UnsupportedType','Attempting to convert an unsupported RAMON annotation type: %d',annotationType); + throw(ex); + + end + + if numObjects == 1 + ramonObjOut = ramonObj; + else + ramonObjOut{ii} = ramonObj; %#ok<*AGROW> + end + end + end + + %% Methods - Query to Cutout HDF file + function h5file = toCutoutHDF(this) + + % Create HDF5 file + tempLocation = getenv('PIPELINE_TEMP_DIR'); + if isempty(tempLocation) + [path, name, ~] = fileparts(tempname); + h5file = fullfile(path,sprintf('OCPHdf_Cutout_%s.h5',name)); + else + [~, name, ~] = fileparts(tempname); + h5file = fullfile(tempLocation,sprintf('OCPHdf_Cutout_%s.h5',name)); + end + + h5Handle = H5F.create(h5file, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + + % Write Resolution + space = H5S.create_simple(1,1,1); + dset = H5D.create(h5Handle, '/RESOLUTION', 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(this.query.resolution)); + H5D.close (dset); + H5S.close (space); + + % Write xyzOffset + xyzOffset = [this.query.xRange(1) this.query.yRange(1) this.query.zRange(1)]; + dims = size(xyzOffset); + rankVal = 1; + dims = dims(2); + space = H5S.create_simple(rankVal,fliplr(dims),fliplr(dims)); + dset = H5D.create(h5Handle, '/XYZOFFSET', 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(xyzOffset)'); + H5D.close (dset); + H5S.close (space); + + % Write CUTOUTSIZE + cutoutSize = [this.query.xRange(2) - this.query.xRange(1),... + this.query.yRange(2) - this.query.yRange(1),... + this.query.zRange(2) - this.query.zRange(1)]; + space = H5S.create_simple(rankVal,fliplr(dims),fliplr(dims)); + dset = H5D.create(h5Handle, '/CUTOUTSIZE', 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(cutoutSize)'); + H5D.close (dset); + H5S.close (space); + + H5F.close(h5Handle); + + % If done set filename in class + this.setFilename(h5file); + + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%% Private Methods %%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + methods( Access = private ) + + %% Private Methods - Ramon2Hdf + function hdfFile = Ramon2Hdf(this,ramonObjIn,outputFilename) + %Ramon2Hdf Function to convert RAMON objects to HDF5 files + % RAMON objects that support conversion to hdf5 objects have toHDF + % methods that invoke this function + % + % Currently supports converting: + % RAMONGeneric + % RAMONSeed + % RAMONSynapse + % RAMONSegment + % RAMONNeuron + % RAMONVolume + %%%%% + + % if outputFilename is not specified, create temp filename + if exist('outputFilename','var') + hdfFile = outputFilename; + else + tempLocation = getenv('PIPELINE_TEMP_DIR'); + if isempty(tempLocation) + [path, name, ~] = fileparts(tempname); + hdfFile = fullfile(path,sprintf('OCPHdf_RAMON_%s.h5',name)); + else + [~, name, ~] = fileparts(tempname); + hdfFile = fullfile(tempLocation,sprintf('OCPHdf_RAMON_%s.h5',name)); + end + end + + if isa(ramonObjIn,'cell') + % Batch Upload! + this.ramonType = 'batch'; + else + % Set type + ramonObj = ramonObjIn; + this.ramonType = class(ramonObj); + end + + for ii = 1:length(ramonObjIn) + if isa(ramonObjIn,'cell') + % Batch Upload! + % Check to see if IDs are set or if OCP should assign + empty_ids = cellfun(@(x) isempty(x.id),ramonObjIn); + if sum(empty_ids) > 0 && sum(empty_ids) ~= length(empty_ids) + % There a MIX of assigned and non-assigned IDs. + % Current we don't support this in a single batch. + error('OCPHdf:MixedIDBatch',... + 'You currently cannot mix a batch upload with pre-assigned and empty ids'); + end + + if sum(empty_ids) == 0 + % Pre-assigned IDs + ramonId = ramonObjIn{ii}.id; + else + % Empty ids! + % make id decimal so batch interface kicks in + ramonId = str2double(sprintf('1.%04d',ii)); + end + ramonObj = ramonObjIn{ii}; + else + % Single annotation + if isempty(ramonObj.id) + ramonObj.setId(0); + end + ramonId = ramonObj.id; + end + + % Switch off object type and build hdf5 file + switch class(ramonObj) + case 'RAMONGeneric' + % Init file - write ID and Annotation Type + + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.generic); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.generic); + end + + % Add Voxel Data + OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + case 'RAMONVolume' + % This is a block style upload of DB data. + % Currently Probabilty Maps and 32bit Annotations + % are supported + + % No batch interface for block style uploads! + if isa(ramonObjIn,'cell') + error('OCPHdf:BatchNotSupported','Batch uploads are not supported with block style uploading'); + end + + % Add Voxel Data + if isempty(ramonObj.data) + error('OCPHdf:NoData','RAMONVolume is empty. No data to upload.'); + end + + if isempty(ramonObj.dataType) + % trying to simply save a RAMONVolume object + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.volume); + + % Add Voxel Data + OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + else + % Trying to block style upload + switch ramonObj.dataType + case eRAMONDataType.prob32 + % Create HDF5 file + h5Handle = H5F.create(hdfFile, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + % Probability Map + OCPHdf.addBlockData(h5Handle, single(ramonObj.data), 'H5T_IEEE_F32LE','H5T_IEEE_F32LE'); + + case eRAMONDataType.anno32 + % Create HDF5 file + h5Handle = H5F.create(hdfFile, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + % 32Bit annotations + OCPHdf.addBlockData(h5Handle, uint32(ramonObj.data), 'H5T_STD_U32LE','H5T_NATIVE_INT'); + + case eRAMONDataType.image16 + % Create HDF5 file + h5Handle = H5F.create(hdfFile, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + % 32Bit annotations + OCPHdf.addBlockData(h5Handle, uint16(ramonObj.data), 'H5T_STD_U16LE','H5T_STD_U16LE'); + + + case eRAMONDataType.image8 + % Create HDF5 file + h5Handle = H5F.create(hdfFile, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + % 32Bit annotations + OCPHdf.addBlockData(h5Handle, uint8(ramonObj.data), 'H5T_STD_U8LE','H5T_STD_U8LE'); + + +% case eRAMONDataType.image8 +% % Trying to save 8bit image data +% %OCPHdf.addBlockData(h5Handle, uint8(ramonObj.data), 'H5T_STD_U8LE','H5T_STD_U8LE'); +% +% % trying to simply save a RAMONVolume object +% h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.volume); +% +% % Add Voxel Data +% OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) +% +% % Write Confidence, Status, KVPairs, author +% OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + + otherwise + error('OCPHdf:UnsupportedUploadType','Unsupported type for block style uploads'); + end + end + + case 'RAMONSeed' + % Init file - write ID and Annotation Type + + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.seed); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.seed); + end + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId); + + % Write Position Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'POSITION', uint32(ramonObj.position),2); + + % Write Cube Location Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'CUBE_LOCATION', uint32(ramonObj.cubeOrientation)); + + % Write Parent Seed Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'PARENT', uint32(ramonObj.parentSeed)); + + % Write Source Entity Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SOURCE', uint32(ramonObj.sourceEntity)); + + + case 'RAMONSynapse' + % Init file - write ID and Annotation Type + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.synapse); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.synapse); + end + + % Add Voxel Data + OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + + % Write Seed Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SEEDS', uint32(ramonObj.seeds),2); + + % Write Cube Location Metadata + segMat = []; + keys = ramonObj.segments.keys; + values = ramonObj.segments.values; + for gg = 1:ramonObj.segments.Count + segMat = cat(1,segMat,[keys{gg} values{gg}]); + end + %if gg ~= 1 + % segMat = segMat'; + %end + OCPHdf.addMetadata(h5Handle, ramonId, 'SEGMENTS', uint32(segMat)); + + % Write Parent Synapse Type Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SYNAPSE_TYPE', uint32(ramonObj.synapseType)); + + % Write Synape Weight Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'WEIGHT', double(ramonObj.weight)); + + + case 'RAMONSegment' + % Init file - write ID and Annotation Type + + + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.segment); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.segment); + end + + % Add Voxel Data + OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + + % Write the class of Segemnt Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SEGMENTCLASS', uint32(ramonObj.class)); + + % Write Synapse ID Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SYNAPSES', uint32(ramonObj.synapses),2); + + % Write Organelle ID Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'ORGANELLES', uint32(ramonObj.organelles),2); + + % Write the Neuron Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'NEURON', uint32(ramonObj.neuron)); + + % Write the Parent Seed Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'PARENTSEED', uint32(ramonObj.parentSeed)); + + + case 'RAMONNeuron' + % Init file - write ID and Annotation Type + + + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.neuron); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.neuron); + end + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + + % Write Segment Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SEGMENTS', uint32(ramonObj.segments),2); + + case 'RAMONOrganelle' + % Init file - write ID and Annotation Type + if ii == 1 + h5Handle = OCPHdf.initH5Obj(hdfFile, ramonId, eRAMONAnnoType.organelle); + else + OCPHdf.addDataset(h5Handle, ramonId, eRAMONAnnoType.synapse); + end + + % Add Voxel Data + OCPHdf.addVoxelData(h5Handle, ramonObj,ramonId) + + % Write Confidence, Status, KVPairs, author + OCPHdf.writeCommonMetadata(h5Handle,ramonObj,ramonId) + + % Write Parent Organelle Class Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'ORGANELLECLASS', uint32(ramonObj.class)); + + % Write the Parent Seed Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'PARENTSEED', uint32(ramonObj.parentSeed)); + + % Write Seed Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'SEEDS', uint32(ramonObj.seeds),2); + + otherwise + % Not a supported RAMON type. + ex = MException('OCPHdf:InvalidInputType','Attempting to convert an unsupported object type. Check input to be of a supported RAMON type.'); + throw(ex); + + end + end + + % Close file handle + H5F.close(h5Handle); + + end + end + + + methods(Static, Access = private ) + %% Private Methods - Ramon2Hdf Helpers + % Method to initialize HDF5 and add base fields + function h5Handle = initH5Obj(filename, id, type) + % Create HDF5 file + h5Handle = H5F.create(filename, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT'); + + % Create dataspace. + space = H5S.create_simple(1,1,1); + + % Create Group for ID + groupM = H5G.create (h5Handle, sprintf('/%d',id), 'H5P_DEFAULT', 'H5P_DEFAULT', 'H5P_DEFAULT'); + H5G.close(groupM); + + % Write Annotation Type + dset = H5D.create(h5Handle, sprintf('/%d/ANNOTATION_TYPE',id), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(type)); + + + H5D.close (dset); + H5S.close (space); + end + + function addDataset(h5Handle, id, type) + % Create dataspace. + space = H5S.create_simple(1,1,1); + + % Create Group for ID + groupM = H5G.create(h5Handle, sprintf('/%d',id), 'H5P_DEFAULT', 'H5P_DEFAULT', 'H5P_DEFAULT'); + H5G.close(groupM); + + % Write Annotation Type + dset = H5D.create(h5Handle, sprintf('/%d/ANNOTATION_TYPE',id), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(type)); + + H5D.close (dset); + H5S.close (space); + end + + % Method to write all "common" metadata including creating the group + function writeCommonMetadata(h5Handle,ramonObj,ramonId) + + groupM = H5G.create (h5Handle, sprintf('/%d/METADATA',ramonId), 'H5P_DEFAULT', 'H5P_DEFAULT', 'H5P_DEFAULT'); + H5G.close(groupM); + + % Write Author Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'AUTHOR', ramonObj.author); + + % Write Confidence Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'CONFIDENCE', ramonObj.confidence); + + % Write Status Metadata + OCPHdf.addMetadata(h5Handle, ramonId, 'STATUS', uint32(ramonObj.status)); + + % Write KVPairs Metadata + keys = ramonObj.getMetadataKeys(); + kvPairs = ''; + for ii = 1:size(keys,2) + value = ramonObj.getMetadataValue(keys{ii}); + if ~ischar(value) + value = num2str(value); + end + kvPairs = sprintf('%s%s,%s\r\n',kvPairs,keys{ii},value); + end + if ~isempty(kvPairs) + kvPairs = strtrim(kvPairs); + end + OCPHdf.addMetadata(h5Handle, ramonId, 'KVPAIRS', kvPairs); + end + + % Method for adding voxel data + function addVoxelData(h5Handle, ramonObj,ramonId) + if isempty(ramonObj.data) + % if no value don't add field + return; + end + + switch ramonObj.dataFormat + case eRAMONDataFormat.dense + % Write xyzOffset + dims = size(ramonObj.xyzOffset); + rankVal = 1; + dims = dims(2); + + space = H5S.create_simple(rankVal,fliplr(dims),fliplr(dims)); + + dset = H5D.create(h5Handle, sprintf('/%d/XYZOFFSET',ramonId), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(ramonObj.xyzOffset)'); + H5D.close (dset); + H5S.close (space); + + + % Write Voxel Data + data = permute(ramonObj.data,[2 1 3]); + rankVal = ndims(data); + dims = size(data); + + %TODO: Figure out if this is the right solution. Temporarily + %force 3D + if rankVal ~= 3 + rankVal = 3; + dims(3) = 1; + end + + space = H5S.create_simple(rankVal,fliplr(dims),[]); + + % Set Chunk sizes + for ii = [256,128,64,32,16,8,4,1] + if (dims(1) >= ii) + x_chunk = ii; + break; + end + end + for ii = [256,128,64,32,16,8,4,1] + if (dims(2) >= ii) + y_chunk = ii; + break; + end + end + for ii = [16,8,4,1] + if (dims(3) >= ii) + z_chunk = ii; + break; + end + end + + % Set Compression (if available) + zipped = false; + avail = H5Z.filter_avail('H5Z_FILTER_DEFLATE'); + if ~avail + error ('gzip filter not available.'); + else + + % Check that it can be used. + H5Z_FILTER_CONFIG_ENCODE_ENABLED = H5ML.get_constant_value('H5Z_FILTER_CONFIG_ENCODE_ENABLED'); + H5Z_FILTER_CONFIG_DECODE_ENABLED = H5ML.get_constant_value('H5Z_FILTER_CONFIG_DECODE_ENABLED'); + filter_info = H5Z.get_filter_info('H5Z_FILTER_DEFLATE'); + if ( ~bitand(filter_info,H5Z_FILTER_CONFIG_ENCODE_ENABLED) || ... + ~bitand(filter_info,H5Z_FILTER_CONFIG_DECODE_ENABLED) ) + error ('gzip filter not available for encoding and decoding.'); + else + % Good to go! Compress GZIP + dcpl = H5P.create('H5P_DATASET_CREATE'); + H5P.set_deflate(dcpl, 9); + H5P.set_chunk (dcpl, fliplr([x_chunk,y_chunk,z_chunk])); + zipped = true; + end + end + + + % Write unsigned int labeled voxel data + dset = H5D.create(h5Handle, sprintf('/%d/CUTOUT',ramonId), 'H5T_STD_U32LE', space, dcpl); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(data)); + H5D.close (dset); + H5S.close (space); + if zipped == true; + H5P.close(dcpl); + end + + + % Write Resolution + rankVal = 1; + dims = 1; + + space = H5S.create_simple(rankVal,fliplr(dims),fliplr(dims)); + + dset = H5D.create(h5Handle, sprintf('/%d/RESOLUTION',ramonId), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(ramonObj.resolution)'); + H5D.close (dset); + H5S.close (space); + + case eRAMONDataFormat.voxelList + % Set voxel list + % Write Voxel List + dims = size(ramonObj.data); + rankVal = ndims(ramonObj.data); + + space = H5S.create_simple(rankVal,dims,[]); + + % Write unsigned int labeled voxel data + dset = H5D.create(h5Handle, sprintf('/%d/VOXELS',ramonId), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(ramonObj.data')); + H5D.close (dset); + H5S.close (space); + + + % Write Resolution + rankVal = 1; + dims = 1; + + space = H5S.create_simple(rankVal,fliplr(dims),fliplr(dims)); + + dset = H5D.create(h5Handle, sprintf('/%d/RESOLUTION',ramonId), 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', uint32(ramonObj.resolution')); + H5D.close (dset); + H5S.close (space); + + otherwise + % Not a supported voxel method + ex = MException('OCPHdf:UnsupportedDataFormat','Unsupported data format when creating HDF5 files: %s',char(qObj.type)); + throw(ex); + end + end + + + % Method for adding voxel data + function addBlockData(h5Handle, data, createType,writeType) + + % Write Voxel Data + data = permute(data,[2 1 3]); + rankVal = ndims(data); + dims = size(data); + + %TODO: Figure out if this is the right solution. Temporarily + %force 3D + if rankVal ~= 3 + rankVal = 3; + dims(3) = 1; + end + + space = H5S.create_simple(rankVal,fliplr(dims),[]); + + % Set Chunk sizes + for ii = [256,128,64,32,16,8,4,1] + if (dims(1) >= ii) + x_chunk = ii; + break; + end + end + for ii = [256,128,64,32,16,8,4,1] + if (dims(2) >= ii) + y_chunk = ii; + break; + end + end + for ii = [16,8,4,1] + if (dims(3) >= ii) + z_chunk = ii; + break; + end + end + + % Set Compression (if available) + zipped = false; + avail = H5Z.filter_avail('H5Z_FILTER_DEFLATE'); + if ~avail + error ('gzip filter not available.'); + else + + % Check that it can be used. + H5Z_FILTER_CONFIG_ENCODE_ENABLED = H5ML.get_constant_value('H5Z_FILTER_CONFIG_ENCODE_ENABLED'); + H5Z_FILTER_CONFIG_DECODE_ENABLED = H5ML.get_constant_value('H5Z_FILTER_CONFIG_DECODE_ENABLED'); + filter_info = H5Z.get_filter_info('H5Z_FILTER_DEFLATE'); + if ( ~bitand(filter_info,H5Z_FILTER_CONFIG_ENCODE_ENABLED) || ... + ~bitand(filter_info,H5Z_FILTER_CONFIG_DECODE_ENABLED) ) + error('gzip filter not available for encoding and decoding.'); + else + % Good to go! Compress GZIP + dcpl = H5P.create('H5P_DATASET_CREATE'); + H5P.set_deflate(dcpl, 9); + H5P.set_chunk (dcpl, fliplr([x_chunk,y_chunk,z_chunk])); + zipped = true; + end + end + + + % Write unsigned int labeled voxel data + dset = H5D.create(h5Handle, '/CUTOUT', createType, space, dcpl); + H5D.write(dset, writeType, 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data); + H5D.close(dset); + H5S.close(space); + if zipped == true + H5P.close(dcpl); + end + end + + + + % Method to write metadata fields + function addMetadata(h5Handle, id, fieldName, data, forceSingleDim) + + if ~exist('forceSingleDim','var') + forceSingleDim = 0; + end + + switch class(data) + case 'double' + if isempty(data) + % if no value don't add field + return; + end + + % Create dataspace based on dimensions of data + dims = size(data); + if isscalar(data) + rankVal = 1; + dims = 1; + forceSingleDim = 0; % it's scalar so forced already + else + rankVal = ndims(data); + end + + % Special 1-D case + if forceSingleDim == 1 + rankVal = 1; + dims = dims(1); + end + if forceSingleDim == 2 + rankVal = 1; + dims = dims(2); + end + + space = H5S.create_simple(rankVal,dims,dims); + + % Write 64-bit float metadata + dataSetName = sprintf('/%d/METADATA/%s',id,fieldName); + dset = H5D.create(h5Handle, dataSetName, 'H5T_IEEE_F64LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_DOUBLE', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data'); + + H5D.close (dset); + H5S.close (space); + + case 'char' + if isempty(data) + % if no value don't add field + return; + end + + dims = size(data); + dataSetName = sprintf('/%d/METADATA/%s',id,fieldName); + + % Create file and memory datatypes. MATLAB strings do not have \0's. + filetype = H5T.copy ('H5T_FORTRAN_S1'); + H5T.set_size(filetype, dims(2)); + memtype = H5T.copy('H5T_C_S1'); + H5T.set_size(memtype, dims(2)); + + % Create dataspace. Setting maximum size to [] sets the maximum + % size to be the current size. + space = H5S.create_simple(1,1, []); + + % Create the dataset and write the string data to it. + dset = H5D.create (h5Handle, dataSetName, filetype, space, 'H5P_DEFAULT'); + % Transpose the data to match the layout in the H5 file to match C + % generated H5 file. + H5D.write (dset, memtype, 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data'); + + % Close and release resources. + H5D.close (dset); + H5S.close (space); + H5T.close (filetype); + H5T.close (memtype); + + case 'uint32' + % Create dataspace based on dimensions of data + if isempty(data) + % if no value don't add field + return; + end + dims = size(data); + if isscalar(data) + rankVal = 1; + dims = 1; + forceSingleDim = 0; % it's scalar so forced already + else + rankVal = ndims(data); + end + + % Special 1-D case + if forceSingleDim == 1 + rankVal = 1; + dims = dims(1); + end + if forceSingleDim == 2 + rankVal = 1; + dims = dims(2); + end + + space = H5S.create_simple(rankVal,dims,dims); + + % Write unsigned int metadata + dataSetName = sprintf('/%d/METADATA/%s',id,fieldName); + dset = H5D.create(h5Handle, dataSetName, 'H5T_STD_U32LE', space, 'H5P_DEFAULT'); + H5D.write (dset, 'H5T_NATIVE_INT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', data'); + + + H5D.close (dset); + H5S.close (space); + + otherwise + ex = MException('OCPHdf:UnsupportedMetadataType','Attempting to write an unsupported metadata type: %s',class(data)); + throw(ex); + end + end + + %% Private Methods - toStruct helpers + function data = loadH5Recursive(loc) + % Original Source Author: Pauli Virtanen + % Script was in the Public Domain with No warranty. + % Heavily Modified by Dean Kleissas + + % Output Structure + data = struct(); + + % Get number of objects in file + numObjs = H5G.get_num_objs(loc); + + % Load groups and datasets recursively + for ii=0:numObjs-1, + objtype = H5G.get_objtype_by_idx(loc, ii); + objname = H5G.get_objname_by_idx(loc, ii); + + % objtype index factory according to matlab version + v = ver('MATLAB'); + if datenum(v.Date)>datenum('04-Aug-2008') + objtype = objtype+1; + end + + if objtype == 1 + % Group + name = regexprep(objname, '.*/', ''); + + group_loc = H5G.open(loc, name); + try + subData = OCPHdf.loadH5Recursive(group_loc); + H5G.close(group_loc); + catch exc + H5G.close(group_loc); + rethrow(exc); + end + + % Put whatever was returned into struct + data.(name) = subData; + + elseif objtype == 2 + % Dataset + name = regexprep(objname, '.*/', ''); + + if ii == 0 && sum(isstrprop(name,'digit')) == length(name) + % You are getting a group full of numbered + % values. Use a map! + data = containers.Map('KeyType', 'double','ValueType', 'any'); + end + + dataset_loc = H5D.open(loc, name); + try + subData = H5D.read(dataset_loc, ... + 'H5ML_DEFAULT', 'H5S_ALL','H5S_ALL','H5P_DEFAULT'); + + H5D.close(dataset_loc); + catch exc + H5D.close(dataset_loc); + rethrow(exc); + end + + subData = OCPHdf.fixData(subData); + + if sum(isstrprop(name,'digit')) == length(name) + subData = double(subData); + data(str2double(name)) = subData; + else + % if name has a hyphen in it we need to replace so + % you can store it as a field in a struct. Warn + % when you are doing this. + if ~isempty(strfind(name,'-')) + % Fix hyphen + name=strrep(name,'-','__'); + warning('OCPHdf:BadFieldChar',... + 'There is a hyphen in an HDF5 field (most likely a channel name). The hyphen (-) has been replaced with two underscores (__). This will automatically be handled by the API during interfacing with OCP, but be aware the channel name listed by OCP.getChannelList will not match the server. Disable OCPHdf:BadFieldChar to suppress this warning'); + end + data.(name) = subData; + end + end + end + end + + function data = fixData(data) + % Original Source Author: Pauli Virtanen + % Script was in the Public Domain with No warranty. + + % Fix some common types of data to more friendly form. + if isstruct(data) + fields = fieldnames(data); + if length(fields) == 2 && strcmp(fields{1}, 'r') && strcmp(fields{2}, 'i') + if isnumeric(data.r) && isnumeric(data.i) + data = data.r + 1j*data.i; + end + end + end + + % for i=1:length(fields) + % f = data.(fields{i}); + % if ischar(f) + % f = permute(f, fliplr(1:ndims(f))); + % data.(fields{i})= f; + % end + % end + + if isnumeric(data) && ndims(data) > 1 + % permute dimensions + data = permute(data, fliplr(1:ndims(data))); + end + end + + %% Private Methods - Hdf2Ramon Helpers + % method to get voxel data fields and add to ramon object + function ramonObj = getVoxelData(HDF5File, ramonObj, rootGroup, qObj) + + switch qObj.type + case eOCPQueryType.RAMONDense + try + % If this works all cutout args were present + data = h5read(HDF5File,sprintf('%s/CUTOUT',rootGroup)); + ramonObj.setCutout(double(permute(data,[2 1 3]))); + res = h5read(HDF5File,sprintf('%s/RESOLUTION',rootGroup)); + ramonObj.setResolution(double(res)); + off = h5read(HDF5File,sprintf('%s/XYZOFFSET',rootGroup)); + ramonObj.setXyzOffset(double(off')); + catch ME %#ok + warning('OCPHdf:NoVoxelData', 'No voxel data was returned from the server.') + ramonObj.setCutout([]); + ramonObj.setResolution([]); + end + + case eOCPQueryType.RAMONVoxelList + % Set voxel list + try + data = h5read(HDF5File,sprintf('%s/VOXELS',rootGroup)); + ramonObj.setVoxelList(data'); + res = h5read(HDF5File,sprintf('%s/RESOLUTION',rootGroup)); + ramonObj.setResolution(double(res)); + catch ME %#ok + warning('OCPHdf:NoVoxelData', 'No voxel data was returned from the server.') + ramonObj.setVoxelList([]); + ramonObj.setResolution([]); + end + + + case eOCPQueryType.RAMONBoundingBox + % Set boudning box + try + data = h5read(HDF5File,sprintf('%s/XYZDIMENSION',rootGroup)); + ramonObj.setBoundingBoxSpan(data'); + off = h5read(HDF5File,sprintf('%s/XYZOFFSET',rootGroup)); + ramonObj.setXyzOffset(double(off')); + res = h5read(HDF5File,sprintf('%s/RESOLUTION',rootGroup)); + ramonObj.setResolution(double(res)); + catch ME %#ok + warning('OCPHdf:NoBoundingBox', 'No bounding box data was returned from the server.') + ramonObj.setBoundingBoxSpan([]); + ramonObj.setResolution([]); + end + + otherwise + % Not a supported voxel method + ex = MException('OCPHdf:UnsupportedDataFormat','Unsupported data format: %s',char(qObj.type)); + throw(ex); + end + end + + % Method to get common metadata and set ramon object + function ramonObj = getCommonMetadata(HDF5File, ramonObj, rootGroup) + + % Populate Author Field + author = h5read(HDF5File,sprintf('%s/METADATA/AUTHOR',rootGroup)); + ramonObj.setAuthor(author{:}); + + % Populate Status Field + status = h5read(HDF5File,sprintf('%s/METADATA/STATUS',rootGroup)); + ramonObj.setStatus(double(status)); + + % Populate Confidence Field + data = OCPHdf.getOptionalField(HDF5File, sprintf('%s/METADATA/CONFIDENCE',rootGroup), []); + ramonObj.setConfidence(double(data)); + + % Populate KVPair Field + + % get data and split into cell array + data = OCPHdf.getOptionalField(HDF5File,sprintf('%s/METADATA/KVPAIRS',rootGroup), []); + if ~isempty(data) + data = data{:}; + end + if ~isempty(data) + remain = data; + cnt = 1; + while ~isempty(remain) + [temp, remain] = strtok(remain,char(13)); %#ok + remain = strtrim(remain); + [kvPairs{cnt,1}, temp] = strtok(temp,','); %#ok + kvPairs{cnt,2} = temp(2:end); %#ok + cnt = cnt + 1; + end + + % Trim leading and trailing whitespace + kvPairs = cellfun(@strtrim,kvPairs,'UniformOutput', false); + + indToNull = find(cellfun(@isempty,kvPairs) == 1); + if ~isempty(indToNull) + kvPairs{indToNull} = []; + end + + % Group into pairs, if a numerical value is stored as a value it is + % converted to a double. + for ii = 1:size(kvPairs,1) + value = kvPairs{ii,2}; + if (sum(isstrprop(value, 'digit')) == length(value)) && ~isempty(value) + value = str2double(value); + end + kvPairs{ii,2} = value; + end + + % Add to object + for ii = 1:size(kvPairs,1) + ramonObj.addDynamicMetadata(kvPairs{ii,1},kvPairs{ii,2}); + end + else + ramonObj.clearDynamicMetadata(); + end + end + + + % method to get and optional field's data + function data = getOptionalField(HDF5File, dataset, defaultVal) + try + data = h5read(HDF5File,dataset); + catch ME + if strcmpi(ME.identifier,'MATLAB:imagesci:h5read:datasetDoesNotExist') || ... + strcmpi(ME.identifier,'MATLAB:imagesci:h5read:libraryError') + % second catch condition is for R2013 support + % Optional field not populated + data = defaultVal; + else + rethrow(ME); + end + end + end + + + end +end + diff --git a/api/matlab/ocp/OCPNetwork.m b/api/matlab/ocp/OCPNetwork.m new file mode 100644 index 0000000..02d2465 --- /dev/null +++ b/api/matlab/ocp/OCPNetwork.m @@ -0,0 +1,383 @@ +classdef OCPNetwork < handle + %OCPNetwork ************************************************ + % Provides network based methods for database interfacing + % + % Usage: + % + % ocpNet = OCPNetwork(); Creates object + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + %% Properties + % Location to store server error pages. Temp location automatically created if empty. + errorPageLocation = []; + end + + properties(SetAccess = 'private', GetAccess = 'private') + %% Java Network Class + % Java class provides the ability to post HDF5 files and better + % error handling than matlab can provide natively + jUrl = []; + disposed = false; + end + + methods( Access = public ) + %% Methods - General + function this = OCPNetwork(varargin) + % This class handles network communication. You may need to + % change the "reliableUrl" address. It is used to verify network connectivity + % and may not work if you are running on a LAN without + % internet connectivity. Change to something you know will + % resolve as long as you are connected to the network. + % + % + % this = OCPNetwork() - no semaphore (default case) + % this = OCPNetwork(String server, int port, + % String read_name, int max_permits_read, int timeout_seconds_read, + % String write_name, int max_permits_write, int timeout_seconds_write) + % + % IMPORTANT: YOU MUST HAVE A REDIS DATABASE INSTALLED AND + % RUNNING FOR THE DISTRIBUTED SEMAPHORE TO WORK. SERVER SHOULD + % POINT TO THE HOST. + % + % ex: this = OCPNetwork("darkhelmet.jhuapl.edu",3679,"readQ", + % 10,0,"writeQ",20,100); + % + % Note: + % - If timeout value is 0 the class will wait forever + % - If read and write semaphore names are the same they will + % behave as a single semaphore + + reliableUrl = 'http://www.google.com'; + + % Check internet connection + cnt = 1; + while cnt <= 5 + try + urlread(reliableUrl); + break; + + catch ME + if cnt == 5 + rethrow(ME); + end + warning('OCPNetwork:OCPNetwork','Internet connectivity check %d failed. \nError: %s',cnt,ME.message); + pause(10*cnt); + cnt = cnt + 1; + end + end + + + % Setup Java network interface class + try + % Set up network interface class + import me.openconnecto.* + switch nargin + case 0 + % No semaphore + this.jUrl = me.openconnecto.OcpUrl(); + + case 1 + % Distributed Semaphore via Network Config + + % Load default settings/server location info + if exist(fullfile(fileparts(which('cajal3d')),'api','matlab','ocp','semaphore_settings.mat'),'file') == 2 + load(fullfile(fileparts(which('cajal3d')),'api','matlab','ocp','semaphore_settings.mat')); + else + error('OCPNetwork:MissingConfig','No semaphore_settings.mat file found. Cannot connect to Redis server. Counld not create OCPNetwork instance'); + end + + try + % Set up redis client + import redis.clients.* + jedisCli = jedis.Jedis(ocp_settings.server,ocp_settings.port); + catch jErr + fprintf('%s\n',jErr.identifier); + ex = MException('OCPNetwork:JavaImportError','Could not load redis java library.'); + throw(ex); + end + + + % Check server for semaphore config + bRead = jedisCli.exists('read_semaphore'); + if bRead.booleanValue == 0 + error('OCPNetwork:MissingConfig','Server side configuration not found. Use SemaphoreTool in /tools/distributed_semaphore to configure server.'); + end + bWrite = jedisCli.exists('write_semaphore'); + if bWrite.booleanValue == 0 + error('OCPNetwork:MissingConfig','Server side configuration not found. Use SemaphoreTool in /tools/distributed_semaphore to configure server.'); + end + + % Load config + ocp_settings.read_semaphore = char(jedisCli.get('read_semaphore')); + ocp_settings.max_read_permits = str2double(jedisCli.get('max_read_permits')); + ocp_settings.read_timeout_seconds = str2double(jedisCli.get('read_timeout_seconds')); + ocp_settings.write_semaphore = char(jedisCli.get('write_semaphore')); + ocp_settings.max_write_permits = str2double(jedisCli.get('max_write_permits')); + ocp_settings.write_timeout_seconds = str2double(jedisCli.get('write_timeout_seconds')); + + % Check semaphore is setup + bRead = jedisCli.exists([ocp_settings.read_semaphore ':EXISTS']); + if bRead.booleanValue == 0 + error('OCPNetwork:NotInitialized','Read semaphore has not been initialized. Use SemaphoreTool in /tools/distributed_semaphore to configure server.'); + end + bWrite = jedisCli.exists([ocp_settings.write_semaphore ':EXISTS']); + if bWrite.booleanValue == 0 + error('OCPNetwork:NotInitialized','Write semaphore has not been initialized. Use SemaphoreTool in /tools/distributed_semaphore to configure server.'); + end + + % Construct + this.jUrl = OcpUrl(ocp_settings.server,ocp_settings.port,... + ocp_settings.read_semaphore,ocp_settings.max_read_permits,... + ocp_settings.read_timeout_seconds,... + ocp_settings.write_semaphore,ocp_settings.max_write_permits,... + ocp_settings.write_timeout_seconds); + + case 8 + % Distributed Semaphore Enabled + this.jUrl = OcpUrl(varargin{1},varargin{2},... + varargin{3},varargin{4},varargin{5},varargin{6},... + varargin{7},varargin{8}); + + otherwise + ex = MException('OCPNetwork:InvalidConstructor','Invalid params to the constructor.'); + throw(ex); + end + + this.disposed = false; + catch jErr + fprintf('%s\n',jErr.identifier); + ex = MException('OCPNetwork:JavaImportError','Could not load cajal3d.jar or create OcpUrl object. \nDepending on your OS and MATLAB version, you may need to run "setupEnvironment"\nin the tools directory to add this jar file to your static path.\n\n%s',jErr.message); + throw(ex); + end + + end + + function delete(this) + % destroy java object + if this.disposed == false + this.jUrl.dispose(); + this.jUrl = 0; + this.disposed = true; + end + end + + function this = setErrorPageLocation(this,loc) + % This method sets the path to save server errors pages to + this.errorPageLocation = loc; + end + + %% Methods - Semaphore + function num = numReadPermits(this) + num = this.jUrl.num_read_permits(); + end + + function num = numWritePermits(this) + num = this.jUrl.num_write_permits(); + end + + % Reset methods clear and reset the semaphore. These should be + % used with CAUTION! + function resetReadSemaphore(this) + this.jUrl.reset_read_semaphore(); + end + function resetWriteSemaphore(this) + this.jUrl.reset_write_semaphore(); + end + function resetSemaphores(this) + this.jUrl.reset_semaphores(); + end + + + % Select non-default database index if desired. + % Run this AFTER creating object but BEFORE a reset or lock. + function selectDatabaseIndex(this,index) + this.jUrl.select_database_index(index) + end + + %% Query with REST args, Return PNG File + function image = queryImage(this,urlStr) + try + % Get the data + responseCode = this.jUrl.read(urlStr,false); + if responseCode == 200 + image = imread(char(this.jUrl.output)); + else + % Server errored + errorMessage = sprintf('Server Response %d - %s \n Error Page: %s\n',... + responseCode, char(this.jUrl.responseMessage),... + char(this.jUrl.output),char(this.jUrl.output)); + + if ispc + %need to fix \ + errorMessage = strrep(errorMessage,'\','\\'); + end + + ex = MException('OCPNetwork:InternalServerError',errorMessage); + throw(ex); + end + + catch err1 + try + urlread('http://www.google.com'); + catch err2 + rethrow(err2); %MATLAB:urlread:ConnectionFailed + end + ex = MException('OCPNetwork:BadQuery', 'Query Failed. Internet connection OK. Check parameters.\n\nAttempted Query: %s \n\nError Message: %s\n\n',urlStr, err1.message); + throw(ex); + end + end + + %% test URL + function testUrl(this,urlStr) + try + % Get the data + responseCode = this.jUrl.read(urlStr,false); + if responseCode ~= 200 + % Server errored + errorMessage = sprintf('Server Response %d - %s \n Error Page: %s\n',... + responseCode, char(this.jUrl.responseMessage),... + char(this.jUrl.output),char(this.jUrl.output)); + + if ispc + %need to fix \ + errorMessage = strrep(errorMessage,'\','\\'); + end + + ex = MException('OCPNetwork:InternalServerError',errorMessage); + throw(ex); + end + + catch err1 + try + urlread('http://www.google.com'); + catch err2 + rethrow(err2); %MATLAB:urlread:ConnectionFailed + end + ex = MException('OCPNetwork:BadQuery', 'Query Failed. Internet connection OK. Check parameters.\n\nAttempted Query: %s \n\nError Message: %s\n\n',urlStr, err1.message); + throw(ex); + end + end + + %% Read/Write Queries + function output = read(this,urlStr,hdfFile) + % This method queries a RESTful web service to read data + % It also supports posting an HDF5 file with the request. + + if ~exist('hdfFile','var') + output = this.processQueryResponse(this.jUrl.read(urlStr,false)); + else + output = this.processQueryResponse(this.jUrl.read(urlStr,hdfFile,false)); + end + end + + function output = write(this,urlStr,hdfFile) + % This method queries a RESTful web service to write data + % It supports posting an HDF5 file with the request. + if ~exist('hdfFile','var') + output = this.processQueryResponse(this.jUrl.write(urlStr,false)); + else + output = this.processQueryResponse(this.jUrl.write(urlStr,hdfFile,false)); + end + end + + function output = readCached(this,urlStr,hdfFile) + % This method queries a RESTful web service to read data + % It also supports posting an HDF5 file with the request. + % + % This method has ALL CACHING ENABLED. Use this for getting + % stable data only (i.e. image databases). There is a + % possiblity that if serverside data changes you will NOT see + % it. + + if ~exist('hdfFile','var') + output = this.processQueryResponse(this.jUrl.read(urlStr,true)); + else + output = this.processQueryResponse(this.jUrl.read(urlStr,hdfFile,true)); + end + end + + + %% Process query response + function output = processQueryResponse(this, responseCode) + if responseCode ~= 200 + % Some error Occured - Write out debug info + if isempty(this.errorPageLocation) + errorPagePath = char(this.jUrl.output); + else + errorPagePath = fullfile(this.errorPageLocation,[datestr(now,30) '.html']); + copyfile(char(this.jUrl.output),errorPagePath); + end + + errorMessage = sprintf('Server Response %d - %s \n Error Page: %s\n',... + responseCode, char(this.jUrl.responseMessage),... + errorPagePath,errorPagePath); + + if ispc + %need to fix \ + errorMessage = strrep(errorMessage,'\','\\'); + end + + ex = MException('OCPNetwork:InternalServerError',errorMessage); + throw(ex); + + else + % Looks Good + output = char(this.jUrl.output); + end + end + + %% Send HTTP DELETE request on a URL + function output = deleteRequest(this,urlStr) + % This method sends a url using a delete request + + % Do delete + responseCode = this.jUrl.delete(urlStr); + + + if responseCode ~= 200 + % Some error Occured - Write out debug info + if isempty(this.errorPageLocation) + errorPagePath = char(this.jUrl.output); + else + errorPagePath = fullfile(this.errorPageLocation,[datestr(now,30) '.html']); + copyfile(char(this.jUrl.output),errorPagePath); + end + + errorMessage = sprintf('Server Response %d - %s \n Error Page: %s\n',... + responseCode, char(this.jUrl.responseMessage),... + errorPagePath,errorPagePath); + + if ispc + %need to fix \ + errorMessage = strrep(errorMessage,'\','\\'); + end + + ex = MException('OCPNetwork:InternalServerError',errorMessage); + throw(ex); + + else + % Looks Good + output = char(this.jUrl.output); + end + end + end +end diff --git a/api/matlab/ocp/OCPQuery.m b/api/matlab/ocp/OCPQuery.m new file mode 100644 index 0000000..6f1366f --- /dev/null +++ b/api/matlab/ocp/OCPQuery.m @@ -0,0 +1,1292 @@ +classdef OCPQuery < handle + %OCPQuery ************************************************ + % Class specifying a query for database services + % + % Usage: + % + % q = OCPQuery(); Creates object + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + %% Properties - Serivce request type (should be of type eRequests) + type = [] + + %% Properties - ID + % This integer value(s) represent the unique 32 bit integer + % assigned to each annotation created by the database + id = []; + + %% Properties - DB Resolution higherarchy level + % This scalar integer value represents the database resolution higherarchy + % level to interact with + resolution = []; + + %% Properties - Volume Args - 1x2 Range [min max] + % These represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin) + % cutout from the corner (xmin,ymin,zmin). These correspond to the + % python conventions for ranges + xRange = []; + yRange = []; + zRange = []; + + %% Properties - Slice Args + % ##### Note ##### + % Slices may not represent "true" data since scaling is + % done to render proper images. If you want a single slice of raw + % data use the "dense" cutout queries with one of the dimensions having + % a range = 1 + % ##### Note ##### + % + % This represents in which coordinate plane the slice should be + % rendered. It should be of type eOCPSlicePlane which is an enumeration + % aid developers + slicePlane = []; % Should be type eOCPSlicePlane + + % These represent a rectanglular plane of size (amax-amin,bmax-bmin) + % from the corner (amin,bmin). The slice is taken at cIndex in the + % out of plane dimension. + aRange = []; + bRange = []; + cIndex = []; + + % This represents the alpha value (0-1) for the overlay service. If + % omitted an overlay alpha of 1 is used for backwards compatibility + overlayAlpha = []; % Should be 0-1 + + %% Properties - ID List query predicates + idListPredicates = containers.Map('KeyType','char','ValueType','double'); + + %% Properties - ID List query limit + % This controls the number of objects returned by an ID query + idListLimit = []; + + %% Properties - XYZ Coord for voxel ID by xyz query + xyzCoord = []; + + %% Properties - List of ids to include in an anno dense or slice query, + % ignoring others within the spatial extent of the query + filterIds = []; + %% Properties - Channel list for multichannel data query (cell array for multiple channels) + channels = []; + end + + methods + %% Methods - General & Setters + function this = OCPQuery(type,id) + % constructor is "overloaded" so that you can intialize it: + % 1) Empty + % 2) With the 'type' property set (eOCPQueryType) + % 3) With both 'type' property (eOCPQueryType) and id set. This + % is useful when simply querying the DB for RAMON objects + + % Guarantee Default Values + this.type = []; + this.id = []; + this.resolution = []; + this.xRange = []; + this.yRange = []; + this.zRange = []; + this.slicePlane = []; + this.aRange = []; + this.bRange = []; + this.cIndex = []; + this.idListPredicates = containers.Map; + this.idListLimit = []; + this.xyzCoord = []; + this.filterIds = []; + this.channels = []; + + if exist('type','var') + this.setType(type); + end + + if exist('id','var') + this.setId(id); + end + end + + function this = setCutoutArgs(this, varargin) + % [xMin xMax] [yMin yMax] [zMin zMax] + % [xMin xMax] [yMin yMax] [zMin zMax], res + % xMin, xMax, yMin, yMax, zMin, zMax + % xMin, xMax, yMin, yMax, zMin, zMax, res + + switch nargin + case 4 + this.setXRange(varargin{1}); + this.setYRange(varargin{2}); + this.setZRange(varargin{3}); + case 5 + this.setXRange(varargin{1}); + this.setYRange(varargin{2}); + this.setZRange(varargin{3}); + this.setResolution(varargin{4}); + + case 7 + this.setXRange([varargin{1} varargin{2}]); + this.setYRange([varargin{3} varargin{4}]); + this.setZRange([varargin{5} varargin{6}]); + + case 8 + this.setXRange([varargin{1} varargin{2}]); + this.setYRange([varargin{3} varargin{4}]); + this.setZRange([varargin{5} varargin{6}]); + this.setResolution(varargin{7}); + + otherwise + ex = MException('OCPQuery:IncorrectNumArgs',... + 'Incorrect Number of Arguments:%d',nargin); + throw(ex); + end + end + + function this = setSliceArgs(this, varargin) + % slicePlane [aMin aMax] [bMin bMax] cIndex + % slicePlane [aMin aMax] [bMin bMax] cIndex resolution + % slicePlane aMin aMax bMin bMax cIndex + % slicePlane aMin aMax bMin bMax cIndex resolution + + switch nargin + case 5 + this.setSlicePlane(varargin{1}); + this.setARange(varargin{2}); + this.setBRange(varargin{3}); + this.setCIndex(varargin{4}); + case 6 + this.setSlicePlane(varargin{1}); + this.setARange(varargin{2}); + this.setBRange(varargin{3}); + this.setCIndex(varargin{4}); + this.setResolution(varargin{5}); + + case 7 + this.setSlicePlane(varargin{1}); + this.setARange([varargin{2} varargin{3}]); + this.setBRange([varargin{4} varargin{5}]); + this.setCIndex(varargin{6}); + + case 8 + this.setSlicePlane(varargin{1}); + this.setARange([varargin{2} varargin{3}]); + this.setBRange([varargin{4} varargin{5}]); + this.setCIndex(varargin{6}); + this.setResolution(varargin{7}); + + otherwise + ex = MException('OCPQuery:IncorrectNumArgs',... + 'Incorrect Number of Arguments:%d',nargin); + throw(ex); + end + end + + function this = setType(this, type) + % This method sets the class property 'type' which + % indicates what type of database query you intend to do. + + if ~exist('type','var') + ex = MException('OCPQuery:MissingArgs',... + 'You must specify the query type using the eOCPQueryType enumeration'); + throw(ex); + end + + if isa(type, 'eOCPQueryType') + % Is of Type eOCPQueryType + else + % Is not of type eOCPQueryType + validateattributes(type,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + type = eOCPQueryType(uint32(type)); + catch ME + rethrow(ME); + end + end + + % If going from a volume to a slice guess that you want to look + % at a Z slice for convenience. + if ~isempty(this.type) + if this.type == eOCPQueryType.annoDense ||... + this.type == eOCPQueryType.imageDense + if type == eOCPQueryType.annoSlice || ... + type == eOCPQueryType.imageSlice || ... + type == eOCPQueryType.overlaySlice + this.aRange = this.xRange; + this.bRange = this.yRange; + this.cIndex = this.zRange(1); + this.slicePlane = eOCPSlicePlane.xy; + end + end + end + + this.type = type; + end + + + function this = setId(this,id) + % This member function sets id to query. It can be a single ID + % or an array. If it is an array the batch interface will be + % used. + validateattributes(id,{'numeric'},{'integer','finite','nonnegative','nonnan','real'}); + this.id = id; + end + + function this = setChannels(this,ch) + % This member function sets channels to query for multichannel + % image databases. Used ONLY in multichannel data. + % If specifying multiple channels, ch must be a cell array + if isa(ch, 'cell') + % Collection of channels + cellfun(@(x) validateattributes(x,{'char'},{'row'}), ch) + else + if ~isempty(ch) + % if not a cell you can pass in empty [] to clear but + % that is it. + error('OCPQuery:ChannelType','Channels must be a cell array'); + end + end + this.channels = ch; + end + + function this = setResolution(this,res) + % This member function sets the db resolution to query. + + validateattributes(res,{'numeric'},{'scalar','integer','finite','nonnegative','nonnan','real'}); + this.resolution = res; + end + + function this = setXRange(this,x) + % This member function sets the x Range [min max] for a volume + % cutout + validateattributes(x,{'numeric'},{'size', [1 2],'integer','finite','nonnegative','nonnan','real'}); + + if x(2) <= x(1) + errmsg = sprintf('%s\n\n%s%s',... + 'Max must be greater than Min.',... + ' Ranges represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin)',... + ' cutout from the corner (xmin,ymin,zmin). These correspond to the',... + ' python conventions for ranges.'); + ex = MException('OCPQuery:BadRange',errmsg); + throw(ex); + end + + this.xRange = x; + end + function this = setYRange(this,y) + % This member function sets the y Range [min max] for a volume + % cutout + validateattributes(y,{'numeric'},{'size', [1 2],'integer','finite','nonnegative','nonnan','real'}); + if y(2) <= y(1) + errmsg = sprintf('%s\n\n%s%s',... + 'Max must be greater than Min.',... + ' Ranges represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin)',... + ' cutout from the corner (xmin,ymin,zmin). These correspond to the',... + ' python conventions for ranges.'); + ex = MException('OCPQuery:BadRange',errmsg); + throw(ex); + end + this.yRange = y; + end + function this = setZRange(this,z) + % This member function sets the z Range [min max] for a volume + % cutout + validateattributes(z,{'numeric'},{'size', [1 2],'integer','finite','nonnegative','nonnan','real'}); + if z(2) <= z(1) + errmsg = sprintf('%s\n\n%s%s',... + 'Max must be greater than Min.',... + ' Ranges represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin)',... + ' cutout from the corner (xmin,ymin,zmin). These correspond to the',... + ' python conventions for ranges.'); + ex = MException('OCPQuery:BadRange',errmsg); + throw(ex); + end + this.zRange = z; + end + + function this = setARange(this,a) + % This member function sets the a Range [min max] + % This corresponds to either x,y,or z coordinates depending on + % which plane the image is being created in (xy,xz,yz) + validateattributes(a,{'numeric'},{'size', [1 2],'integer','finite','nonnegative','nonnan','real'}); + + if a(2) <= a(1) + errmsg = sprintf('%s\n\n%s%s',... + 'Max must be greater than Min.',... + ' Ranges represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin)',... + ' cutout from the corner (xmin,ymin,zmin). These correspond to the',... + ' python conventions for ranges.'); + ex = MException('OCPQuery:BadRange',errmsg); + throw(ex); + end + + this.aRange = a; + end + function this = setBRange(this,b) + % This corresponds to either x,y,or z coordinates depending on + % which plane the image is being created in (xy,xz,yz) + validateattributes(b,{'numeric'},{'size', [1 2],'integer','finite','nonnegative','nonnan','real'}); + + if b(2) <= b(1) + errmsg = sprintf('%s\n\n%s%s',... + 'Max must be greater than Min.',... + ' Ranges represent a cube of size (xmax-xmin,ymax-ymin,zmax-zmin)',... + ' cutout from the corner (xmin,ymin,zmin). These correspond to the',... + ' python conventions for ranges.'); + ex = MException('OCPQuery:BadRange',errmsg); + throw(ex); + end + + this.bRange = b; + end + function this = setCIndex(this,index) + % This corresponds to either x,y,or z index depending on + % which plane the image is being created in (xy,xz,yz) + validateattributes(index,{'numeric'},{'scalar','integer','finite','nonnegative','nonnan','real'}); + this.cIndex = index; + end + + function this = setOverlayAlpha(this,val) + % This member function sets the alpha value used in overlay queries + validateattributes(val,{'numeric'},{'finite','nonnegative','nonnan','real','>=',0,'<=',1}); + this.overlayAlpha = val; + end + + function this = setSlicePlane(this, plane) + % This method sets the class property 'slicePlane' which + % indicates in which image plane the image slice should be rendered + % (xy, xz, yz). + + if isa(plane, 'eOCPSlicePlane') + % Is of Type eOCPSlicePlane + else + % Is not of type eOCPSlicePlane + validateattributes(plane,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + plane = eOCPSlicePlane(uint32(plane)); + catch ME + rethrow(ME); + end + end + + this.slicePlane = plane; + end + + + function this = addIdListPredicate(this,predicate,value) + % This method adds a predicate to the id list query + if isa(predicate, 'eOCPPredicate') + % Is of Type eOCPPredicate + else + % Is not of type eOCPPredicate + validateattributes(predicate,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + predicate = eOCPPredicate(uint32(predicate)); + catch ME + rethrow(ME); + end + end + + this.idListPredicates(predicate.char()) = value; + end + + function this = removeIdListPredicate(this,predicate) + % This method removes a predicate from the id list query + this.idListPredicates.remove(predicate.char()); + end + + function this = setIdListLimit(this,limit) + % This member function sets list size limit. + validateattributes(limit,{'numeric'},{'scalar','integer','finite','nonnegative','nonnan','real','positive','nonzero'}); + this.idListLimit = limit; + end + + + function this = setXyzCoord(this, xyz) + % This method sets the class property 'xyzCoord' which + % indicates a point in the data set by its x,y,z coordinate + + validateattributes(xyz,{'numeric'},{'integer','finite','nonnegative','nonnan','real','size', [1 3]}); + this.xyzCoord = xyz; + end + + function this = setFilterIds(this,ids) + % This member function sets list of filter IDs + validateattributes(ids,{'numeric'},{'integer','finite','nonnegative','nonnan','real'}); + this.filterIds = ids; + end + + + + %% Methods - Validation & Dev Help + function [valid, msg] = validate(this,dbInfo) + % Method to validate cutout args. Returns a true for valid, false for + % invalid and a message describing what failed checks + valid = true; + msg = ''; + + %% Basic checking + if isempty(this.type) + valid = false; + msg = sprintf('%s- type is required for all queries and to enable full validation.\n',msg); + return + else + switch this.type + case {eOCPQueryType.imageDense,... + eOCPQueryType.annoDense,... + eOCPQueryType.probDense} + + if isempty(this.xRange) + valid = false; + msg = sprintf('%s[E] X Range is required for dense cutouts\n',msg); + end + if isempty(this.yRange) + valid = false; + msg = sprintf('%s[E] Y Range is required for dense cutouts\n',msg); + end + if isempty(this.zRange) + valid = false; + msg = sprintf('%s[E] Z Range is required for dense cutouts\n',msg); + end + + % Warnings + if ~isempty(this.id) + msg = sprintf('%s[W] id ignored with Dense queries.\n',msg); + end + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with Dense queries.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with Dense queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with Dense queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with Dense queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with Dense queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with Dense queries.\n',msg); + end + + + case {eOCPQueryType.imageSlice,... + eOCPQueryType.annoSlice} + + if isempty(this.aRange) + valid = false; + msg = sprintf('%s[E] A Range is required for slice cutouts\n',msg); + end + if isempty(this.bRange) + valid = false; + msg = sprintf('%s[E] B Range is required for slice cutouts\n',msg); + end + if isempty(this.cIndex) + valid = false; + msg = sprintf('%s[E] cIndex is required for slice cutouts\n',msg); + end + + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with slice cutouts.\n',msg); + end + + if isempty(this.slicePlane) + valid = false; + msg = sprintf('%s[E] slicePlane is required for slice cutouts\n',msg); + end + + if ~isa(this.slicePlane,'eOCPSlicePlane') + valid = false; + msg = sprintf('%s[E] slicePlane must be of type eOCPSlicePlane\n',msg); + end + + % Warnings + if ~isempty(this.id) + msg = sprintf('%s[W] id ignored with Dense queries.\n',msg); + end + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + msg = sprintf('%s[W] X,Y, and Z ranges ignored with Slice queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with Slice queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with Slice queries.\n',msg); + end + + case {eOCPQueryType.overlaySlice} + + if isempty(this.aRange) + valid = false; + msg = sprintf('%s[E] A Range is required for slice cutouts\n',msg); + end + if isempty(this.bRange) + valid = false; + msg = sprintf('%s[E] B Range is required for slice cutouts\n',msg); + end + if isempty(this.cIndex) + valid = false; + msg = sprintf('%s[E] cIndex is required for slice cutouts\n',msg); + end + + if isempty(this.slicePlane) + valid = false; + msg = sprintf('%s[E] slicePlane is required for slice cutouts\n',msg); + end + + if ~isa(this.slicePlane,'eOCPSlicePlane') + valid = false; + msg = sprintf('%s[E] slicePlane must be of type eOCPSlicePlane\n',msg); + end + + % Warnings + if isempty(this.overlayAlpha) + this.overlayAlpha = 1; + msg = sprintf('%s[W] overlayAlpha missing. Value set to default of 1.\n',msg); + end + + if ~isempty(this.id) + msg = sprintf('%s[W] id ignored with Dense queries.\n',msg); + end + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + msg = sprintf('%s[W] X,Y, and Z ranges ignored with Slice queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with Slice queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with Slice queries.\n',msg); + end + + + case {eOCPQueryType.RAMONDense} + if isempty(this.id) + valid = false; + msg = sprintf('%s[E] id is required for dense RAMON cutouts\n',msg); + end + + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 0 + valid = false; + msg = sprintf('%s[E] If you are restricting the query to a cutout, X,Y, and Z ranges are required. Otherwise clear X,Y, and Z ranges.\n',msg); + end + end + + % Warnings + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with RAMON Object queries.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with RAMON Object queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with RAMON Object queries.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + case {eOCPQueryType.RAMONVoxelList} + if isempty(this.id) + valid = false; + msg = sprintf('%s[E] id is required for RAMON voxelList\n',msg); + end + + % Warnings + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + msg = sprintf('%s[W] X,Y, and Z ranges ignored with RAMON voxel list queries.\n',msg); + end + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with RAMON Object queries.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with RAMON Object queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with RAMON Object queries.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + + case eOCPQueryType.RAMONMetaOnly + if isempty(this.id) + valid = false; + msg = sprintf('%s[E] id is required for Metadata Only RAMON queries\n',msg); + end + + % Warnings + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + msg = sprintf('%s[W] X,Y, and Z ranges ignored with Metadata Only queries.\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with Metadata Only queries.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with Metadata Only queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with Metadata Only queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with Metadata Only queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with RAMON Object queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with Metadata Only queries.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with Metadata Only queries.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + case eOCPQueryType.RAMONBoundingBox + + if isempty(this.id) + valid = false; + msg = sprintf('%s[E] id is required for RAMON object bounding box queries\n',msg); + end + + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 0 + valid = false; + msg = sprintf('%s[E] If you are restricting the query to a cutout, X,Y, and Z ranges are required. Otherwise clear X,Y, and Z ranges.\n',msg); + end + end + + % Warnings + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with RAMON Bounding Box queries.\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with RAMON Bounding Box queries.\n',msg); + end + if this.idListPredicates.Count ~= 0 + msg = sprintf('%s[W] idListPredicates ignored with RAMON Bounding Box queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with RAMON Bounding Box queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with RAMON Bounding Box queries.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored with RAMON Bounding Box queries.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with RAMON Bounding Box queries.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + case eOCPQueryType.RAMONIdList + + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 0 + valid = false; + msg = sprintf('%s[E] If you are restricting the query to a cutout, X,Y, and Z ranges are required. Otherwise clear X,Y, and Z ranges.\n',msg); + end + end + + % Warnings + if isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit is empty. All annotations meeting the predicate requirements will be returned (this can take a long time depending on your database!)\n',msg); + end + if ~isempty(this.id) + msg = sprintf('%s[W] id ignored with Id List queries.\n',msg); + end + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored with Id List queries.\n',msg); + end + + if isempty(this.idListPredicates) + msg = sprintf('%s[W] idListPredicates is empty. Every object in the database will be returned\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored with Id List queries.\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored with Id List queries.\n',msg); + end + if ~isempty(this.xyzCoord) + msg = sprintf('%s[W] xyzCoord ignored with Id List queries.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with Id List queries.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + case eOCPQueryType.voxelId + + if isempty(this.xyzCoord) + valid = false; + msg = sprintf('%s[E] xyzCoord is required when querying a voxel ID by xyz coordinate\n',msg); + end + + % Warnings + if isempty(this.resolution) + msg = sprintf('%s[W] Resolution is empty. Default resolution will be used at runtime.\n',msg); + end + if sum([isempty(this.aRange) isempty(this.bRange) isempty(this.cIndex)]) ~= 3 + msg = sprintf('%s[W] A and B ranges and cIndex ignored when querying a voxel ID by xyz coordinate.\n',msg); + end + if sum([isempty(this.xRange) isempty(this.yRange) isempty(this.zRange)]) ~= 3 + msg = sprintf('%s[W] Cutout Args are ignored when querying a voxel ID by xyz coordinate.\n',msg); + end + if ~isempty(this.id) + msg = sprintf('%s[W] id is ignored when querying a voxel ID by xyz coordinate\n',msg); + end + if ~isempty(this.idListPredicates) + msg = sprintf('%s[W] idListPredicates is ignored when querying a voxel ID by xyz coordinate\n',msg); + end + if ~isempty(this.slicePlane) + msg = sprintf('%s[W] slicePlane ignored when querying a voxel ID by xyz coordinate\n',msg); + end + if ~isempty(this.overlayAlpha) + msg = sprintf('%s[W] overlayAlpha ignored querying a voxel ID by xyz coordinate.\n',msg); + end + if ~isempty(this.idListLimit) + msg = sprintf('%s[W] idListLimit ignored when querying a voxel ID by xyz coordinate.\n',msg); + end + if ~isempty(this.filterIds) + msg = sprintf('%s[W] filterIds ignored with voxel ID by xyz coordinate.\n',msg); + end + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + + + otherwise + ex = MException('OCPQuery:BadQueryTYpe',... + 'Invalid Query Type:%d',uint32(this.type)); + throw(ex); + end + end + + %% Advanced Checking + % if good so far and dbinfo exists do more advanced checking + if valid == true && exist('dbInfo','var') + + switch this.type + case {eOCPQueryType.imageDense,... + eOCPQueryType.annoDense,... + eOCPQueryType.probDense} + + % Make sure Resolution if valid + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + if ~isempty(this.resolution) + % Make sure x,y,z are good + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.xRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower X Range out of valid dataset range.\n',msg); + end + if this.xRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper X Range out of valid dataset range.\n',msg); + end + if this.yRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower Y Range out of valid dataset range.\n',msg); + end + if this.yRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper Y Range out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.zRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower Z Range out of valid dataset range.\n',msg); + end + if this.zRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper Z Range out of valid dataset range.\n',msg); + end + + % Check if channels are listed and valid for + % multichannel queries + if (dbInfo.PROJECT.TYPE == eRAMONDataType.channels16) || ... + (dbInfo.PROJECT.TYPE == eRAMONDataType.channels8) + + if isempty(this.channels) + % Gotta have channels for multichannel + % data! + valid = false; + msg = sprintf('%s[E] Must specify channels to cutout for multichannel data\n',msg); + return + end + + + % Check that channels requested are in DB + channel_cell_array = fieldnames(dbInfo.CHANNELS); + + if isa(this.channels, 'cell') + % Collection of channels + for jj = 1:length(this.channels) + ch_matches(jj) = any(strcmp(this.channels{jj},channel_cell_array)); %#ok + end + + if all(ch_matches) == 0 + valid = false; + bad_ind = find(ch_matches == 0); + msg = sprintf('%s[E] Channels not found in database: ',msg); + tmsg = sprintf('%s,',this.channels{bad_ind}); %#ok + msg = sprintf('%s%s\n',msg, tmsg(1:end-1)); + return + end + end + else + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + end + + + case {eOCPQueryType.imageSlice,... + eOCPQueryType.annoSlice,... + eOCPQueryType.overlaySlice} + + % Make sure Resolution if valid + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + % Make sure a,b,c are good + switch this.slicePlane + case eOCPSlicePlane.xy + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.aRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower A Range (X dim) out of valid dataset range.\n',msg); + end + if this.aRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper A Range (X dim) out of valid dataset range.\n',msg); + end + if this.bRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower B Range (Y dim) out of valid dataset range.\n',msg); + end + if this.bRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper B Range (Y dim) out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.cIndex < dbInfo.DATASET.SLICERANGE(1) || this.cIndex > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] cIndex (Z dim) out of valid dataset range.\n',msg); + end + + + case eOCPSlicePlane.xz + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.aRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower A Range (X dim) out of valid dataset range.\n',msg); + end + if this.aRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper A Range (X dim) out of valid dataset range.\n',msg); + end + if this.bRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower B Range (Z dim) out of valid dataset range.\n',msg); + end + if this.bRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper B Range (Z dim) out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.cIndex < 0 || this.cIndex > imgDims(2) + valid = false; + msg = sprintf('%s[E] cIndex (Y dim) out of valid dataset range.\n',msg); + end + + + case eOCPSlicePlane.yz + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.aRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower A Range (Y dim) out of valid dataset range.\n',msg); + end + if this.aRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper A Range (Y dim) out of valid dataset range.\n',msg); + end + if this.bRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower B Range (Z dim) out of valid dataset range.\n',msg); + end + if this.bRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper B Range (Z dim) out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.cIndex < 0 || this.cIndex > imgDims(1) + valid = false; + msg = sprintf('%s[E] cIndex (X dim) out of valid dataset range.\n',msg); + end + + end + + % Check if channels are listed and valid for + % multichannel queries + if (dbInfo.PROJECT.TYPE == eRAMONDataType.channels16) || ... + (dbInfo.PROJECT.TYPE == eRAMONDataType.channels8) + + % Check that channels requested are in DB + channel_cell_array = fieldnames(dbInfo.CHANNELS); + + if isa(this.channels, 'cell') + % Collection of channels + for jj = 1:length(this.channels) + ch_matches(jj) = any(strcmp(this.channels{jj},channel_cell_array)); %#ok + end + + if all(ch_matches) == 0 + valid = false; + bad_ind = find(ch_matches == 0); + msg = sprintf('%s[E] Channels not found in database: ',msg); + tmsg = sprintf('%s,',this.channels{bad_ind}); %#ok + msg = sprintf('%s%s\n',msg, tmsg(1:end-1)); + return + end + end + else + % not multichannel + msg = sprintf('%s[W] channels ignored with non-multichannel data.\n',msg); + end + + + + case {eOCPQueryType.RAMONDense} + % Make sure Resolution if valid + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + % If Doing a cutout then check xyz + if ~isempty(this.xRange) && ~isempty(this.yRange) && ~isempty(this.zRange) + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.xRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower X Range out of valid dataset range.\n',msg); + end + if this.xRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper X Range out of valid dataset range.\n',msg); + end + if this.yRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower Y Range out of valid dataset range.\n',msg); + end + if this.yRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper Y Range out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.zRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower Z Range out of valid dataset range.\n',msg); + end + if this.zRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper Z Range out of valid dataset range.\n',msg); + end + end + + + case {eOCPQueryType.RAMONVoxelList} + % Make sure Resolution if valid + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + case eOCPQueryType.RAMONMetaOnly + % No db checks + + + case eOCPQueryType.RAMONBoundingBox + % Make sure Resolution if valid + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + % If Doing a cutout then check xyz + if ~isempty(this.xRange) && ~isempty(this.yRange) && ~isempty(this.zRange) + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.xRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower X Range out of valid dataset range.\n',msg); + end + if this.xRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper X Range out of valid dataset range.\n',msg); + end + if this.yRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower Y Range out of valid dataset range.\n',msg); + end + if this.yRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper Y Range out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.zRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower Z Range out of valid dataset range.\n',msg); + end + if this.zRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper Z Range out of valid dataset range.\n',msg); + end + end + + + case eOCPQueryType.RAMONIdList + + % If Doing a cutout then check xyz + if ~isempty(this.xRange) && ~isempty(this.yRange) && ~isempty(this.zRange) + % Make sure Resolution if valid + if ~isempty(this.resolution) + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + return + end + + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.xRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower X Range out of valid dataset range.\n',msg); + end + if this.xRange(2) > imgDims(1) + valid = false; + msg = sprintf('%s[E] Upper X Range out of valid dataset range.\n',msg); + end + if this.yRange(1) < 0 + valid = false; + msg = sprintf('%s[E] Lower Y Range out of valid dataset range.\n',msg); + end + if this.yRange(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Upper Y Range out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.zRange(1) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] Lower Z Range out of valid dataset range.\n',msg); + end + if this.zRange(2) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Upper Z Range out of valid dataset range.\n',msg); + end + end + + case eOCPQueryType.voxelId + + % Make sure Resolution if valid + if ~isempty(this.resolution) + if ~ismember(this.resolution,dbInfo.DATASET.RESOLUTIONS) + valid = false; + msg = sprintf('%s[E] Resolution not supported by database.\n',msg); + end + end + + if ~isempty(this.resolution) + imgDims = dbInfo.DATASET.IMAGE_SIZE(this.resolution); + if this.xyzCoord(1) < 0 + valid = false; + msg = sprintf('%s[E] X coord out of valid dataset range.\n',msg); + end + if this.xyzCoord(1) > imgDims(1) + valid = false; + msg = sprintf('%s[E] X coord out of valid dataset range.\n',msg); + end + if this.xyzCoord(2) < 0 + valid = false; + msg = sprintf('%s[E] Y coord out of valid dataset range.\n',msg); + end + if this.xyzCoord(2) > imgDims(2) + valid = false; + msg = sprintf('%s[E] Y coord out of valid dataset range.\n',msg); + end + else + msg = sprintf('%s[W] Since resolution not specified X and Y coordinates could not be checked against the database.\n',msg); + end + + if this.xyzCoord(3) < dbInfo.DATASET.SLICERANGE(1) + valid = false; + msg = sprintf('%s[E] z coord out of valid dataset range.\n',msg); + end + if this.xyzCoord(3) > dbInfo.DATASET.SLICERANGE(2) + valid = false; + msg = sprintf('%s[E] Z coord out of valid dataset range.\n',msg); + end + + otherwise + ex = MException('OCPQuery:BadQueryTYpe',... + 'Invalid Query Type:%d',uint32(this.type)); + throw(ex); + end + end + end + + %% Methods - Save Query + function save(this,file) + % Method to save a query for later use. If filename is not + % included a dialog will open to select location to save. + if ~exist('file','var') + [filename,pathname] = uiputfile('*.mat','Save Query As'); + + if isequal(filename,0) + warning('OCPQuery:FileSelectionCancel','No file was selected. Query not saved.'); + return; + end + + file = fullfile(pathname,filename); + end + + query = this; %#ok + + save(file,'query'); + end + + end + + %% Static Method for Loading + methods(Static) + + function query = open(file) + % Method to load a saved query. If filename is not + % included a dialog will open to select file to load + + if ~exist('file','var') + [filename, pathname, ~] = uigetfile( ... + { '*.mat','MAT-files (*.mat)'}, ... + 'Pick a Query File', ... + 'MultiSelect', 'off'); + + if isequal(filename,0) + warning('OCPQuery:FileSelectionCancel','No file was selected. Query not opened.'); + query = []; + return; + end + + file = fullfile(pathname,filename); + end + + loaded = load(file); + query = loaded.query; + end + end + +end + diff --git a/api/matlab/ocp/cajal3d.jar b/api/matlab/ocp/cajal3d.jar new file mode 100644 index 0000000..9e7646a Binary files /dev/null and b/api/matlab/ocp/cajal3d.jar differ diff --git a/api/matlab/ocp/enum/eOCPConflictOption.m b/api/matlab/ocp/enum/eOCPConflictOption.m new file mode 100644 index 0000000..d8a6277 --- /dev/null +++ b/api/matlab/ocp/enum/eOCPConflictOption.m @@ -0,0 +1,38 @@ +classdef eOCPConflictOption < uint32 + %eOCPConflictOption Enumeration of options for database voxel write + % conflict resolution. + % + % overwrite - overwrite existing annotations. This is the default. + % preserve - keep existing annotations. Discard conflicting new annotations. + % exception - keep all anotations using the exceptions capability. + % reduce - remove voxels labeled by the uploaded annotation. + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + overwrite (0) + preserve (1) + exception (2) + reduce (2) + end + +end + diff --git a/api/matlab/ocp/enum/eOCPPredicate.m b/api/matlab/ocp/enum/eOCPPredicate.m new file mode 100644 index 0000000..96097bb --- /dev/null +++ b/api/matlab/ocp/enum/eOCPPredicate.m @@ -0,0 +1,35 @@ +classdef eOCPPredicate < uint32 + %eOCPPredicate Enumeration of supported predicates used in ID queries + % 0 = type - annotation type (eRAMONAnnoType) + % 1 = status - annotation status (eRAMONAnnoStatus) + % 2 = confidence_gt - annotation confidence is greater than a value + % 3 = confidence_lt - annotation confidence is les than a value + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + type (0) + status (1) + confidence_gt (2) + confidence_lt (3) + end + +end + diff --git a/api/matlab/ocp/enum/eOCPPropagateStatus.m b/api/matlab/ocp/enum/eOCPPropagateStatus.m new file mode 100644 index 0000000..0fd90f4 --- /dev/null +++ b/api/matlab/ocp/enum/eOCPPropagateStatus.m @@ -0,0 +1,45 @@ +classdef eOCPPropagateStatus < uint32 + %eOCPPropagateStatus Enumeration of options for database propagation + %status + % + % inconsistent (0) - Read/Write mode. Database has been editied since last propagation + % or has never been propagated. Annotations at + % different resolutions may be inconsistent, but you + % can edit the database. + % propagating (1) - Read-only mode. The database is currently + % propagating all annotations in the background. You + % must wait for this process to complete before the + % database will become writable again. + % consistent (2) - Read-only mode. The database has been propagated + % and is now consistent across all resolutions. To + % write again to the database you must unlock it by + % invoking the "makeAnnoWritable" or + % "makeAnnoWritable" methods in the OCP class. + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + inconsistent (0) + propagating (1) + consistent (2) + end +end + diff --git a/api/matlab/ocp/enum/eOCPQueryType.m b/api/matlab/ocp/enum/eOCPQueryType.m new file mode 100644 index 0000000..311916c --- /dev/null +++ b/api/matlab/ocp/enum/eOCPQueryType.m @@ -0,0 +1,73 @@ +classdef eOCPQueryType < uint32 + %eRequests Enumeration of server request types + % imageDense - Dense Image DB Cutout + % imageSlice - Single Slice of Image DB + % annoDense - Dense Annotation DB Cutout + % annoSlice - Single Slice of Annotation DB + % overlaySlice - Single Slice of Overlay of image and annotation DBs + % RAMONDense - RAMON object query with voxel data in Dense Format + % RAMONVoxelList - RAMON object query with voxel data in Voxel List Format + % RAMONMetaOnly - RAMON object query with NO voxel data returned + % RAMONBoundingBox - Returns a cuboid aligned bounding box around RAMON Object + % RAMONIdList - Query to search annotation database by setting + % RAMON object metadata predicates. Can be restricted to + % a cutout by setting Cutout Args. By default searches + % whole DB if X, Y, and Z ranges are empty. + % voxelId - Query the ID of a voxel based on it's x,y, and z + % coordinates. This is useful if you want to see what ID that + % database has assigned to an annotation after you have created it. + % probDense - dense cutout of a probability database. returns + % RAMONVolume with data of type single + % + % Terminology: + % - Dense: 3D volumetric cutout where 0 is unlabled voxels and + % non-zero are labled voxels (uint32) + % - VoxelList: Nx3 array of the x,y,z coordinates of the labeled + % voxels in database coordinates at the resolution you are working! + % (only works when operating on a single RAMON object) + % - MetaOnly: Query retrieves no voxel data, but only RAMON object + % metadata + % + % NOTE: + % - Slice service does NOT return raw data, but depending on the selected + % resolution, a scaled image for visualization purposes encoded as a png. + % If you want a single slice of raw data use the Dense Cutout Service + % with your z dimension span = 1 + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + enumeration + imageDense (0) + imageSlice (1) + annoDense (2) + annoSlice (3) + overlaySlice (4) + RAMONDense (5) + RAMONVoxelList (6) + RAMONMetaOnly (7) + RAMONBoundingBox (8) + RAMONIdList (9) + voxelId (10) + probDense (11) + end + +end + diff --git a/api/matlab/ocp/enum/eOCPSlicePlane.m b/api/matlab/ocp/enum/eOCPSlicePlane.m new file mode 100644 index 0000000..3c2d4cc --- /dev/null +++ b/api/matlab/ocp/enum/eOCPSlicePlane.m @@ -0,0 +1,33 @@ +classdef eOCPSlicePlane < uint32 + %eOCPSlicePlane Enumeration of available slice image planes + % 0 = xy + % 1 = xz + % 2 = yz + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + xy (0) + xz (1) + yz (2) + end + +end + diff --git a/api/matlab/ocp/jedis-2.1.0.jar b/api/matlab/ocp/jedis-2.1.0.jar new file mode 100644 index 0000000..a88b954 Binary files /dev/null and b/api/matlab/ocp/jedis-2.1.0.jar differ diff --git a/api/matlab/ocp/semaphore_settings_example.mat b/api/matlab/ocp/semaphore_settings_example.mat new file mode 100644 index 0000000..01c8ec3 Binary files /dev/null and b/api/matlab/ocp/semaphore_settings_example.mat differ diff --git a/api/matlab/ramon/RAMONAttributedRegion.m b/api/matlab/ramon/RAMONAttributedRegion.m new file mode 100644 index 0000000..51f2f63 --- /dev/null +++ b/api/matlab/ramon/RAMONAttributedRegion.m @@ -0,0 +1,41 @@ +classdef RAMONAttributedRegion < RAMONVolume + %RAMONAttributedRegion ************************************************ + % Currently not implemented server side. + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + + end + + methods + function this = RAMONAttributedRegion() + % Constructor + + ex = MException('RAMONAttributedRegion:Unsupported','RAMONAttributedRegion type not yet supported.'); + throw(ex); + end + + + + end +end + diff --git a/api/matlab/ramon/RAMONBase.m b/api/matlab/ramon/RAMONBase.m new file mode 100644 index 0000000..c816f56 --- /dev/null +++ b/api/matlab/ramon/RAMONBase.m @@ -0,0 +1,324 @@ +classdef RAMONBase < handle + %RAMONBase ************************************************ + % Base Annotation class. All RAMON annotations contain this + % commen set of information + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + id % Unique 32bit ID value assigned by OCP database + confidence % Value 0-1 indicating confidence in annotation + dynamicMetadata % A flexible, unspecified collection key-value pairs + status % Status of annotation in database + author % username of the person who created the annotation + end + + methods + function this = RAMONBase(varargin) + % Assign fields based on input arguments + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + this.setAuthor('unspecified'); + + if nargin > 0 + this.setId(varargin{1}); + end + if nargin > 1 + this.setConfidence(varargin{2}); + end + if nargin > 2 + this.setStatus(varargin{3}); + end + if nargin > 3 + if ~isempty(varargin{4}) + [numKey, col] = size(varargin{4}); + if col ~= 2 + ex = MException('RAMONBase:DMDFormatInvalid','The dynamic metadata format is invalid. Check documentation.'); + throw(ex); + end + data = varargin{4}; + this.clearDynamicMetadata(); + for ii = 1:numKey + this = this.addDynamicMetadata(data{ii,1},data{ii,2}); + end + end + end + if nargin > 4 + this.setAuthor(varargin{5}); + end + if nargin > 5 + ex = MException('RAMONBase:TooManyArguments','Too many arguments in constructor for initialization, see documentation for use.'); + throw(ex); + end + + end + + %% Setter Functions for input validation + + function this = setId(this,value) + % This member function sets the id field (32 bit positive + % non-zero integer) + + try + validateattributes(value,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + catch %#ok<*CTCH> + ME = lasterror; %#ok<*LERR> + ME.message = sprintf('Error setting id property: %s',ME.message); + rethrow(ME); + end + + this.id = value; + end + + function this = setConfidence(this,value) + % This member function sets the confidence field. (0-1) + + if isempty(value) + % Default confidence is 1. All annotations must have a + % confidence value. It is assumed that unless a confidence + % has been calculated the confidence is 1. + value = 1; + warning('RAMONBase:DefaultConfidence','Attemped to set confidence to null value. Default value of 1 used instead'); + end + + try + validateattributes(value,{'numeric'},{'scalar','<=',1,'>=',0,'finite','nonnegative','nonnan','real'}); + catch + ME = lasterror; + ME.message = sprintf('Error setting confidence property: %s',ME.message); + rethrow(ME) + end + + this.confidence = value; + end + + function this = setAuthor(this,value) + % This member function sets the author field. + + if ~isempty(value) && ~isa(value,'char'); + ex = MException('RAMONBase:InvalidAuthor','Author must be of type char'); + throw(ex); + end + + this.author = value; + end + + function this = setStatus(this,value) + % This member function sets the status field. + % The value of this field must either be of the enumeration + % eRAMONAnnoStatus or a uint32 that corresponds to a supported + % enumeration in eRAMONAnnoStatus + + if isa(value, 'eRAMONAnnoStatus') + % Is of Type AnnotationStatus + + else + % Is not of type AnnotationStatus + try + validateattributes(value,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + catch + ME = lasterror; + ME.message = sprintf('Error setting status property: %s',ME.message); + rethrow(ME) + end + + try + value = eRAMONAnnoStatus(value); + catch ME + rethrow(ME); + end + end + + this.status = value; + end + + function this = setDynamicMetadata(this,map) + % This member function directly sets the dynamic metadata and + % is useful when copying a whole set of metadata from one + % object to another + + if ~isa(map,'containers.Map'); + ex = MException('RAMONBase:InvalidDMType','Error setting dynamic metadata: Dynamic Metadata must be represented as a container.Map'); + throw(ex); + end + + this.dynamicMetadata = map; + end + + function this = addDynamicMetadata(this,key,value) + % This member function adds a key value pair to the + % dynamic metadata field. They key must not already + % exist. + + if ~isa(key,'char'); + ex = MException('RAMONBase:InvalidKey','Error adding dynamic metadata: Key must be of type char'); + throw(ex); + end + + % add key + if this.dynamicMetadata.Count == 0 + % first key + this.dynamicMetadata(key) = value; + else + % not the first key + + % check if key exists already + if ~isempty(find(ismember(this.dynamicMetadata.keys, key)==1, 1)) + ex = MException('RAMONBase:DuplicateKey',sprintf('Error adding dynamic metadata: Key "%s" already exists. Cannot Add. Try update.',key)); + throw(ex); + end + + % If you are here the key doesn't exist + this.dynamicMetadata(key) = value; + end + end + + function this = removeDynamicMetadata(this,key) + % This member function removes a key value pair from the + % dynamic metadata field. + + if ~isa(key,'char'); + ex = MException('RAMONBase:InvalidKey','Error adding dynamic metadata: Key must be of type char'); + throw(ex); + end + + % check if key exists already + if isempty(find(ismember(this.dynamicMetadata.keys, key)==1, 1)) + warning('RAMONBase:KeyNotFound', 'Key not found. Nothing was removed.') + else + this.dynamicMetadata.remove(key); + end + end + + function this = updateDynamicMetadata(this,key,value) + % This member function updates a key value pair to the point + % annotation dynamic metadata field. If they key doesn't + % already exist it is simply created. + + if ~isa(key,'char'); + ex = MException('RAMONBase:InvalidKey','Error adding dynamic metadata: Key must be of type char'); + throw(ex); + end + + % Update/Add key + this.dynamicMetadata(key) = value; + end + + function this = clearDynamicMetadata(this) + % This member function clears the point annotation dynamic + % metadata field. + this.dynamicMetadata = containers.Map(); + end + + function keys = getMetadataKeys(this) + keys = this.dynamicMetadata.keys(); + end + + function value = getMetadataValue(this, key) + if ~isa(key,'char'); + ex = MException('RAMONBase:InvalidKey','Key must be of type char'); + throw(ex); + end + + % check if key exists already + if isempty(find(ismember(this.dynamicMetadata.keys, key)==1, 1)) + ex = MException('RAMONBase:KeyDoesNotExist',sprintf('Key "%s" does not exist. Cannot Get Value.',key)); + throw(ex); + end + + value = this.dynamicMetadata(key); + end + + function handle = clone(this, ~) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + end + + %% RAMON to HDF METHOD - Static + + function filename = toHDF(this) + filename = OCPHdf(this); + end + + + + end + + methods(Static) + %% Pass through voxel count method (since no voxels) + function count = voxelCount() + % Returns the number of voxels stored in the RAMON objects that + % don't have RAMONVolume as a super class + count = 0; + end + end + + + %% Methods - Utility Functions + methods( Access = protected) + function ramonObj = setRAMONBaseProperties(this,ramonObj) + % RAMONBase + ramonObj.setId(this.id); + ramonObj.setConfidence(this.confidence); + ramonObj.setDynamicMetadata(this.dynamicMetadata); + ramonObj.setStatus(this.status); + ramonObj.setAuthor(this.author); + end + + function metadataConstructHelper(this,var) + + [numkey, col] = size(var); + if numkey ~= 0 + if col ~= 2 + ex = MException('RAMONBase:MetadataFormatInvalid','The init dynamic metadata format is invalid. Should be Nx2 cell array.'); + throw(ex); + end + data = var; + for ii = 1:numkey + this = this.addDynamicMetadata(data{ii,1},data{ii,2}); + end + else + this.clearDynamicMetadata(); + end + end + + function handle = baseCloneHelper(this, handle) + % Copy all properties. + % Base + handle.setId(this.id); + handle.setConfidence(this.confidence); + handle.setDynamicMetadata(this.dynamicMetadata); + handle.setStatus(this.status); + handle.setAuthor(this.author); + end + end + +end + diff --git a/api/matlab/ramon/RAMONGeneric.m b/api/matlab/ramon/RAMONGeneric.m new file mode 100644 index 0000000..1937b32 --- /dev/null +++ b/api/matlab/ramon/RAMONGeneric.m @@ -0,0 +1,91 @@ +classdef RAMONGeneric < RAMONVolume + %RAMONGeneric ************************************************ + % Generic Annotation Class used to capture basic annotations before they + % have been attributed to a RAMON type, or to accomodate data that does + % not have a RAMON type. You should try to fit into the RAMON + % data model and use this object as an intermediate product + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + + end + + methods + function this = RAMONGeneric() + % Constructor + + end + + function handle = clone(this, option) + % Perform a deep copy because these are handles and not + % objects. + % Default using the = operator just copies the handle and not + % the underlying object. + % + % Optionally pass in 'novoxels' to perform copy without voxel + % data + % + % ex: new_obj = my_obj.clone('novoxels'); + + if ~exist('option','var'); + option = []; + end + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Volume + handle = this.volumeCloneHelper(handle,option); + end + + + + %% RAMON Converstion Methods + function ramonObj = toSynapse(this) + ramonObj = RAMONSynapse(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + ramonObj = this.setRAMONVolumeProperties(ramonObj); + end + + function ramonObj = toSeed(this) + ramonObj = RAMONSeed(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + end + + function ramonObj = toSegment(this) + ramonObj = RAMONSegment(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + ramonObj = this.setRAMONVolumeProperties(ramonObj); + end + + function ramonObj = toNeuron(this) + ramonObj = RAMONNeuron(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + end + + + end +end + diff --git a/api/matlab/ramon/RAMONNeuron.m b/api/matlab/ramon/RAMONNeuron.m new file mode 100644 index 0000000..e945feb --- /dev/null +++ b/api/matlab/ramon/RAMONNeuron.m @@ -0,0 +1,128 @@ +classdef RAMONNeuron < RAMONBase + % RAMONNeuron - data type to store information about a neuron. + % Constructor available to initialize neuron state. For any field not + % desired to initialize use []. If the field is omitted a default + % value will be assigned + % + % Example + % neuron1 = RAMONNeuron(); default seed + % neuron1 = RAMONNeuron(segments, id, confidence, status, dynamicMetadata); + % + % Neuron data structure: + % neuron.segments - segment ids that are part of neuron + % neuron.id - unique identifier for the neuron - type: integer + % neuron.confidence - Value 0-1 indicating confidence in annotation - + % type: double + % neuron.status - Current state of the neuron. RAMONAnnotationStatus + % neuron.dynamicMetadata - Cell array of key-value pairs + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + segments = []; + end + + methods + function this = RAMONNeuron(varargin) + % RAMONNeuron(); + % RAMONNeuron(...); + % RAMONNeuron(segments, id, confidence, status, dynamicMetadata, author); + + this.setSegments([]); + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + this.setAuthor('unspecified'); + + + if nargin > 0 + this.setSegments(varargin{1}); + end + if nargin > 1 + this.setId(varargin{2}); + end + if nargin > 2 + this.setConfidence(varargin{3}); + end + if nargin > 3 + this.setStatus(varargin{4}); + end + if nargin > 4 + if ~isempty(varargin{5}) + [numKey, col] = size(varargin{5}); + if col ~= 2 + ex = MException('RAMONBase:DMDFormatInvalid','The dynamic metadata format is invalid. Check documentation.'); + throw(ex); + end + data = varargin{5}; + this.clearDynamicMetadata(); + for ii = 1:numKey + this = this.addDynamicMetadata(data{ii,1},data{ii,2}); + end + end + end + if nargin > 5 + this.setAuthor(varargin{6}); + end + if nargin > 6 + ex = MException('RAMONNeuron:TooManyArguments','Too many arguments in constructor for initialization, see documentation for use.'); + throw(ex); + end + end + + + %% Set Functions to validate data + + %SET SEGMENTS FIELD + function this = setSegments(this,seg) + % This member function sets the segment's linked synapses field. + + validateattributes(seg,{'numeric'},{'integer','nonnegative','nonnan','real'}); + this.segments = seg; + end + + function handle = clone(this,~) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + + % Class + handle.setSegments(this.segments); + end + + %% RAMON Converstion Methods + function ramonObj = toGeneric(this) + ramonObj = RAMONGeneric(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + end + + end + +end + diff --git a/api/matlab/ramon/RAMONOrganelle.m b/api/matlab/ramon/RAMONOrganelle.m new file mode 100644 index 0000000..bfd4506 --- /dev/null +++ b/api/matlab/ramon/RAMONOrganelle.m @@ -0,0 +1,187 @@ +classdef RAMONOrganelle < RAMONVolume + %RAMONOrganelle ************************************************ + % Annotation for neuron organelles. eRAMONOrganelleClass enumeration + % contains the currently supported organelles and can be extended. If + % you wish to add to the baseline so it is standard amoung all users + % contact OCP developers. + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + class = []; %eRAMONOrganelleClass + seeds = []; %list of seed IDs related to organelle + parentSeed = []; %id of seed that resulted in organelle creation + end + + methods + function this = RAMONOrganelle(varargin) + % RAMONOrganelle(); + % RAMONOrganelle(...); + % RAMONOrganelle(volumeData, dataFormat, xyzOffset, resolution, class,... + % seeds, parentSeed, id, confidence, status,... + % dynamicMetadata, author); + + this.setCutout([]); + this.setXyzOffset([]); + this.setResolution([]); + this.setClass(eRAMONOrganelleClass.unknown); + this.setSeeds([]); + this.setParentSeed([]); + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + this.setAuthor('unspecified'); + + if nargin == 1 + ex = MException('RAMONOrganelle:MissingDataFormat',... + 'When instantiating a RAMONOrganelle object with voxel data you must include the "dataFormat" argument indicating the voxel data representation.'); + throw(ex); + end + if nargin > 1 + this = this.setDataFormat(varargin{2}); + this = this.initVoxelData(varargin{1}, this.dataFormat); + end + if nargin > 2 + this.setXyzOffset(varargin{3}); + end + if nargin > 3 + this.setResolution(varargin{4}); + end + if nargin > 4 + this.setClass(varargin{5}); + end + if nargin > 5 + this.setSeeds(varargin{6}); + end + if nargin > 6 + this.setParentSeed(varargin{7}); + end + if nargin > 7 + this.setId(varargin{8}); + end + if nargin > 8 + this.setConfidence(varargin{9}); + end + if nargin > 9 + this.setStatus(varargin{10}); + end + if nargin > 10 + this.metadataConstructHelper(varargin{11}) + end + if nargin > 11 + this.setAuthor(varargin{12}); + end + if nargin > 12 + ex = MException('RAMONOrganelle:TooManyArguments','Too many attributes, see documentation for use.'); + throw(ex); + end + end + + + + %% Set Functions to validate data + + function this = setClass(this,type) + % This member function sets the organelle class field. + + if isempty(type) + % If type is empty set to default + this.class = eRAMONOrganelleClass.unknown; + end + + if isa(type, 'eRAMONOrganelleClass') + % Is of Type eRAMONOrganelleClass + + else + % Is not of type eRAMONOrganelleClass + validateattributes(type,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + type = eRAMONOrganelleClass(uint32(type)); + catch ME + rethrow(ME); + end + end + + this.class = type; + end + + function this = setSeeds(this,seeds) + % This member function sets the organelles associated seeds field. + if ~isempty(seeds) + validateattributes(seeds,{'numeric'},{'finite','nonnegative','nonnan','real'}); + end + + if size(seeds,1) > 1 + ex = MException('RAMONOrganelle:InvalidFormat','Seed list should be a 1xN array'); + throw(ex); + end + + this.seeds = seeds; + end + + function this = setParentSeed(this,ps) + % This member function sets the organelle's associated parent seed field. + + if ~isempty(ps) + validateattributes(ps,{'numeric'},{'scalar','finite','nonnegative','nonnan','real'}); + end + this.parentSeed = ps; + end + + %% RAMON Converstion Methods + function ramonObj = toGeneric(this) + ramonObj = RAMONGeneric(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + ramonObj = this.setRAMONVolumeProperties(ramonObj); + end + + function handle = clone(this, option) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + % + % Optionally pass in 'novoxels' to perform copy without voxel + % data + % + % ex: new_obj = my_obj.clone('novoxels'); + + if ~exist('option','var'); + option = []; + end + + + % Instantiate new object of the same class. + handle = feval('RAMONOrganelle'); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Volume + handle = this.volumeCloneHelper(handle, option); + % Class + handle.setClass(this.class); + handle.setParentSeed(this.parentSeed); + handle.setSeeds(this.seeds); + end + end +end + diff --git a/api/matlab/ramon/RAMONSeed.m b/api/matlab/ramon/RAMONSeed.m new file mode 100644 index 0000000..a5fc25d --- /dev/null +++ b/api/matlab/ramon/RAMONSeed.m @@ -0,0 +1,199 @@ +classdef RAMONSeed < RAMONBase + % RAMONSeed - data type to store information about a seed point. + % Constructor available to initialize seed to state. For any field not + % desired to initialize use []. If the field is omitted a default + % value will be assigned + % + % + % Example + % seed1 = RAMONSeed(); default seed + % seed1 = RAMONSeed(...); + % seed1 = RAMONSeed(position,cubeOrientation,parentSeed,sourceEntity,... + % id,confidence,status, dynamicMetadata); + % + % Seed properties: + % seed.position - x,y,z global coordinates [x,y,z] - type:integer + % seed.cubeOrientation - Options are: +x,-x,+y,-y,+z,-z,c which specify where + % a cube would be created in relation to the seed - type: + % RAMONCubeOriencation + % seed.parentSeed - Parent seed ID that derived this child seed (only + % non-null if a child seed) - type: integer + % seed.sourceEntity - ID of annotation entity that derived the parent + % seed - type: integer + % seed.id - unique identifier for the seed - type: integer + % seed.confidence - Value 0-1 indicating confidence in annotation - + % type: double + % seed.status - Current state of the seed. 0 = new, 1 = processing, + % 2 = processed/complete - type: eRAMONAnnoStatus + % seed.dynamicMetadata - Cell array of key-value pairs + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + position = [0 0 0]; % x,y,z coordinates (global frame) + cubeOrientation = eRAMONCubeOrientation.centered; % Where the seed is in relation to a cube it would generate. Options are: +x,-x,+y,-y,+z,-z,c + parentSeed = []; % id of pervious generation seed + sourceEntity = []; % ID of entity that derived this seed (first generation seeds only) + end + + methods + function this = RAMONSeed(varargin) + % RAMONSeed(); + % RAMONSeed(...); + % RAMONSeed(position,cubeOrientation,parentSeed,sourceEntity,id,... + % confidence,status,metadata,author); + + this.setPosition([0 0 0]); + this.setCubeOrientation(eRAMONCubeOrientation.centered); + this.setParentSeed([]); + this.setSourceEntity([]); + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + this.setAuthor('unspecified'); + + % Assign fields based on input arguments + if nargin > 0 + this.setPosition(varargin{1}); + end + if nargin > 1 + this.setCubeOrientation(varargin{2}); + end + if nargin > 2 + this.setParentSeed(varargin{3}); + end + if nargin > 3 + this.setSourceEntity(varargin{4}); + end + if nargin > 4 + this.setId(varargin{5}); + end + if nargin > 5 + this.setConfidence(varargin{6}); + end + if nargin > 6 + this.setStatus(varargin{7}); + end + if nargin > 7 + [numKey, col] = size(varargin{8}); + if col ~= 2 + ex = MException('RAMONPointAnnotation:MetadataFormatInvalid','The dynamic metadata format is invalid. Check documentation.'); + throw(ex); + end + data = varargin{8}; + for ii = 1:numKey + this = this.addDynamicMetadata(data{ii,1},data{ii,2}); + end + end + if nargin > 8 + this.setAuthor(varargin{9}); + end + if nargin > 9 + ex = MException('RAMONSeed:TooManyArguments','Too many attributes, see documentation for use.'); + throw(ex); + end + end + + + %% Set Functions to validate data + + function this = setPosition(this,position) + % This member function sets the seed's position field. If position is null + % it will be set to default [0 0 0] + + if isempty(position) + % If position is empty set to default + position = [0 0 0]; + end + + validateattributes(position,{'numeric'},{'finite','integer','nonnan','real','size',[1,3]}); + this.position = position; + end + + function this = setCubeOrientation(this,orientation) + % This member function sets the seed's orientation field. + % If orientation is null it will be set to default 'c' for center + + if isempty(orientation) + % If orientation is empty set to default + this.cubeOrientation = eRAMONCubeOrientation.centered; + end + + if isa(orientation, 'eRAMONCubeOrientation') + % Is of Type eRAMONCubeOrientation + + else + % Is not of type AnnotationStatus + validateattributes(orientation,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + orientation = eRAMONCubeOrientation(orientation); + catch ME + rethrow(ME); + end + end + + this.cubeOrientation = orientation; + end + + function this = setParentSeed(this,parentSeed) + % This member function sets the seed's parentSeed field. + + validateattributes(parentSeed,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + this.parentSeed = parentSeed; + end + + function this = setSourceEntity(this,sourceEntity) + % This member function sets the seed's sourceEntity field. + + validateattributes(sourceEntity,{'numeric'},{'finite','positive','integer','nonnan','real'}); + this.sourceEntity = sourceEntity; + end + + %% RAMON Converstion Methods + function ramonObj = toGeneric(this) + ramonObj = RAMONGeneric(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + end + + function handle = clone(this,~) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Class + handle.setPosition(this.position); + handle.setCubeOrientation(this.cubeOrientation); + handle.setParentSeed(this.parentSeed); + handle.setSourceEntity(this.sourceEntity); + end + + end + + +end + diff --git a/api/matlab/ramon/RAMONSegment.m b/api/matlab/ramon/RAMONSegment.m new file mode 100644 index 0000000..89c51a5 --- /dev/null +++ b/api/matlab/ramon/RAMONSegment.m @@ -0,0 +1,275 @@ +classdef RAMONSegment < RAMONVolume + % RAMONSegment - data type to store a segment of a neuron + % Constructor available to initialize segment. For any field not + % desired to initialize use []. If the field is omitted a default + % value will be assigned + % + + % SEGMENT- Annotation representing a segment of the membrane + % boundary of a neuron + % ID: Database assigned unique value + % Neuron: Neuron ID connected to this segment + % Synapses: Synapse IDs connected to this segment + % Parent Seed: Seed that resulted in the creation of this segment + % Organelles: Associated organelle IDs + % Class: axon, dendrite, soma, unknown + % Voxel Set: Voxels representing this segment + % Status: integer field that can be used to set status information + % Confidence: value 0-1 representing confidence in accuracy of annotation + % Dynamic Metadata: Key-value pairs + + % Example + % seg1 = RAMONSegment(volumeData, xyzOffset, resolution, class,... + % synapses, organelles, parentSeed, id, confidence, status,... + % dynamicMetadata, author); + % + % Required Fields: status, class + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + + class = []; %eRAMONSegmentClass + neuron = []; % neuron.id + synapses = []; %array (synapse.id) + organelles = []; %array (organelle.id) + parentSeed = []; %seed.id + end + + methods + function this = RAMONSegment(varargin) + % RAMONSegment(); + % RAMONSegment(...); + % RAMONSegment(volumeData, dataFormat, xyzOffset, resolution, class,... + % neuron, synapses, organelles, parentSeed, id, confidence, status,... + % dynamicMetadata, author); + + this.setCutout([]); + this.setXyzOffset([]); + this.setResolution([]); + this.setClass(eRAMONSegmentClass.unknown); + this.setNeuron([]); + this.setSynapses([]); + this.setOrganelles([]); + this.setParentSeed([]); + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + this.setAuthor('unspecified'); + + % Assign fields based on input arguments + if nargin == 1 + ex = MException('RAMONSegment:MissingDataFormat',... + 'When instantiating a RAMONSegment object you must include the "dataFormat" argument indicating the voxel data representation.'); + throw(ex); + end + if nargin > 1 + this = this.setDataFormat(varargin{2}); + this = this.initVoxelData(varargin{1}, this.dataFormat); + end + if nargin > 2 + this.setXyzOffset(varargin{3}); + end + if nargin > 3 + this.setResolution(varargin{4}); + end + if nargin > 4 + this.setClass(varargin{5}); + end + if nargin > 5 + this.setNeuron(varargin{6}); + end + if nargin > 6 + this.setSynapses(varargin{7}); + end + if nargin > 7 + this.setOrganelles(varargin{8}); + end + if nargin > 8 + this.setParentSeed(varargin{9}); + end + if nargin > 9 + this.setId(varargin{10}); + end + if nargin > 10 + this.setConfidence(varargin{11}); + end + if nargin > 11 + this.setStatus(varargin{12}); + end + if nargin > 12 + this.metadataConstructHelper(varargin{13}) + end + if nargin > 13 + this.setAuthor(varargin{14}); + end + if nargin > 14 + ex = MException('RAMONSegment:TooManyArguments','Too many attributes, see documentation for use.'); + throw(ex); + end + end + + %% Set Functions to validate data + + %% Segment class + function this = setClass(this,type) + % This member function sets the segment class field. + + if isempty(type) + % If type is empty set to default + this.class = eRAMONSegmentClass.unknown; + end + + if isa(type, 'eRAMONSegmentClass') + % Is of Type eRAMONSegmentClass + + else + % Is not of type eRAMONSegmentClass + validateattributes(type,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + type = eRAMONSegmentClass(uint32(type)); + catch ME + rethrow(ME); + end + end + + this.class = type; + end + + %% SET NEURON FIELD + function this = setNeuron(this,neu) + % This member function sets the segment's linked neuron field. + if ~isempty(neu) + validateattributes(neu,{'numeric'},{'scalar','finite','nonnegative','nonnan','real'}); + end + this.neuron = neu; + end + + %% SET SYNAPSE FIELD + function this = setSynapses(this,syn) + % This member function sets the segment's linked synapses field. + if ~isempty(syn) + validateattributes(syn,{'numeric'},{'finite','nonnegative','nonnan','real'}); + end + + if size(syn,1) > 1 + ex = MException('RAMONSegment:InvalidFormat','Synapses should be a 1xN array'); + throw(ex); + end + + this.synapses = syn; + end + + function this = removeSynapses(this, synapseID) + + validateattributes(synapseID,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + + for ii = 1:length(synapseID) + % check if key exists already + if isempty(find(ismember(this.synapse, synapseID(ii))==1, 1)) + warning('RAMONSegment:IDNotFound', 'ID not found in synapse. Nothing was removed.') + else + ind = find(ismember(this.synapses, synapseID(ii))==1, 1); + this.synapses(ind) = []; + end + end + end + + %% SET ORGANELLE FIELD + + function this = setOrganelles(this,org) + % This member function sets the segment's linked organelle field. + if ~isempty(org) + validateattributes(org,{'numeric'},{'finite','nonnegative','nonnan','real'}); + end + if size(org,1) > 1 + ex = MException('RAMONSegment:InvalidFormat','Organelles should be a 1xN array'); + throw(ex); + end + + this.organelles = org; + end + + function this = removeOrganelles(this, organelleID) + + validateattributes(organelleID,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + + % check if key exists already + for ii = 1:length(organelleID) + if isempty(find(ismember(this.organelles, organelleID(ii))==1, 1)) + warning('RAMONSegment:IDNotFound', 'ID not found in organelles. Nothing was removed.') + else + ind = find(ismember(this.organelles, organelleID(ii))==1, 1); + this.organelles(ind) = []; + end + end + end + + %% SET PARENT SEED FIELD + + function this = setParentSeed(this,ps) + % This member function sets the segment's linked parent seed field. + + if ~isempty(ps) + validateattributes(ps,{'numeric'},{'scalar','finite','nonnegative','nonnan','real'}); + end + this.parentSeed = ps; + end + + %% RAMON Converstion Methods + function ramonObj = toGeneric(this) + ramonObj = RAMONGeneric(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + ramonObj = this.setRAMONVolumeProperties(ramonObj); + end + + function handle = clone(this,option) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + % + % Optionally pass in 'novoxels' to perform copy without voxel + % data + % + % ex: new_obj = my_obj.clone('novoxels'); + + if ~exist('option','var'); + option = []; + end + + % Instantiate new object of the same class. + handle = RAMONSegment(); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Volume + handle = this.volumeCloneHelper(handle, option); + % Class + handle.setClass(this.class); + handle.setNeuron(this.neuron); + handle.setSynapses(this.synapses); + handle.setOrganelles(this.organelles); + handle.setParentSeed(this.parentSeed); + end + end +end \ No newline at end of file diff --git a/api/matlab/ramon/RAMONSynapse.m b/api/matlab/ramon/RAMONSynapse.m new file mode 100644 index 0000000..2d21c1e --- /dev/null +++ b/api/matlab/ramon/RAMONSynapse.m @@ -0,0 +1,314 @@ +classdef RAMONSynapse < RAMONVolume + % RAMONSynapse - data type to store information about a synapse + % Constructor available to initialize synapse. For any field not + % desired to initialize use []. If the field is omitted a default + % value will be assigned + % + % Example + % s1 = RAMONSynapse(volumeData, refCoordinates, synapseType,weight,... + % segments,id,confidence,status,dynamicMetadata); + % + % Required Fields: status, synapseType + % + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + synapseType = eRAMONSynapseType.unknown; % eRAMONSynapseType + weight = []; % double + segments = containers.Map('KeyType', 'uint32','ValueType','uint32'); % Map [segmentID, RAMONSegementType] + seeds = []; % array 1xN (seedID) + end + + methods + function this = RAMONSynapse(varargin) + % RAMONSynapse(); + % RAMONSynapse(...); + % RAMONSynapse(volumeData, isCutout, xyzOffset, resolution, synapseType,... + % weight, segments, seeds, id, confidence,status,... + % dynamicMetadata, author); + + % "overloaded" constructor. You can include any number of args + % to init the object during constructor. Any field you want to + % skip over use "[]" + + % Default props + this.setCutout([]); + this.setXyzOffset([]); + this.setResolution([]); + this.setSynapseType(eRAMONSynapseType.unknown); + this.setWeight([]); + this.clearSegments(); + this.setSeeds([]); + this.setId([]); + this.setConfidence(1); + this.setStatus(eRAMONAnnoStatus.unprocessed); + this.clearDynamicMetadata(); + + if nargin == 1 + ex = MException('RAMONSynapse:MissingDataFormat',... + 'When instantiating a RAMONSynapse object you must include the "dataFormat" argument indicating the voxel data representation.'); + throw(ex); + end + if nargin > 1 + this = this.setDataFormat(varargin{2}); + this = this.initVoxelData(varargin{1}, this.dataFormat); + end + if nargin > 2 + this.setXyzOffset(varargin{3}); + end + if nargin > 3 + this.setResolution(varargin{4}); + end + if nargin > 4 + this.setSynapseType(varargin{5}); + end + if nargin > 5 + this.setWeight(varargin{6}); + end + if nargin > 6 + this.segmentConstructHelper(varargin{7}) + end + if nargin > 7 + this.setSeeds(varargin{8}); + end + if nargin > 8 + this.setId(varargin{9}); + end + if nargin > 9 + this.setConfidence(varargin{10}); + end + if nargin > 10 + this.setStatus(varargin{11}); + end + if nargin > 11 + this.metadataConstructHelper(varargin{12}) + end + if nargin > 12 + this.setAuthor(varargin{13}); + end + if nargin > 13 + ex = MException('RAMONSynapse:TooManyArguments','Too many attributes, see documentation for use.'); + throw(ex); + end + end + + + %% Set Functions to validate data + + function this = setSynapseType(this,type) + % This member function sets the seed's synapse type field. + + if isempty(type) + % If type is empty set to default + this.synapseType = eRAMONSynapseType.unknown; + end + + if isa(type, 'eRAMONSynapseType') + % Is of Type eRAMONSynapseType + + else + % Is not of type eRAMONSynapseType + validateattributes(type,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + type = eRAMONSynapseType(type); + catch ME + rethrow(ME); + end + end + + this.synapseType = type; + end + + function this = setWeight(this,weight) + % This member function sets the synapse's wieght field. + + validateattributes(weight,{'numeric'},{'finite','nonnegative','nonnan','real'}); + this.weight = weight; + end + + function this = setSegments(this,segmentMap) + % This member function sets the seed's segments field with + % an existing containers.Map file + + if ~isempty(segmentMap) + % Check argument + if ~isa(segmentMap,'containers.Map') + ex = MException('RAMONSynapse:InvalidType','"segments" must be of type containers.Map'); + throw(ex); + end + + if ~strcmpi(segmentMap.KeyType,'uint32') + ex = MException('RAMONSynapse:KeyType','"segments" container.Map keys must be uint32'); + throw(ex); + end + if ~strcmpi(segmentMap.ValueType,'uint32') + ex = MException('RAMONSynapse:ValueType','"segments" container.Map values must be uint32'); + throw(ex); + end + end + + this.segments = segmentMap; + end + + function this = addSegment(this,segmentID, segmentFlowDir) + + validateattributes(segmentID,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + + if ~isa(segmentFlowDir, 'eRAMONFlowDirection') + try + eRAMONFlowDirection(segmentFlowDir); + catch ME + rethrow(ME); + end + else + segmentFlowDir = int32(segmentFlowDir); + end + + % add key + % check if id exists already + errFlag = 0; + try + this.segments(segmentID); + errFlag = 1; + catch %#ok + % it doesn't so create it + this.segments(segmentID) = int32(segmentFlowDir); + end + if errFlag == 1 + ex = MException('RAMONSynapse:IDExists',sprintf('Segment ID "%d" already exists in synapse. Cannot Add to synapse',segmentID)); + throw(ex); + end + end + + function this = updateSegment(this,segmentID, segmentFlowDir) + + validateattributes(segmentID,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + + if ~isa(segmentFlowDir, 'eRAMONFlowDirection') + try + eRAMONFlowDirection(segmentFlowDir); + catch ME + rethrow(ME); + end + else + segmentFlowDir = int32(segmentFlowDir); + end + + % update key + this.segments(segmentID) = int32(segmentFlowDir); + end + + + function this = removeSegment(this, segmentId) + + validateattributes(segmentId,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + + this.segments.remove(segmentId); + end + + function this = clearSegments(this) + % remove all segement data + this.segments = containers.Map('KeyType', 'uint32','ValueType','uint32'); + end + + function this = setSeeds(this,seeds) + % This member function sets the synapses seeds field. + + validateattributes(seeds,{'numeric'},{'finite','nonnegative','nonnan','real','integer'}); + this.seeds = seeds; + end + + + %% RAMON Converstion Methods + function ramonObj = toGeneric(this) + ramonObj = RAMONGeneric(); + ramonObj = this.setRAMONBaseProperties(ramonObj); + ramonObj = this.setRAMONVolumeProperties(ramonObj); + end + + function handle = clone(this,option) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + % + % Optionally pass in 'novoxels' to perform copy without voxel + % data + % + % ex: new_obj = my_obj.clone('novoxels'); + + if ~exist('option','var'); + option = []; + end + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Volume + handle = this.volumeCloneHelper(handle,option); + % Class + handle.setSynapseType(this.synapseType); + handle.setWeight(this.weight); + handle.setSegments(this.segments); + handle.setSeeds(this.seeds); + end + end + + %% Private helper to clean up code + methods(Access = private) + function segmentConstructHelper(this,segmentMat) + + if isempty(segmentMat) + this.clearSegments(); + else + if size(segmentMat,2) ~= 2 + ex = MException('RAMONSynapse:SegmentsFormatInvalid','Segment init format is invalid. Should be Nx2 uint32 array'); + throw(ex); + end + for ii = 1:size(segmentMat,1) + this.addSegment(segmentMat(ii,1),segmentMat(ii,2)); + end + end + end + + function metadataconstructhelper(this,var) + + [numkey, col] = size(var); + if numkey ~= 0 + if col ~= 2 + ex = MException('RAMONSynapse:MetadataFormatInvalid','The init dynamic metadata format is invalid. Should be Nx2 cell array.'); + throw(ex); + end + data = var; + for ii = 1:numkey + this = this.addDynamicMetadata(data{ii,1},data{ii,2}); + end + else + this.clearDynamicMetadata(); + end + end + end + +end + diff --git a/api/matlab/ramon/RAMONVolume.m b/api/matlab/ramon/RAMONVolume.m new file mode 100644 index 0000000..91e1428 --- /dev/null +++ b/api/matlab/ramon/RAMONVolume.m @@ -0,0 +1,599 @@ +classdef RAMONVolume < RAMONBase + % RAMONVolume - class to store voxel data + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties(SetAccess = 'private', GetAccess = 'public') + xyzOffset = []; + resolution = []; % Level in the Database Resolution Hierarchy + + name = 'Volume1'; % Descriptive display name to give the cube + sliceDisplayIndex = 1; % Contains Z index for slice to display + + dataFormat = []; % Property indicating the format of the data property + % eRAMONDataFormat.dense - dense NxMxKx.. voxel cutout + % eRAMONDataFormat.voxelList - Nx3 XYZ coordinates + % of labled voxels + % eRAMONDataFormat.boundingBox - 1x3 X,Y,Z span (from xyzOffset start point) + data = []; % Property containing voxel data + + % This indicates what datatype this RAMONVolume represents. Since + % developers often just make everything doubles in MATLAB + % autodetecting this is unreliable. + % DEPRICATED. WILL BE REMOVED IN FUTURE VERSIONS + uploadType = [] + % DEPRICATED. WILL BE REMOVED IN FUTURE VERSIONS + + % This indicates what datatype this RAMONVolume represents. Since + % developers often just make everything doubles in MATLAB + % autodetecting this is unreliable. This is automatically synced automatically with + % the database project type when possible. + dataType = [] + end + + methods + + function this = RAMONVolume(varargin) + % this = RAMONVolume() + % this = RAMONVolume(data, dataFormat) + % this = RAMONVolume(data, dataFormat, [ref coords]) + % this = RAMONVolume(data, dataFormat, [ref coords], resolution) + + this.setCutout([]); + this.setXyzOffset([]); + this.setResolution([]); + + if nargin == 1 + ex = MException('RAMONVolume:MissingDataFormat',... + 'When instantiating a RAMONVolume object you must include the "dataFormat" argument indicating the voxel data representation.'); + throw(ex); + end + if nargin > 1 + this = this.setDataFormat(varargin{2}); + this = this.initVoxelData(varargin{1}, this.dataFormat); + end + if nargin > 2 + this = this.setXyzOffset(varargin{3}); + end + if nargin > 3 + this = this.setResolution(varargin{4}); + end + if nargin > 4 + ex = MException('RAMONVolume:TooManyArguments','Too many properties for initialization, see documentation for use.'); + throw(ex); + end + end + + %% Setter Functions for property validation + + function this = setCutout(this,cube) + % this = setCutout(this,cube) + % + % This member function sets the data field in cutout format. + % A volume object can be 1x1 to NxNxN, representing a single + % point to a 3D volume. + % If an annotation, a value of 0 represents unlabled space. + % Any other nonnegative integer value represents a labeled region + % If non-integer data is probabilities or image data. + + validateattributes(cube,{'numeric','logical'},{'finite','nonnegative','nonnan','real'}); + + this.data = cube; + this.dataFormat = eRAMONDataFormat.dense; + end + + function this = setVoxelList(this,voxels) + % this = setVoxelList(this,cube) + % + % This member function sets the data field in voxel list format + % A volume object can be 1x1 to NxNxN, representing a single + % point to a 3D volume. + % A voxel value of 0 represents unlabled space. Any other + % nonnegative integer value represents a labeled region + + if ~isempty(voxels) + validateattributes(voxels,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + + if ndims(voxels) ~= 2 + ex = MException('RAMONVolume:InvalidVoxelList','Voxel list should be an Nx3 matrix.'); + throw(ex); + end + + if size(voxels,2) ~= 3 + ex = MException('RAMONVolume:InvalidVoxelList','Voxel list should be an Nx3 matrix.'); + throw(ex); + end + end + + this.data = voxels; + this.dataFormat = eRAMONDataFormat.voxelList; + end + + function this = setBoundingBoxSpan(this,bboxSpan) + % this = this.setBoundingBoxSpan([X Y Z]) + % + % This member function sets the data field in bounding box + % span format (1x3 - X,Y,Z). The span is related to the property + % xyzOffset which represents the starting corner of the the bounding box + % that contains the annotation. + % + % If the query IS NOT restricted to a cutout the entire object + % will be contained by the bounding box. + % If the query IS restricted to a cutout the object may or may + % not be contained entirely in the bounding box. + + if ~isempty(bboxSpan) + validateattributes(bboxSpan,{'numeric'},{'finite','nonnegative','integer','nonnan','real','size', [1,3]}); + end + + this.data = bboxSpan; + this.dataFormat = eRAMONDataFormat.boundingBox; + end + + function this = setXyzOffset(this,coord) + % this = setXyzOffset(this,[x y z]) + % + % This member function sets the volume object xyzOffset field. + if ~isempty(coord) + validateattributes(coord,{'numeric'},{'finite','nonnegative','integer','nonnan','real','size', [1 3]}); + end + this.xyzOffset = coord; + end + + function this = setResolution(this,res) + % this = setResolution(this,resolution) + % + % This member function sets the volume object resolution field. + + if ~isempty(res) + validateattributes(res,{'numeric'},{'finite','nonnegative','integer','nonnan','real','scalar'}); + end + this.resolution = res; + end + + function this = setName(this,name) + % this = setName(this,'string name') + % + % This member function sets the volume object name field. + + if isa(name, 'char') + this.name = name; + else + ex = MException('RAMONVolume:NameTypeError','Name field must be a character string'); + throw(ex); + end + end + + function this = setUploadType(this,type) + % This member function sets the volume object upload type field. + % The data will be converted to this type on upload! If there + % is a mismatch between your data and the selected type + % information could be lost. If there is a mismatch between the + % database data type (specified by the token) and the upload + % type the upload will fail. + + error('RAMONVolume:MethodDepricated', 'setUploadType has been depricated. Use setDataType and eRAMONDataType instead. Note, this property now is automatically set by the OCP class in most cases.') + + end + + function this = setDataType(this,type) + % This member function sets the volume object datatype field. + % The data will be converted to this type on upload! If there + % is a mismatch between your data and the selected type + % information could be lost. If there is a mismatch between the + % database data type (specified by the token) and the upload + % type the upload will fail. + + if isa(type, 'eRAMONDataType') + % Is of Type eRAMONDataType + + else + % Is not of type eRAMONDataType + validateattributes(type,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + type = eRAMONDataType(type); + catch ME + rethrow(ME); + end + end + + this.dataType = type; + end + + + function handle = clone(this,option) + % Perform a deep copy because these are handles and not objects + % default using the = operator just copies the handle and not + % the underlying object. + % + % Optionally pass in 'novoxels' to perform copy without voxel + % data + % + % ex: new_obj = my_obj.clone('novoxels'); + + if ~exist('option','var'); + option = []; + end + + % Instantiate new object of the same class. + handle = feval(class(this)); + + % Copy all properties. + % Base + handle = this.baseCloneHelper(handle); + % Volume + handle = this.volumeCloneHelper(handle,option); + + end + + + %% Coordinate Transform Methods + % Remember Matlab is 1 indexed! + + function globalCoord = local2Global(this,localCoord) + % This method returns a given local coordinate (coordinate + % system of the cutout) in global coordinates (coordinate + % system of the database at the current resolution) + + if this.dataFormat ~= eRAMONDataFormat.dense + error('RAMONVolume:DataFormatNotSupported',... + 'Local2Global coordinate transform only applies to dense cutouts'); + end + + gx = localCoord(:,1) + this.xyzOffset(1) - 1; + gy = localCoord(:,2) + this.xyzOffset(2) - 1; + gz = localCoord(:,3) + this.xyzOffset(3) - 1; + + globalCoord = [gx gy gz]; + end + + function localCoord = global2Local(this,globalCoord) + % This method returns a given global coordinates (coordinate + % system of the database at the current resolution) in local + % coordinates (coordinate system of the cutout) + + if this.dataFormat ~= eRAMONDataFormat.dense + error('RAMONVolume:DataFormatNotSupported',... + 'Global2Local coordinate transform only applies to dense cutouts'); + end + + lx = globalCoord(:,1) - this.xyzOffset(1) + 1; + ly = globalCoord(:,2) - this.xyzOffset(2) + 1; + lz = globalCoord(:,3) - this.xyzOffset(3) + 1; + + if sum(lx < 1) || sum(lx > size(this.data,2)) ||... + sum(ly < 1) || sum(ly > size(this.data,1)) ||... + sum(lz < 1) || sum(lz > size(this.data,3)) + + warning('RAMONVolume:GCOutsideVolume',... + 'At least 1 of the provided global coordinates are outside the local volume.'); + end + + localCoord = [lx ly lz]; + end + + %% Resolution Transform Methods + %TODO + + %% VoxelList-Cutout Transform Methods + function this = toVoxelList(this) + % Convert the data to voxel list representation + switch this.dataFormat + case eRAMONDataFormat.dense + if unique(this.data) > 2 + error('RAMONVolume:TooManyObjects',... + 'You can only represent a single RAMON object as a voxel list. Failed to convert annotation database cutout to voxel list'); + end + + [y, x, z] = ind2sub(size(this.data), find(this.data ~= 0)); + voxels = cat(2,x,y,z) + repmat(this.xyzOffset-1,length(x),1); + + this.setVoxelList(voxels); + this.setXyzOffset([]); + + case eRAMONDataFormat.voxelList + % already in voxel list format + + case eRAMONDataFormat.boundingBox + error('RAMONVolume:NotApplicable',... + 'There is no voxel data stored with the bounding box data type. Cannot convert to Voxel List.'); + + otherwise + error('Unsupported data format'); + end + end + + function this = toCutout(this) + % Convert the data to voxel list representation + switch this.dataFormat + case eRAMONDataFormat.dense + % already in cutout form + + case eRAMONDataFormat.voxelList + this.setXyzOffset(min(this.data,[],1)); + + this.data = this.data - repmat(this.xyzOffset - 1,size(this.data,1),1); + + maxVal = max(this.data,[],1); + + cutout = zeros(maxVal(2),maxVal(1),maxVal(3)); + + inds = sub2ind(size(cutout),... + this.data(:,2),... + this.data(:,1),... + this.data(:,3)); + if isempty(this.id) + cutout(inds) = 1; + else + cutout(inds) = this.id; + end + + this.setCutout(cutout); + + case eRAMONDataFormat.boundingBox + error('RAMONVolume:NotApplicable',... + 'There is no voxel data stored with the bounding box data type. Cannot convert to cutout'); + + otherwise + error('Unsupported data format'); + end + end + + function this = toBoundingBox(this) + ex = MException('RAMONVolume:NotSupported','This method is not yet supported'); + throw(ex); + end + + %% Create image query based on volume object. + function queryObj = toImageDenseQuery(this) + % If the volume object is holding a cutout, give a query that + % would retrieve an image volume + if this.dataFormat == eRAMONDataFormat.dense + queryObj = OCPQuery(eOCPQueryType.imageDense); + [y, x, z] = size(this.data); + queryObj.setCutoutArgs([this.xyzOffset(1) this.xyzOffset(1) + x],... + [this.xyzOffset(2) this.xyzOffset(2) + y],... + [this.xyzOffset(3) this.xyzOffset(3) + z],... + this.resolution); + else + queryObj = []; + end + end + + function queryObj = toAnnoDenseQuery(this) + % If the volume object is holding a cutout, give a query that + % would retrieve an annotation volume + if this.dataFormat == eRAMONDataFormat.dense + queryObj = OCPQuery(eOCPQueryType.annoDense); + [y, x, z] = size(this.data); + queryObj.setCutoutArgs([this.xyzOffset(1) this.xyzOffset(1) + x],... + [this.xyzOffset(2) this.xyzOffset(2) + y],... + [this.xyzOffset(3) this.xyzOffset(3) + z],... + this.resolution); + else + queryObj = []; + end + end + + %% Volume Support Methods + function dims = size(this, dim) + % dims = SIZE(obj) + % dims = SIZE(obj, dim) + % + % Returns the dimension of the RAMONVolume + % See SIZE for more information on usage + % + + if exist('dim','var') + dims = size(this.data,dim); + else + dims = size(this.data); + end + + end + + function count = voxelCount(this) + % Returns the number of voxels stored in the RAMONVolume object + if isempty(this.dataFormat) + count = 0; + else + switch this.dataFormat + case eRAMONDataFormat.dense + [x,y,z] = size(this.data); + count = x*y*z; + + case eRAMONDataFormat.voxelList + count = size(this.data, 1); + + case eRAMONDataFormat.boundingBox + error('RAMONVolume:NotApplicable',... + 'There is no voxel data stored with the bounding box data type. Cannot compute voxel count.'); + + otherwise + error('Unsupported data format'); + end + end + end + + + function h = image(this, slice, pos) + if nargin < 2, + slice = []; + end + + if nargin < 3, + pos = []; + end + + if isempty(pos), + pos = [100,100]; + end + + if isempty(slice), + slice = this.sliceDisplayIndex; + else + this.sliceDisplayIndex = slice; + end + + if slice > this.size(3) || slice < 1 + warning('RAMONVolume:SliceInvalid',... + 'Slice number %d out of range. %d slices available\n',... + slice,this.size(3)); + slice = 1; + end + + h = VolumeImageBox(this.data, this.xyzOffset, slice, pos, this.name, this.dataType); + end + + + function notify_dataupdate_(this) + % This function is called when the data within the object is updated. + % It is up to the user to override this function for additional + % functionality. + % + + end + + function notify_dimensionupdate_(this) + % This function is called the the data dimensions are updated by a + % class member function. It is up to the user to override this + % function for additional functionality. + + end + end + + %% Methods - Utility Functions + methods(Access = protected) + function this = initVoxelData(this, data, format) + % This method is used by the constructor to properly set the data and format + + switch format + case eRAMONDataFormat.dense + this.setCutout(data); + + case eRAMONDataFormat.voxelList + this.setVoxelList(data); + + case eRAMONDataFormat.boundingBox + this.setBoundingBoxSpan(data); + + otherwise + ex = MException('RAMONVolume:UnsupportedFormat','The specified data format is unsupported: %s', char(format)); + throw(ex); + end + end + + function handle = volumeCloneHelper(this, handle, option) + % Copy all properties. + handle.setResolution(this.resolution); + if ~exist('option','var') + % Normal copy + switch this.dataFormat + case eRAMONDataFormat.boundingBox + handle.setBoundingBoxSpan(this.data); + + case eRAMONDataFormat.dense + handle.setCutout(this.data); + + case eRAMONDataFormat.voxelList + handle.setVoxelList(this.data); + end + + handle.setXyzOffset(this.xyzOffset); + handle.setDataType(this.dataType); + else + % copy with option + + if isempty(option) + % you passed in [] for the option which means do normal + % copy + % Normal copy + switch this.dataFormat + case eRAMONDataFormat.boundingBox + handle.setBoundingBoxSpan(this.data); + + case eRAMONDataFormat.dense + handle.setCutout(this.data); + + case eRAMONDataFormat.voxelList + handle.setVoxelList(this.data); + end + + handle.setXyzOffset(this.xyzOffset); + handle.setDataType(this.dataType); + elseif strcmpi(option,'novoxels') == 0 + error('RAMONVolume:InvalidCloneOption',... + 'Currently "novoxels" is the only supported option'); + end + end + + end + + function ramonObj = setRAMONVolumeProperties(this,ramonObj) + % RAMONVolume + switch this.dataFormat + case eRAMONDataFormat.dense + ramonObj.setCutout(this.data); + + case eRAMONDataFormat.voxelList + ramonObj.setVoxelList(this.data); + + case eRAMONDataFormat.boundingBox + ramonObj.setBoundingBoxSpan(this.data); + + otherwise + ex = MException('RAMONVolume:UnsupportedFormat','The specified data format is unsupported: %s', char(format)); + throw(ex); + end + + ramonObj.setXyzOffset(this.xyzOffset); + ramonObj.setResolution(this.resolution); + ramonObj.setDataFormat(this.dataFormat); + end + + function this = setDataFormat(this,format) + % this = setDataFormat(this,true) + % + % This member function sets the volume object dataFormat field. + if isempty(format) + ex = MException('RAMONVolume:FormatMissing','No data format specified.'); + throw(ex); + end + + if isa(format, 'eRAMONDataFormat') + % Is of Type eRAMONDataFormat + + else + % Is not of type eRAMONSegmentClass + validateattributes(format,{'numeric'},{'finite','nonnegative','integer','nonnan','real'}); + try + format = eRAMONDataFormat(uint32(format)); + catch ME + rethrow(ME); + end + end + + this.dataFormat = format; + end + end + +end + diff --git a/api/matlab/ramon/basic_viewer/ImageSelection.class b/api/matlab/ramon/basic_viewer/ImageSelection.class new file mode 100644 index 0000000..cfac41d Binary files /dev/null and b/api/matlab/ramon/basic_viewer/ImageSelection.class differ diff --git a/api/matlab/ramon/basic_viewer/KeyPressEvent.m b/api/matlab/ramon/basic_viewer/KeyPressEvent.m new file mode 100644 index 0000000..463bc15 --- /dev/null +++ b/api/matlab/ramon/basic_viewer/KeyPressEvent.m @@ -0,0 +1,29 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory +% All Rights Reserved. +% Contact the JHU/APL Office of Technology Transfer for any additional rights. +% www.jhuapl.edu/ott +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +classdef KeyPressEvent < event.EventData + properties + Character = 0; + end + methods + function eventData = KeyPressEvent(value) + eventData.Character = value; + end + end +end diff --git a/api/matlab/ramon/basic_viewer/VolumeImageBox.m b/api/matlab/ramon/basic_viewer/VolumeImageBox.m new file mode 100644 index 0000000..674f0ad --- /dev/null +++ b/api/matlab/ramon/basic_viewer/VolumeImageBox.m @@ -0,0 +1,868 @@ +classdef VolumeImageBox < handle + % An VolumeImageBox is a display window that provides an image display and preview + % box with ability to select portion of image to view and resize (zoom) on the + % area of interest. + % + % Keys Overriden: + % ================= + % =/- zoom in and out by a factor of 2 + % + % Mouse events overriden: + % ====================== + % scroll wheel zooms in and out by 5% + % + % + % obj = ImageBox(img, pos) + % obj = ImageBox(img, pos, title) + % + % Creates an ImageBox object do display the provided image. + % + % img = NxM or NxMx3 (Grayscale or RGB) image to display. + % pos = position of lower-left corner of window. + % win_title = Title of window (default 'Image') + % + % Added new modes to (S) save all slices and (M) to make a movie + % Sped up overlay rendering for simple use cases. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties + hFig = []; % Contains handle to figure + hAx = []; % Contains handle to axes + hImg = []; % Contains handle to image + hScroll = []; % Contains handle to a scrollpane + hPlotAx = []; % Contains handle to last plot figure + + zoom_box = []; % Contains ZoomBox object + + img_dims = []; % Contains dimension of image + + img_data = []; % Contains a uint8 version of the image + + dataCube = []; % Conatins the data cube loaded to be displayed + datatype = []; % Contains the eRAMONDataType enumeration of data stored + dataOffset = []; % Contains the xyzOffset of the data cube + sliceIndex = []; % Contains the index of the slice currently displayed + + associated_cube = {}; % Contains cell array of cubes associated with this Image (e.g. spectra) + associated_key = {}; % List of keys specifying data + + roi_list = {}; % List of ROI's created + invert_mode = 0; % State of invert mode (0 = off, 1 = on) + overlayMode = 0; % State of annotation overlay mode (0 = off, 1 = on) + overlayColors = [1,255,0,0; 2,0,255,0;3,0, 0, 255; 4,255, 0, 255; 5, 0, 255, 255]; % Array containing color info (id,r,g,b) + overlayMethod = 1; %set to 1 to use fast render mode for simple cases + nColors = 16; %number of overlay colors + end + + events + WindowClosed % Event that is triggered when Image window closed + KeyPressed % Event that is triggered when key is pressed. + UpdateTitle % Event that is triggered to update the title + end + + methods + + function this = update_image(this, data) + % obj = update_image(obj, data) + % + % Internal helper function to aide in interfacing various GUI. This + % function updates the internal image data to the provided data. + % + if this.datatype == eRAMONDataType.rgba32 + % save 3 channels + img = squeeze(data(:,:,this.sliceIndex,1:3)); + + if (this.invert_mode == 1) + img(:,:,1) = max(max(img(:,:,1))) - img(:,:,1); + img(:,:,2) = max(max(img(:,:,2))) - img(:,:,2); + img(:,:,3) = max(max(img(:,:,3))) - img(:,:,3); + end + else + % Save single slice and manipulate to force proper scaling + img = data(:,:,this.sliceIndex); + + % Rescale image: + mmin = min(img(:)); + mmax = max(img(:)); + + if (mmax-mmin) == 0, + mmin = 0; + end + + img = uint8(double(img - mmin) / double(mmax-mmin)*255.0); + + if (this.invert_mode == 1) + img = max(img(:)) - img; + end + end + + if (this.overlayMode == 1) + % Update image to have annotation overlays + + if length(this.associated_cube) == 1 + % if 1 annotation cube randomize colors + % TODO: Change to color palette instead of random + % colors + + if size(img,3) == 1, + img = cat(3, img,img,img); + end + + anno = this.associated_cube{1}; + + % If associated cube is a different size you need to + % use coords to pop it into place + xForm = 0; + if ndims(anno.data) ~= ndims(this.dataCube) + if this.datatype == eRAMONDataType.rgba32 + xForm = 0; + + elseif size(anno.data) ~= size(this.dataCube) + xForm = 1; + end + elseif size(anno.data) ~= size(this.dataCube) + xForm = 1; + end + + if xForm == 0 + annoSlice = anno.data(:,:,this.sliceIndex); + else + % find overlay data + dataInd = double(this.dataOffset(3)) + this.sliceIndex - 1; + sliceInds = 1:size(anno,3); + if ~isempty(anno.xyzOffset) + sliceInds = double(anno.xyzOffset(3)) + sliceInds - 1; + end + indToOverlay = find(dataInd == sliceInds); + + if isempty(indToOverlay) + % not on a slice with overlay + annoSlice = zeros(size(this.dataCube(:,:,1))); + else + labledInds = find(anno.data(:,:,indToOverlay) > 0); + [row,col] = ind2sub(size(anno.data),labledInds); + rowDiff = double(anno.xyzOffset(1) - this.dataOffset(1)); + colDiff = double(anno.xyzOffset(2) - this.dataOffset(2)); + + if rowDiff < 0 || colDiff < 0 + % annotation outside cube + annoSlice = zeros(size(this.dataCube(:,:,1))); + else + annoSlice = zeros(size(this.dataCube(:,:,1))); + row = row + rowDiff; + col = col + colDiff; + lableInds = sub2ind(size(this.dataCube(:,:,1)),row,col); + annoSlice(lableInds) = 1; + annoSlice = annoSlice(1:size(this.dataCube(:,:,1),1),... + 1:size(this.dataCube(:,:,1),2)); + end + + end + end + + if this.overlayMethod == 1 + % inputs are annoSlice and img + + %if length(unique(annoSlice)) > 5 %TODO + this.overlayColors = round(255*jet(this.nColors));% Array containing color info (id,r,g,b) + % else + this.overlayColors(1:5,:) = 255*[1 1 0; 0 1 0; 0 0 1; 1 0 0; 1 1 0]; + % end + this.overlayColors(end+1,:) = [0,0,0]; %for BG + + colorIdx = mod(annoSlice,this.nColors - 1) + 1; + colorIdx(annoSlice == 0) = size(this.overlayColors,1); + annoOut(:,:,1) = reshape(this.overlayColors(colorIdx,1),size(annoSlice,1),size(annoSlice,2)); + annoOut(:,:,2) = reshape(this.overlayColors(colorIdx,2),size(annoSlice,1),size(annoSlice,2)); + annoOut(:,:,3) = reshape(this.overlayColors(colorIdx,3),size(annoSlice,1),size(annoSlice,2)); + + overlayImgLayer = uint8(annoOut); + overlayAlphaLayer = uint8(sum(overlayImgLayer,3)); + overlayAlphaLayer(overlayAlphaLayer > 0) = 100; + + % Merge Images + overlayAlphaLayer = repmat(overlayAlphaLayer,[1 1 3]); + + overlayAlphaLayer = double(overlayAlphaLayer); + img = double(img); + overlayImgLayer = double(overlayImgLayer); + img = (img .* (255 - overlayAlphaLayer)./255) + (overlayImgLayer .* (overlayAlphaLayer./255)); + + img = uint8(img); + + + else + ids = unique(annoSlice); + r = zeros(size(annoSlice)); + g = r; + b = r; + + for cc = 1:length(ids) + if ids(cc) == 0 + % Don't label background + continue + end + + % get color + this.overlayColors = jet(16);% Array containing color info (id,r,g,b) + + colors = round(this.overlayColors(mod(ids(cc),15)+1,1:3)*255); + + % Label pixels + r(annoSlice == ids(cc)) = colors(1); + g(annoSlice == ids(cc)) = colors(2); + b(annoSlice == ids(cc)) = colors(3); + end + + overlayImgLayer = uint8(cat(3,r,g,b)); + + overlayAlphaLayer = uint8(sum(overlayImgLayer,3)); + overlayAlphaLayer(overlayAlphaLayer > 0) = 100; + + % Merge Images + overlayAlphaLayer = repmat(overlayAlphaLayer,[1 1 3]); + + overlayAlphaLayer = double(overlayAlphaLayer); + img = double(img); + overlayImgLayer = double(overlayImgLayer); + img = (img .* (255 - overlayAlphaLayer)./255) + (overlayImgLayer .* (overlayAlphaLayer./255)); + img = uint8(img); + end + else + % If 2+ annotation cubes group colors by cube + if size(img,3) == 1, + img = cat(3, img,img,img); + end + + rOverlay = zeros(size(img(:,:,1))); + gOverlay = rOverlay; + bOverlay = rOverlay; + + for ss = 1:length(this.associated_cube) + + anno = this.associated_cube{ss}; + + % If associated cube is a different size you need to + % use coords to pop it into place + xForm = 0; + if ndims(anno.data) ~= ndims(this.dataCube) + xForm = 1; + elseif size(anno.data) ~= size(this.dataCube) + xForm = 1; + end + + if xForm == 0 + annoSlice = anno.data(:,:,this.sliceIndex); + else + % find overlay data + dataInd = double(this.dataOffset(3)) + this.sliceIndex - 1; + sliceInds = 1:size(anno,3); + sliceInds = double(anno.xyzOffset(3)) + sliceInds - 1; + indToOverlay = find(dataInd == sliceInds); + + if isempty(indToOverlay) + % not on a slice with overlay + annoSlice = zeros(size(this.dataCube(:,:,1))); + else + labledInds = find(anno.data(:,:,indToOverlay) > 0); + [row,col] = ind2sub(size(anno.data),labledInds); + rowDiff = double(anno.xyzOffset(1) - this.dataOffset(1)); + colDiff = double(anno.xyzOffset(2) - this.dataOffset(2)); + + if rowDiff < 0 || colDiff < 0 + % annotation outside cube + annoSlice = zeros(size(this.dataCube(:,:,1))); + else + annoSlice = zeros(size(this.dataCube(:,:,1))); + row = row + rowDiff; + col = col + colDiff; + annoSlice(row,col) = 1; + annoSlice = annoSlice(1:size(this.dataCube(:,:,1),1),... + 1:size(this.dataCube(:,:,1),2)); + end + + end + end + + r = zeros(size(img(:,:,1))); + g = r; + b = r; + + ids = unique(annoSlice); + if length(ids) == 1 + % Don't label background + continue + end + + % get color + if isempty(this.overlayColors) + % gen color + colors = floor(rand(1,3)*255); + this.overlayColors = [ss colors]; + else + % check color list + ind = find(this.overlayColors(:,1)==ss, 1); + if isempty(ind) + %gen color + colors = floor(rand(1,3)*255); + this.overlayColors(end+1,:) = [ss colors]; + else + % reuse color + colors = this.overlayColors(ind,2:4); + end + end + + % Label pixels + r(annoSlice > 0) = colors(1); + g(annoSlice > 0) = colors(2); + b(annoSlice > 0) = colors(3); + + rOverlay = rOverlay + r; + gOverlay = gOverlay + g; + bOverlay = bOverlay + b; + end + + overlayImgLayer = uint8(cat(3,rOverlay,gOverlay,bOverlay)); + overlayAlphaLayer = uint8(sum(overlayImgLayer,3)); + overlayAlphaLayer(overlayAlphaLayer > 0) = 85; + + % Merge Images + overlayAlphaLayer = double(repmat(overlayAlphaLayer,[1 1 3])); + overlayImgLayer = double(overlayImgLayer); + img = double(img); + img = (img .* (255 - overlayAlphaLayer)./255) + (overlayImgLayer .* (overlayAlphaLayer./255)); + img = uint8(img); + end + end + + set(this.hImg, 'CData', img); + this.img_data = img; + + + img = imresize(img, this.zoom_box.img_dims); + this.zoom_box.update_image(img); + end + + function this = VolumeImageBox(cube, xyzOffset, slice, pos, win_title, datatype) + % obj = VolumeImageBox(cube) + % obj = VolumeImageBox(cube, slice) + % obj = VolumeImageBox(cube, slice, pos) + % obj = VolumeImageBox(cube, slice, pos, win_title) + % + % Creates an VolumeImageBox object do display the provided cube/img + % + % cube = NxM or NxMxZ (Grayscale or RGB) image or cube to display. + % slice = index in Z of image to display. If img and not a cube + % set to 1 + % pos = position of lower-left corner of window. + % win_title = Title of window (default 'Image') + % + + if nargin < 2, + xyzOffset = [1 1 1]; + end + + if nargin < 3, + slice = []; + end + + if nargin < 4, + pos = []; + end + + if nargin < 5, + win_title = []; + end + + if isempty(slice), + this.sliceIndex = 1; + else + this.sliceIndex = slice; + end + + if isempty(win_title), + win_title = 'Image'; + end + + this.dataCube = cube; + this.datatype = datatype; + if isempty(xyzOffset) + this.dataOffset = [1 1 1]; + else + this.dataOffset = xyzOffset; + end + clear cube % clear up dup memory + + screen_dims = get(0, 'ScreenSize'); + screen_size = screen_dims(end-1:end); + screen_x = screen_size(1); + screen_y = screen_size(2); + + img_dims_val = size(this.dataCube(:,:,this.sliceIndex)); + img_dims_val = img_dims_val(1:2); + + this.img_dims = img_dims_val; + + if (img_dims_val(1) > screen_x) || (img_dims_val(2) > screen_y) + win_dims = floor(0.75 * [screen_x, screen_y]); + if win_dims(1) == 0 || win_dims(2) == 0 + win_dims = this.img_dims; + end + else + win_dims = this.img_dims; + end + + prev_dims = floor(0.2 * this.img_dims); + if prev_dims(1) == 0 || prev_dims(2) == 0 + prev_dims = this.img_dims; + end + + min_preview_size = 100; + min_window_size = 250; + + if max(prev_dims) <= min_window_size + prev_dims = prev_dims * (min_window_size/max(prev_dims)); + end + + if min(prev_dims) < min_preview_size, + prev_dims = floor((min_preview_size/min(prev_dims)) * prev_dims); + end + + if min(win_dims) < min_window_size, + win_dims = floor((min_window_size/min(win_dims)) * win_dims); + end + + % Automatically set window position + if isempty(pos), + pos = [screen_x/2, screen_y/2] - [win_dims(2), win_dims(1)]; + end + + + % It's possible above operations made width of an image too small. + % In this case, we will just scale in a non-uniform manner: + %prev_dims(prev_dims < min_dim_size) = min_dim_size; + + if datatype == eRAMONDataType.rgba32 + % save 3 channels + img = squeeze(this.dataCube(:,:,this.sliceIndex,1:3)); + else + % Save single slice and manipulate to force proper scaling + img = this.dataCube(:,:,this.sliceIndex); + + mmin = min(img(:)); + mmax = max(img(:)); + + if (mmax-mmin) == 0, + mmin = 0; + end + + img = uint8(double(img - mmin) / double(mmax-mmin) * 255.0); + end + + this.hFig = figure('Toolbar', 'none', ... + 'MenuBar', 'none', ... + 'Units', 'pixels', ... + 'Position', [pos, win_dims(2), win_dims(1)], ... + 'NumberTitle', 'off', ... + 'Name', win_title, ... + 'Resize', 'on', ... + 'CloseRequestFcn', @CloseFcn, ... + 'KeyPressFcn', @KeyPressFcn, ... + 'WindowScrollWheelFcn', @WindowScrollWheelFcn ); + + this.img_data = img; + this.hAx = axes('parent', this.hFig, ... + 'position', [0,0,1,1]); + + this.hImg = imagesc(img, 'parent', this.hAx); + + set(this.hAx, 'ytick', [], 'xtick', []); + + if size(img,3) == 1, + colormap(this.hAx, gray); + end + + prev_xpos = pos(1) + 10 + this.img_dims(2); + prev_ypos = pos(2); % Stays the same + prev_width = prev_dims(2); + prev_height = prev_dims(1); + + if prev_xpos + prev_width > screen_x, + prev_xpos = pos(1) - 10 - this.img_dims(2); + end + + prev_pos = [prev_xpos, prev_ypos]; + preview_img = imresize(img, [prev_height, prev_width]); + prev_pos(prev_pos < 0) = 0; + + this.zoom_box = ZoomBox(preview_img, prev_pos); + + addlistener(this.zoom_box, 'StateChange', @UpdateAxisFcn); + addlistener(this.zoom_box, 'WindowClosed', @ClosedZoom); + + addlistener(this, 'UpdateTitle', @UpdateAxisFcn); + + set(this.zoom_box.hFig, 'KeyPressFcn', @KeyPressFcn, ... + 'WindowScrollWheelFcn', @WindowScrollWheelFcn ); + + set(this.hFig, 'ResizeFcn', @ResizeConstraintFcn); + base_title = get(this.hFig, 'Name'); + notify(this, 'UpdateTitle'); + + function ResizeConstraintFcn(~, ~) + % Update magnification level of scroll panel while + % resizing: + + %api = iptgetapi(this.hScroll); + %screen_dims = get(0, 'ScreenSize'); + %screen_size = screen_dims(end-1:end); + %screen_x = screen_size(1); + %screen_y = screen_size(2); + + %position = get(this.hFig, 'Position'); + %mag = max(position(3) / this.img_dims(2), ... + % position(4) / this.img_dims(1)); + %api.setMagnification(mag); + end + + function ResetAxisProportionFcn(~, ~) + % Reset the axis to the correct dimension if the box has + % been resized wierd + + position = get(this.hFig, 'Position'); + c1 = position(3); + r1 = position(4); + [r2 c2] = size(this.dataCube(:,:,this.sliceIndex)); + + if r1/c1 == r2/c2 + return + end + + if r1 > c1 + newC1 = c1* ((r1 * c2) / (r2 * c1)); + set(this.hFig, 'Position',[position(1) position(2) newC1 r1]); + else + newR1 = r1* ((r2 * c1) / (r1 * c2)); + set(this.hFig, 'Position',[position(1) position(2) c1 newR1]); + end + end + + function UpdateAxisFcn(~, ~) + pos = this.zoom_box.getPosition(this.img_dims); + newaxis = [pos(1), pos(1) + pos(3), pos(2), pos(2)+pos(4)]; + axis(this.hAx, newaxis); + if this.overlayMode == 1 + annoMode = 'ON'; + else + annoMode = 'OFF'; + end + set(this.hFig, 'Name', sprintf('%s X: [%d, %d] Y: [%d, %d] Z: %d Anno: %s', ... + base_title, round(newaxis(1)), round(newaxis(2)), ... + round(newaxis(3)), round(newaxis(4)),this.sliceIndex,annoMode)); + zw = pos(3); + zh = pos(4); + + figpos = get(this.hFig, 'Position'); + ul = figpos(1:2) + figpos(3:4); + + screen_dims = get(0, 'ScreenSize'); + screen_width = screen_dims(end-1); + screen_height = screen_dims(end); + + new_w = (zw / zh) * figpos(4); + new_h = (zh / zw) * figpos(3); + + if screen_height < screen_width, + figpos(4) = new_h; + else + figpos(3) = new_w; + end + + figpos(1:2) = ul - figpos(3:4); + + + if min(figpos) < -100, + figpos(figpos < -100) = -100; + end + set(this.hFig, 'Position', figpos); + + end + + function KeyPressFcn(src, event) + % windims = get(this.hFig, 'Position'); + % windims = windims(3:4); + % + % screendims = get(0, 'ScreenSize'); + % screendims = screendims(end-1:end); + %maxfactor = max(windims ./ screendims); + %minfactor = min(windims ./ screendims); + + switch (event.Character) + case {'?','/'}, + fprintf(1, '====[ VolumeImageBox Keys ] =====\n'); + fprintf(1, '? - Display help on available shortcut keys\n'); + fprintf(1, ' space - resize image window to be proportional\n'); + fprintf(1, ' +,= - Zoom into region\n'); + fprintf(1, ' - - Zoom out of region\n'); + fprintf(1, ' s - Save current figure\n'); + fprintf(1, ' c - Copy current figure to clipboard\n'); + fprintf(1, ' i - Invert image by subtracting from maximum value.\n'); + fprintf(1, ' a - Overlay annotations. You must associate an annotation volume object first.\n'); + fprintf(1, ' S - Save all slices.\n'); + fprintf(1, ' M - Make a movie of all slices.\n'); + fprintf(1, ' uparrow - Increment Slice.\n'); + fprintf(1, ' dnarrow - Decrement Slice.\n'); + + % Dispatch this key so that other handlers can + % display proper message: + notify(this, 'KeyPressed', KeyPressEvent(event.Character)); + + case {' '}, + ResetAxisProportionFcn(src, event); + + case {'+', '='}, + % Allow change? + this.zoom_box.zoom(0.5); + + case {'-'} + this.zoom_box.zoom(2); + + case {'s'} + [filename, pathname] = uiputfile({'*.fig', 'MATLAB Figure'; '*.png', 'Image File' }, 'Save Figure As'); + if isequal(filename, 0) + return; + end + oldfcn = get(this.hFig,'CloseRequestFcn'); + set(this.hFig,'CloseRequestFcn', 'closereq'); + saveas(this.hFig, fullfile(pathname, filename)); + set(this.hFig,'CloseRequestFcn', oldfcn); + + case {'S'} + %save all + + folder_name = uigetdir; + disp('saving all slices beginning at slice 1...may be slow...') + disp('first reset to slice 1') + this.sliceIndex = 1; + this = this.update_image(this.dataCube); + notify(this, 'UpdateTitle'); + %this = update_image(this, img); + drawnow + disp('then iterate over all slices') + for iii = this.sliceIndex:size(this.dataCube,3) + ee.Key = 'uparrow'; + ee.Character = 'uparrow'; + KeyPressFcn(src, ee) + drawnow + fprintf('Saving slice %d of %d...\n', iii, size(this.dataCube,3)) + imwrite(this.img_data, fullfile(folder_name,['Slice_',num2str(iii),'.png'])); + end + + case {'M'} + if isunix && ~ismac + [filename, pathname] = uiputfile({'*.mp4', 'MATLAB Movie'}, 'Save Movie As'); disp('saving all slices beginning at slice 1...may be slow...') + else + [filename, pathname] = uiputfile({'MATLAB Movie'}, 'Save Movie As'); disp('saving all slices beginning at slice 1...may be slow...') + end + disp('first reset to slice 1') + this.sliceIndex = 1; + this = this.update_image(this.dataCube); + notify(this, 'UpdateTitle'); + %this = update_image(this, img); + drawnow + disp('then iterate over all slices') + + if isunix && ~ismac %linux + + writerObj = VideoWriter(fullfile(pathname,filename)); + else %windows, mac + writerObj = VideoWriter(fullfile(pathname,filename),'MPEG-4'); + + end + writerObj.FrameRate = 2; + writerObj.Quality = 100; + open(writerObj); + + for iii = this.sliceIndex:size(this.dataCube,3) + ee.Key = 'uparrow'; + ee.Character = 'uparrow'; + KeyPressFcn(src, ee) + drawnow + + frame = getframe(this.hFig); + writeVideo(writerObj,frame); + + + + fprintf('Saving slice %d of %d...\n', iii, size(this.dataCube,3)) + + end + + close(writerObj); + + + + case {'c'} + imclipboard('copy', this.img_data); + fprintf('Image copied to clipboard.\n'); + + case {'i'} + % Invert image + this.img_data = max(this.img_data(:)) - this.img_data; + set(this.hImg, 'CData', this.img_data); + this.invert_mode = ~this.invert_mode; + case {'a'} + % overlay annotations + if ~isempty(this.associated_cube) + this.overlayMode = ~this.overlayMode; + this = this.update_image(this.dataCube); + else + fprintf('You must associate annotation data contained in a VolumeObject via the "associate" method. It must be the same size as the image data\n'); + end + + otherwise, + switch (event.Key) + case {'uparrow'} + + if this.sliceIndex + 1 <= size(this.dataCube,3) + this.sliceIndex = this.sliceIndex + 1; + this = this.update_image(this.dataCube); + end + + notify(this, 'UpdateTitle'); + + case {'downarrow'} + + if this.sliceIndex - 1 > 0 + this.sliceIndex = this.sliceIndex - 1; + this = this.update_image(this.dataCube); + end + + notify(this, 'UpdateTitle'); + + otherwise + notify(this, 'KeyPressed', KeyPressEvent(event.Character)); + end + + end + end + + function WindowScrollWheelFcn(~, event) + + %windims = get(this.hFig, 'Position'); + %windims = windims(3:4); + + %screendims = get(0, 'ScreenSize'); + %screendims = screendims(end-1:end); + %maxfactor = max(windims ./ screendims); + + if event.VerticalScrollCount < 0, + this.zoom_box.zoom(0.9); + elseif event.VerticalScrollCount > 0, + this.zoom_box.zoom(1.05); + end + end + + function ClosedZoom(~, ~) + close(this); + notify(this, 'WindowClosed'); + end + + function CloseFcn(~, ~) + try + close(this); + notify(this, 'WindowClosed'); + catch ME %#ok + set(gcf,'CloseRequestFcn', 'closereq'); + delete(gcf); + end + end + + end + + function close(this) + delete(this.hFig); + this.hFig = []; + delete(this.zoom_box); + this.zoom_box = []; + end + + function delete(this) + delete(this.zoom_box); + this.zoom_box = []; + + delete(this.hFig); + this.hFig = []; + + this.hAx = []; + this.hImg = []; + end + + function save(this, filename) + imwrite(this.img_data, filename); + end + + function associate(this, cube, key) + % OBJ.associate(this, cube) + % OBJ.associate(this, cube, key) + % OBJ.associate(this, key, cube) + % + % Contains a cube to associate image data with + % + if nargin < 3, + key = []; + end + + if isempty(key), + key = 'annotation'; + end + + if isobject(key), + nkey = cube; + cube = key; + key = nkey; + end + + [tf, loc] = ismember(this.associated_key, key); + if isempty(tf), + loc = numel(this.associated_key)+1; + end + + if (loc == 0), + loc = numel(this.associated_key)+1; + end + + if isempty(cube), % Deletion: + this.associated_key(loc) = []; + this.associated_cube(loc) = []; + else + this.associated_key{loc} = key; + if isempty(cube.xyzOffset) + cube.setXyzOffset([1 1 1]); + end + this.associated_cube{loc} = cube; + end + + end + + end + +end + diff --git a/api/matlab/ramon/basic_viewer/ZoomBox.m b/api/matlab/ramon/basic_viewer/ZoomBox.m new file mode 100644 index 0000000..2d59a8e --- /dev/null +++ b/api/matlab/ramon/basic_viewer/ZoomBox.m @@ -0,0 +1,232 @@ +classdef ZoomBox < handle +% ZoomBox class handles the zoom window preview box. The rectangle in this box +% can be dragged around to provide notice to another window in regards to the +% location to display. +% +% +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory +% All Rights Reserved. +% Contact the JHU/APL Office of Technology Transfer for any additional rights. +% www.jhuapl.edu/ott +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + properties + hFig = []; % Handle of display figure + hAx = []; % Axes of display image + + rect = []; % Handle to imrect containing selection + hImg = []; % Handle to image + hScroll = []; % Scroll panel for zoombox + hFill = []; % Rectangle patch for display purposes + img_dims = []; % Contains dimension of the preview image + end + + events + StateChange % Event is notified when the imrect position changes + WindowClosed % Event is notified when the window is closed + end + + methods + function this = update_image(this, img) + % obj = update_image(obj, img) + % + % Helper function used in standardizing interaction between various + % GUI windows. Update ZoomBox image with the provided image + % + set(this.hImg, 'CData', img); + end + + function this = ZoomBox(img, pos) + % obj = ZoomBox(img, pos) + % Constructs a ZoomBox with the provided image. The ZoomBox is + % initially positioned with the lower-left corner centered at pos. + % + im_dims = size(img); + im_dims = im_dims(1:2); + this.img_dims = im_dims; + + win_dims = im_dims; + + screen_dims = get(0, 'ScreenSize'); + screen_size = screen_dims(end-1:end); + screen_x = screen_size(1); + screen_y = screen_size(2) - 60; + + + if win_dims(1) > screen_y, + win_dims(1) = screen_y; + end + + if win_dims(2) > screen_x, + win_dims(2) = screen_x; + end + + this.hFig = figure('Toolbar', 'none', ... + 'Menubar', 'none', ... + 'Units', 'pixels', ... + 'Position', [pos, win_dims(2), win_dims(1)], ... + 'NumberTitle', 'off', ... + 'Name', 'Image Preview', ... + 'Resize', 'off', ... + 'CloseRequestFcn', @CloseFcn); + + mag = max(win_dims ./ this.img_dims); + + this.hAx = axes('parent', this.hFig, ... + 'position', [0, 0, 1, 1]); + + this.hImg = imagesc(img, 'parent', this.hAx); + set(this.hAx, 'ytick', [], 'xtick', []); + + this.hScroll = imscrollpanel(this.hFig, this.hImg); + + api = iptgetapi(this.hScroll); + api.setMagnification(mag); + + if size(img,3) == 1, + colormap( this.hAx, gray); + end + + % Finally, draw rectangle: + + constrainFcn = makeConstrainToRectFcn('imrect', ... + [0.5, im_dims(2)+0.5], [0.5, im_dims(1)+0.5]); + + this.rect = imrect(this.hAx, ... + [1,1, win_dims(2), win_dims(1)], ... + 'PositionConstraintFcn', constrainFcn); + + this.rect.setColor('r'); + this.rect.addNewPositionCallback(... + @(pos)(notify(this,'StateChange'))); + + + function CloseFcn(src, event) + close(this); + notify(this, 'WindowClosed'); + end + end + + function close(this) + % close(obj) + % Close a ZoomBox window + delete(this.hFig); + this.hFig = []; + end + + function r = getPosition(this, newdims) + % r = getPosition(obj) + % r = getPosition(obj, newdims) + % + % Returns the position of the rectangle in [x,y,w,h] + % If newdims specified, returns r in terms of the new image dimensions + % This is for converting between coordinates of the preview image + % which may be smaller than the original image. + % newdims is in terms of [nRows, nCols] + % + if nargin < 2, + newdims = []; + end + + r = this.rect.getPosition(); + + if isempty(newdims), + return; + end + + w = this.img_dims(2); + h = this.img_dims(1); + nw = newdims(2); + nh = newdims(1); + + r(1) = r(1) * (nw / w); + r(2) = r(2) * (nh / h); + r(3) = r(3) * (nw / w); + r(4) = r(4) * (nh / h); + end + + function setPosition(this, r, newdims) + % setPosition(obj, r) + % setPosition(obj, r, newdims) + % + % Updates the position of the zoombox to the provided coordinates. + % If newdims specified, r is specified in terms of the image + % dimension given by newdims. + % + if nargin < 3, + newdims = []; + end + + if ~isempty(newdims), + w = this.img_dims(2); + h = this.img_dims(1); + nw = newdims(2); + nh = newdims(1); + + r(1) = r(1) * (w/nw); + r(2) = r(2) * (h/nh); + r(3) = r(3) * (w/nw); + r(4) = r(4) * (h/nh); + end + this.rect.setPosition(r); + notify(this,'StateChange'); + end + + function delete(this) + % Destructor for ZoomBox class + delete(this.hFig); + this.hFig = []; + end + + function tf = zoom(this, scale) + % TF = OBJ.zoom(scale) + % + % Attempts to resize the rectangle in the zoombox while preserving + % aspect ratio. If rectangle cannot be resized successfully (e.g. it + % scales outside of cube or too small), then false is returned and no + % change is made. Otherwise, true is returned. + % + r = this.rect.getPosition(); + + xc = r(1) + r(3)/2; + yc = r(2) + r(4)/2; + + r(1) = (r(1) - xc)* scale + xc; + r(2) = (r(2) - yc)* scale + yc; + r(3) = r(3) * scale; + r(4) = r(4) * scale; + + tf = 1; + if r(3) < 1 || r(4) < 1 || ... + r(3) > this.img_dims(2) || ... + r(4) > this.img_dims(1), + tf = 0; + return; + end + + r(1) = max(0, min(this.img_dims(2) - r(3), r(1))); + r(2) = max(0, min(this.img_dims(1) - r(4), r(2))); + r(3) = max(1, min(this.img_dims(2), r(3))); + r(4) = max(1, min(this.img_dims(1), r(4))); + + this.rect.setPosition(r); + notify(this, 'StateChange'); + end + + end + +end diff --git a/api/matlab/ramon/basic_viewer/imclipboard.m b/api/matlab/ramon/basic_viewer/imclipboard.m new file mode 100644 index 0000000..d6a60d3 --- /dev/null +++ b/api/matlab/ramon/basic_viewer/imclipboard.m @@ -0,0 +1,271 @@ +function varargout = imclipboard(clipmode, varargin) +%IMCLIPBOARD Copy and paste images to and from system clipboard. +% +% IMCLIPBOARD('copy', IMDATA) sets the clipboard content to the image +% represented by IMDATA. IMDATA must be MxN grayscale (double, uint8, +% uint16), MxN black and white (logical), MxNx3 true color (double, +% uint8, uint16) +% +% IMCLIPBOARD('copy', X, MAP) sets the clipboard content to the image +% data represented by indexed image X with colormap MAP. X must be MxN +% matrix (double, uint8, uint16) and MAP must be Px3 (double). +% +% IMDATA = IMCLIPBOARD('paste') returns the current image content in the +% clipboard as a true color image (MxNx3 uint8). +% +% [X, MAP] = IMCLIPBOARD('paste') returns the current image content in +% the clipboard as an indexed color image. +% +% IMCLIPBOARD('paste') displays the image in a new figure window. +% +% [...] = IMCLIPBOARD('paste', FILENAME) saves the image as FILENAME. FILENAME +% must be a name to one of the following image formats: JPG, GIF, BMP, +% PNG, TIF. +% +% Note: IMCLIPBOARD requires Java on all platforms. +% +% Example: +% im = imread('peppers.png'); +% imclipboard('copy', im); +% % paste into a paint program +% +% im2 = imclipboard('paste'); +% +% See also CLIPBOARD. + +% Jiro Doke +% Copyright 2010 The MathWorks, Inc. +% Sept 12, 2010 +% +% Copyright (c) 2010, The MathWorks, Inc. +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are +% met: +% +% * Redistributions of source code must retain the above copyright +% notice, this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in +% the documentation and/or other materials provided with the distribution +% * Neither the name of the The MathWorks, Inc. nor the names +% of its contributors may be used to endorse or promote products derived +% from this software without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. + +error(nargchk(1, 3, nargin, 'struct')); +error(javachk('awt', 'IMCLIPBOARD')); + +% Import necessary Java classes +import java.awt.Toolkit.* +import java.awt.image.BufferedImage +import java.awt.datatransfer.DataFlavor + +% Get System Clipboard object (java.awt.Toolkit) +cb = getDefaultToolkit.getSystemClipboard(); + +clipmode = validatestring(clipmode, {'copy', 'paste'}, mfilename, 'CLIPMODE'); + +switch clipmode + case 'copy' + error(nargoutchk(0, 0, nargout, 'struct')); + + % Add java class (ImageSelection) to the path + if ~exist('ImageSelection', 'class') + javaaddpath(fileparts(which(mfilename)), '-end'); + end + + data = validateCopyInput(varargin{:}); + + % Get image size + ht = size(data, 1); wd = size(data, 2); + + % Convert to Blue-Green-Red format + data = data(:, :, [3 2 1]); + + % Convert to 3xWxH format + data = permute(data, [3, 2, 1]); + + % Append Alpha data (not used) + data = cat(1, data, 255*ones(1, wd, ht, 'uint8')); + + % Create image buffer + imBuffer = BufferedImage(wd, ht, BufferedImage.TYPE_INT_RGB); + imBuffer.setRGB(0, 0, wd, ht, typecast(data(:), 'int32'), 0, wd); + + % Create ImageSelection object + % % custom java class + imSelection = ImageSelection(imBuffer); + + % Set clipboard content to the image + cb.setContents(imSelection, []); + + case 'paste' + error(nargoutchk(0, 2, nargout, 'struct')); + + filename = validatePasteInput(varargin{:}); + + try + % Attempt to retrieve image data from system clipboard. If there is no + % image data, it will throw an exception. + imBuffer = cb.getData(DataFlavor.imageFlavor); + catch %#ok + disp('No image data in clipboard'); + imBuffer = []; + end + + if isempty(imBuffer) + im = []; + else + im = imBuffer.getRGB(0, 0, imBuffer.getWidth, imBuffer.getHeight, [], 0, imBuffer.getWidth); + % "im" is an INT32 array, where each value contains 4 bytes of information + % (Blue, Green, Red, Alpha). Alpha is not used. + + % type cast INT32 to UINT8 (--> 4 times as many elements) + im = typecast(im, 'uint8'); + + % Reshape to 4xWxH + im = reshape(im, [4, imBuffer.getWidth, imBuffer.getHeight]); + + % Remove Alpha information (4th row) because it is not used + im = im(1:3, :, :); + + % Convert to HxWx3 array + im = permute(im, [3 2 1]); + + % Convert color space order to R-G-B + im = im(:, :, [3 2 1]); + end + + if nargout == 1 + varargout{1} = im; + elseif nargout == 2 + if isempty(im) + varargout{1} = []; + varargout{2} = []; + else + % Convert to indexed image (getting full possible color space) + [varargout{1}, varargout{2}] = rgb2ind(im, 65536); + end + else + if nargin == 1 && ~isempty(im) + figure; + imshow(im); + end + end + if nargin == 2 && ~isempty(im) + [~, ~, e] = fileparts(filename); + if strcmpi(e, '.gif') + % GIF files require indexed image + [x, map] = rgb2ind(im, 256); + imwrite(x, map, filename); + else + imwrite(im, filename); + end + end + +end + +end + +%% Helper Function +function data = validateCopyInput(varargin) + +if nargin == 0 + error('imclipboard:NotEnoughArguments', ... + 'For ''copy'', the second argument must be an image array'); +end + +if nargin == 2 % X and colormap + % X + validateattributes(varargin{1}, {'double', 'uint8', 'uint16'}, {'2d', 'real', 'nonnegative'}, mfilename, 'X'); + if isa(varargin{1}, 'double') + validateattributes(varargin{1}, {'double'}, {'>=', 0, '<=', 1}, mfilename, 'X'); + end + + % MAP + validateattributes(varargin{2}, {'double'}, {'size', [nan 3], '>=', 0, '<=', 1}, mfilename, 'MAP'); + data = ind2rgb(varargin{1}, varargin{2}); + data = uint8(255*data); + +else % nargin == 1 % Grayscale, True Color, Black and White + validateattributes(varargin{1}, ... + {'double', 'logical', 'uint8', 'uint16'}, ... + {'real', 'nonnegative'}, mfilename, 'IMDATA'); + + switch class(varargin{1}) + case 'double' + validateattributes(varargin{1}, {'double'}, {'>=', 0, '<=', 1}, mfilename, 'IMDATA'); + if ndims(varargin{1}) == 2 % grayscale + data = uint8(255*varargin{1}); + data = cat(3, data, data, data); + elseif ndims(varargin{1}) == 3 + validateattributes(varargin{1}, {'double'}, {'size', [nan nan 3]}, mfilename, 'IMDATA'); + data = uint8(255*varargin{1}); + else + error('imclipboard:InvalideDoubleImage', ... + 'Double image data must be 2 or 3 dimensions'); + end + + case 'logical' + validateattributes(varargin{1}, {'logical'}, {'2d'}, mfilename, 'IMDATA'); + data = uint8(255*varargin{1}); + data = cat(3, data, data, data); + + case 'uint8' + if ndims(varargin{1}) == 2 + data = cat(3, varargin{1}, varargin{1}, varargin{1}); + elseif ndims(varargin{1}) == 3 + validateattributes(varargin{1}, {'uint8'}, {'size', [nan nan 3]}, mfilename, 'IMDATA'); + data = varargin{1}; + else + error('imclipboard:InvalideUINT8Image', ... + 'UINT8 image data must be 2 or 3 dimensions'); + end + + case 'uint16' + if ndims(varargin{1}) == 2 + data = uint8(varargin{1}*(double(intmax('uint8'))/double(intmax('uint16')))); + data = cat(3, data, data, data); + elseif ndims(varargin{1}) == 3 + validateattributes(varargin{1}, {'uint16'}, {'size', [nan nan 3]}, mfilename, 'IMDATA'); + data = uint8(varargin{1}*(double(intmax('uint8'))/double(intmax('uint16')))); + else + error('imclipboard:InvalideUINT16Image', ... + 'UINT16 image data must be 2 or 3 dimensions'); + end + + end + +end + +end + +function filename = validatePasteInput(varargin) + +if nargin == 0 + filename = ''; +else + filename = varargin{1}; + validateattributes(filename, {'char'}, {'row'}, mfilename, 'FILENAME'); + [~, ~, e] = fileparts(filename); + if ~ismember(lower(e), ... + {'.jpg', '.jpeg', '.tif', '.tiff', '.png', '.gif', '.bmp'}) + error('imclipboard:InvalidFileFormat', ... + 'You must specify a valid image extension: .jpg, .tif, .png, .gif, .bmp'); + end +end + +end \ No newline at end of file diff --git a/api/matlab/ramon/basic_viewer/make_overlay.m b/api/matlab/ramon/basic_viewer/make_overlay.m new file mode 100644 index 0000000..85e5f9a --- /dev/null +++ b/api/matlab/ramon/basic_viewer/make_overlay.m @@ -0,0 +1,78 @@ +function overlayOut = make_overlay(imgVol, annoVol) + +% This is a helper function to make an overlay volume suitable for use with +% the viewer (e.g., image(RAMONVolume). It has limited functionality and +% requires that images are the same size and are coregistered. + +% Inputs: RAMONVolume with images, RAMONVolume with annotations +% Output: MxNxPx4 array containing overlaid images +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory +% All Rights Reserved. +% Contact the JHU/APL Office of Technology Transfer for any additional rights. +% www.jhuapl.edu/ott +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nColors = 16; + +img = imgVol.data; +anno = annoVol.data; + +% Rescale image: +mmin = min(img(:)); +mmax = max(img(:)); + +if (mmax-mmin) == 0, + mmin = 0; +end + +img = uint8(double(img - mmin) / double(mmax-mmin)*255.0); + +overlayColors = round(255*jet(nColors));% Array containing color info (id,r,g,b) +overlayColors(end+1,:) = [0,0,0]; %for BG +%annoOut = uint8(zeros(size(anno,1),size(anno,2),3,size(anno,3))); +for i = 1:size(anno,3) + if mod(i,20) == 0 + sprintf('Now processing slice %d of %d...\n',i,size(anno,3)); + end + annoSlice = anno(:,:,i); + imgSlice = img(:,:,i); + + if size(imgSlice,3) == 1, + imgSlice = cat(3, imgSlice,imgSlice,imgSlice); + end + + colorIdx = mod(annoSlice,nColors - 1) + 1; + colorIdx(annoSlice == 0) = size(overlayColors,1); + annoOut(:,:,1) = reshape(overlayColors(colorIdx,1),size(annoSlice,1),size(annoSlice,2)); + + annoOut(:,:,2) = reshape(overlayColors(colorIdx,2),size(annoSlice,1),size(annoSlice,2)); + annoOut(:,:,3) = reshape(overlayColors(colorIdx,3),size(annoSlice,1),size(annoSlice,2)); + + overlayImgLayer = uint8(annoOut); + overlayAlphaLayer = uint8(sum(overlayImgLayer,3)); + overlayAlphaLayer(overlayAlphaLayer > 0) = 100; + + % Merge Images + overlayAlphaLayer = repmat(overlayAlphaLayer,[1 1 3]); + + overlayAlphaLayer = double(overlayAlphaLayer); + imgSlice = double(imgSlice); + overlayImgLayer = double(overlayImgLayer); + overlaySlice = (imgSlice .* (255 - overlayAlphaLayer)./255) + (overlayImgLayer .* (overlayAlphaLayer./255)); + + overlayOut(:,:,:,i) = uint8(overlaySlice); +end + diff --git a/api/matlab/ramon/enum/eRAMONAnnoStatus.m b/api/matlab/ramon/enum/eRAMONAnnoStatus.m new file mode 100644 index 0000000..0b0847d --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONAnnoStatus.m @@ -0,0 +1,36 @@ +classdef eRAMONAnnoStatus < int32 + %eRAMONAnnoStatus Enumeration of status field values for RAMON annotations + % 0 = Unprocessed + % 1 = Locked + % 2 = Processing + % 3 = Ignored + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + enumeration + unprocessed (0) + locked (1) + processed (2) + ignored (3) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONAnnoType.m b/api/matlab/ramon/enum/eRAMONAnnoType.m new file mode 100644 index 0000000..075bb08 --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONAnnoType.m @@ -0,0 +1,46 @@ +classdef eRAMONAnnoType < int32 + %eRAMONAnnoType Enumeration of the types of RAMON annotations + %that can be stored in the OCP annotation database + % + + % Generic = 1 + % SYNAPSE = 2 + % SEED = 3 + % SEGMENT = 4 + % NEURON = 5 + % ORGANELLE = 6 + % ATTRIBUTEDREGION = 7 + % VOLUME = 8 + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + generic(1) + synapse (2) + seed (3) + segment (4) + neuron (5) + organelle (6) % not supported yet + attributedRegion (7) % not support yet + volume(8) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONCubeOrientation.m b/api/matlab/ramon/enum/eRAMONCubeOrientation.m new file mode 100644 index 0000000..6134cda --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONCubeOrientation.m @@ -0,0 +1,43 @@ +classdef eRAMONCubeOrientation < int32 + %RAMONCubeOrientation Enumeration of possible cube orientations + % This is used in the RAMONSeed class. It indicatates how a cube + % should be generated around a seed. + % pos_x - place the seed in the center of the +x face + % neg_x - place the seed in the center of the -x face + % pos_y - place the seed in the center of the +y face + % neg_y - place the seed in the center of the -y face + % pos_z - place the seed in the center of the +z face + % neg_z - place the seed in the center of the -z face + % centered - center the cube around the seed + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + pos_x (0) + neg_x (1) + pos_y (2) + neg_y (3) + pos_z (4) + neg_z (5) + centered (6) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONDataFormat.m b/api/matlab/ramon/enum/eRAMONDataFormat.m new file mode 100644 index 0000000..72882f8 --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONDataFormat.m @@ -0,0 +1,33 @@ +classdef eRAMONDataFormat < int32 + %eRAMONDataFormat Enumeration of the types of + % 0 = dense + % 1 = voxel list + % 2 = bounding box + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + dense (0) + voxelList (1) + boundingBox (2) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONDataType.m b/api/matlab/ramon/enum/eRAMONDataType.m new file mode 100644 index 0000000..b2d7b72 --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONDataType.m @@ -0,0 +1,38 @@ +classdef eRAMONDataType < int32 + %eRAMONDataType Enumeration of the types of data in OCP DB + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + image8 (1) + anno32 (2) + channels16 (3) + channels8 (4) + prob32 (5) + bitmask (6) + anno64 (7) + image16 (8) + rgba32 (9) + rgba64 (10) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONFlowDirection.m b/api/matlab/ramon/enum/eRAMONFlowDirection.m new file mode 100644 index 0000000..78e766d --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONFlowDirection.m @@ -0,0 +1,36 @@ +classdef eRAMONFlowDirection < int32 + %RAMONFlowDirection Enumeration of information flow direction for + %synapse and segments + % 0 = Unknown + % 1 = Pre-Synaptic + % 2 = Post-Synaptic + % 3 = Bi-Directional + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + unknown (0) + preSynaptic (1) + postSynaptic (2) + biDirectional (3) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONOrganelleClass.m b/api/matlab/ramon/enum/eRAMONOrganelleClass.m new file mode 100644 index 0000000..91941e5 --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONOrganelleClass.m @@ -0,0 +1,39 @@ +classdef eRAMONOrganelleClass < int32 + %RAMONOrgannelClass Enumeration of organelle classifications + % 0 = Unknown + % 1 = Mitochondria + % 2 = Vesicle + % 3 = Axoplasmic Reticula + % 4 = Microtubules + % 5 = Nucleus + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + unknown (0) + mitochondria (1) + vesicle (2) + axoplasmicReticula (3) + microtubules (4) + nucleus (5) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONSegmentClass.m b/api/matlab/ramon/enum/eRAMONSegmentClass.m new file mode 100644 index 0000000..b4fb6db --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONSegmentClass.m @@ -0,0 +1,35 @@ +classdef eRAMONSegmentClass < int32 + %RAMONSegmentClass Enumeration of segment classes + % 0 = Unknown + % 1 = Axon (fragment) + % 2 = Dendrite (fragment) + % 3 = Soma (fragment) + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + unknown (0) + axon (1) + dendrite (2) + soma (3) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONSynapseType.m b/api/matlab/ramon/enum/eRAMONSynapseType.m new file mode 100644 index 0000000..67a1eaa --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONSynapseType.m @@ -0,0 +1,36 @@ +classdef eRAMONSynapseType < int32 + %RAMONFlowDirection Enumeration of information flow direction for + %synapse and segments + % 0 = Unknown + % 1 = Excitatory synapse + % 2 = Inhibitory synapse + % 3 = Gap Junction + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + unknown (0) + excitatory (1) + inhibitory (2) + gapJunction (3) + end + +end + diff --git a/api/matlab/ramon/enum/eRAMONUploadDataType.m b/api/matlab/ramon/enum/eRAMONUploadDataType.m new file mode 100644 index 0000000..9ce2d82 --- /dev/null +++ b/api/matlab/ramon/enum/eRAMONUploadDataType.m @@ -0,0 +1,45 @@ +classdef eRAMONUploadDataType < int32 + %eRAMONUploadDataType Enumeration of the types of + % + % 8bit Image Data (0) + % 32bit Annotation Data (1) + % 16bit Multichannel data (2) + % 8bit Multichannel data + % 32bit Probability Map (4) + % Bitmask (5) + % 64bit Annotation Data (6) + % RGBA 32 bit image data (6) + % + % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory + % All Rights Reserved. + % Contact the JHU/APL Office of Technology Transfer for any additional rights. + % www.jhuapl.edu/ott + % + % Licensed under the Apache License, Version 2.0 (the "License"); + % you may not use this file except in compliance with the License. + % You may obtain a copy of the License at + % + % http://www.apache.org/licenses/LICENSE-2.0 + % + % Unless required by applicable law or agreed to in writing, software + % distributed under the License is distributed on an "AS IS" BASIS, + % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + % See the License for the specific language governing permissions and + % limitations under the License. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + enumeration + image8 (0) + anno32 (1) + channels16 (2) + channels8 (3) + prob32 (4) + bitmask (5) + anno64 (6) + rgba32 (7) + end + +end + diff --git a/api/matlab/wrapper/basicWrapper.py b/api/matlab/wrapper/basicWrapper.py new file mode 100755 index 0000000..f09dc66 --- /dev/null +++ b/api/matlab/wrapper/basicWrapper.py @@ -0,0 +1,127 @@ +#!/usr/bin/python + +## Template for a basic matlab wrapper for the LONI Pipeline## + +## It is assumed that the matlab function that is being called does not create files internally and that any +## file that is output is specified by an input argument. This allows LONI pipeline to generate/manage filenames. +## If you are creating files internally you must use the advanced wrapping scripts +## +## Your matlab function also should not have any outputs. You can fprintf a string to the command window and +## using LONI extract the result as an output to pass to the next module. This is much faster than writing files +## if you are only passing a few values between modules. Other than that all outputs +## should be files that are specified by an input filename argument. +## +## In the LONI module that calls this, the first argument should be your function name without the .m extension. +## It must be on the MATLAB search path (everything inside the framework directory structure is). +## Do NOT include a command line prefix. +## All other arguments should be entered into the LONI module in the same order they are in the matlab function call. +## You MUST include command line prefixes for all arguments indicating their type as described in matlabInit.m +## +## Make sure that you specify the environment variable MATLAB_EXE_LOCATION inside the LONI module. This can be +## set under advanced options on the 'Execution' tab in the module set up. +############################################################################# +# Copyright 2015 The Johns Hopkins University / Applied Physics Laboratory +# All Rights Reserved. +# Contact the JHU/APL Office of Technology Transfer for any additional rights. +# www.jhuapl.edu/ott +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################# + +from sys import argv +from sys import exit +#import argv +import sys +import re +import os +from subprocess import Popen,PIPE + +# read in command line args +params = list(argv) +del params[0] + +# get matlab executible location +matLoc = os.getenv("MATLAB_EXE_LOCATION") + +mfile = params[0] +if mfile[0] is os.sep: + # absolute path + mfile = params[0] +else: + # relative path + # get root directory of framework + frameworkRoot = os.getenv("CAJAL3D_LOCATION") + if frameworkRoot is None: + raise Exception('You must set the CAJAL3D_LOCATION environment variable so the wrapper knows where the framework is!') + mfile = frameworkRoot + os.sep + params[0] + + +# Parse arguments and build command string +cmdStr = '-nodesktop -nosplash -nodisplay -r "matlabInit(' + '\'' + mfile + '\'' + ',' +del params[0] + +for p in params: + cmdStr = cmdStr + '\'' + p + '\'' + ',' + +cmdStr = cmdStr[:-1] +cmdStr = cmdStr + ')"' + +# Make sure there aren't any crazy slashes quotes due to string manipulation for cells +slashes = [match.start() for match in re.finditer(re.escape('\\'), cmdStr)] +cmdStr=list(cmdStr) +for ind in slashes: + cmdStr[ind]='' +cmdStr=''.join(cmdStr) + +# Make sure there aren't any repeated single quotes due to LONI interpretation +doubleQuotes = [match.start() for match in re.finditer(re.escape('\'\''), cmdStr)] +cmdStr=list(cmdStr) +for ind in doubleQuotes: + cmdStr[ind]='' +cmdStr=''.join(cmdStr) + +# call matlab +process = Popen([matLoc, cmdStr], stdout=PIPE, stderr=PIPE) +output = process.communicate() +matError = output[1] +output = output[0] +exit_code = process.wait() + +# Check to see if MATLAB itself errored (i.e. seg fault) +if exit_code != 0: + # it bombed. Write out matlab errors and return error code + sys.stderr.write(matError) + print output + exit(exit_code) + +# Check to see if there was an error message internally to MATLAB (i.e. bug in MATLAB code) +starts = [match.start() for match in re.finditer(re.escape('@@@ MATLAB ERROR'), output)] + +if not starts: + # There wasn't an error. Print the matlab output to stdout directly + # Return a zero valued exit code + print output +else: + # There was an error. Extract the error message and write it to stderr. + # Write the remaining output to stdout + # Return non-zero exit code + + errorStr = output[starts[0]:starts[1]+43] + output = output[:starts[0]] + output[starts[1]+44:] + sys.stderr.write(errorStr) + print output + exit(-1) + + + + diff --git a/api/matlab/wrapper/basicWrapperModuleTemplate.pipe b/api/matlab/wrapper/basicWrapperModuleTemplate.pipe new file mode 100644 index 0000000..b41531b --- /dev/null +++ b/api/matlab/wrapper/basicWrapperModuleTemplate.pipe @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + test/matlab/api/matlabInitTFunction.m + + + + + + 345 + + + + + 0 + 1 + + + 0 + + + + + + testStringHERE + + + + + + [1,2;3,4] + + + + + true + false + + + true + + + + + + + + + +