|
| 1 | +# Contributing Guidelines |
| 2 | + |
| 3 | +PAMGuardMatlab is an open-source project. Whilst out license does not require it, we welcome you to contribute any changes you make back to the original repository. |
| 4 | + |
| 5 | +## Getting Started |
| 6 | + |
| 7 | +If you're planning on making contributions to PAMGuardMatlab, we highly recommend that you fork the repository, and then clone it into your machine. In the MATLAB editor, on the left sidebar, click on the 'Project' button then select [pgmatlab.prj](pgmatlab.prj) in the root of your cloned repository. This will automatically set-up the development environment, including the MATLAB path. |
| 8 | + |
| 9 | +## Testing |
| 10 | + |
| 11 | +There is a comprehensive testing suite located in the [tests](tests) folder. To run these tests, run the following commands. |
| 12 | + |
| 13 | +```commandline |
| 14 | +cd tests; |
| 15 | +runtests; |
| 16 | +``` |
| 17 | + |
| 18 | +If you add new functionality to PAMGuardMatlab, please ensure you write appropriate unit tests in the testing suite. |
| 19 | + |
| 20 | +> If you find that the changes you have made are failing existing tests (due to an existing bug in the program or the testing suite), you are welcome to change the testing suite. |
| 21 | +
|
| 22 | +## Making a Pull Request |
| 23 | + |
| 24 | +Once you are satisfied with your tested changes, you should make a pull request, linking an issue and with a detailed commit history (if there is one), changelog, and details and any new tests written. |
| 25 | + |
| 26 | +The GitHub repository will automatically run the unit tests in MacOS, Linux, and Windows - and you can see this by viewing your pull request. |
| 27 | + |
| 28 | +## Creating a New Release |
| 29 | + |
| 30 | +Stable code is maintained through new releases. This allows users to download a lightweight copy of the code without development tools such as tests. |
| 31 | + |
| 32 | +Upon the creation of a release, the following CI action is executed (allow 30-60 seconds for this to complete): |
| 33 | + |
| 34 | +- The user-facing code (README.md, LICENCE, pgmatlab/*) is put in an archive and attached to the release. |
| 35 | + |
| 36 | +Releases should be semantically named and tagged like so. These tags are dynamically inserted in the tarball and wheel uploaded to PyPI. |
| 37 | + |
| 38 | +- V1.2.3 |
| 39 | + - Tag: v1.2.3 |
| 40 | +- V1.2.3 Beta 1 |
| 41 | + - Tag: v1.2.3-b1 |
| 42 | +- V1.2.3 Alpha 1 |
| 43 | + - Tag: v1.2.3-a1 |
| 44 | + |
| 45 | +## Structure |
| 46 | + |
| 47 | +All the source code is found in the [pgmatlab/+pgmatlab](pgmatlab/+pgmatlab/) folder. |
| 48 | + |
| 49 | +Folders use the plus (+) prefix to be treated as a 'package' (where [+pgmatlab](pgmatlab/+pgmatlab/) is the root). By adding [pgmatlab/](pgmatlab/) only the namespace `pgmatlab` is added to the MATLAB path. All classes and functions are accessible through sub-packages, such as: `pgmatlab.utils.millisToDateNum()`. We have temporarily kept three legacy entry points in the root source code folder to allow existing users to continue using the updated code. |
| 50 | + |
| 51 | +PAMGuardMatlab has three main sub-packages: |
| 52 | + |
| 53 | +1. [+core](pgmatlab/+pgmatlab/+core/): contains classes for reading chunks from data files. |
| 54 | + |
| 55 | +2. [+db](pgmatlab/+pgmatlab/+db/): contains functions for interacting with the database (legacy). |
| 56 | + |
| 57 | +3. [+utils](pgmatlab/+pgmatlab/+utils/): contains functions |
| 58 | +for utilities used by the rest of the library. |
| 59 | + |
| 60 | +## Adding New Modules |
| 61 | + |
| 62 | +The object-oriented structure of PAMGuardMatlab allows you to easily create new modules by extending the base classes. This section provides templates and instructions for creating new module types. |
| 63 | + |
| 64 | +### Creating a New Module Class |
| 65 | + |
| 66 | +To create a new module, you need to extend the `StandardModule` class and implement the required abstract methods. |
| 67 | + |
| 68 | +#### Template for a New Module |
| 69 | + |
| 70 | +Create a new file in `pgmatlab/+pgmatlab/+core/+modules/` with the following template: |
| 71 | + |
| 72 | +```matlab |
| 73 | +classdef YourModuleName < pgmatlab.core.standard.StandardModule |
| 74 | + properties (Access = public) |
| 75 | + objectType = 'Your Object Type'; % Set this to match PAMGuard's object type |
| 76 | + end |
| 77 | + |
| 78 | + methods |
| 79 | + function obj = YourModuleName() |
| 80 | + % Constructor - set custom header/footer classes if needed |
| 81 | + obj.header = @pgmatlab.core.standard.StandardModuleHeader; |
| 82 | + obj.footer = @pgmatlab.core.standard.StandardModuleFooter; |
| 83 | + obj.background = -1; % Set to a background class if needed |
| 84 | + end |
| 85 | + |
| 86 | + function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState) |
| 87 | + % Read module-specific data from the binary file |
| 88 | + % This is where you implement the actual data reading logic |
| 89 | + |
| 90 | + % Example: Read some custom fields |
| 91 | + data.customField1 = fread(fid, 1, 'int32'); |
| 92 | + data.customField2 = fread(fid, 1, 'double'); |
| 93 | + |
| 94 | + % Additional processing can be done here |
| 95 | + |
| 96 | + % Return selState (1 = keep, 0 = skip, 2 = stop if sorted) |
| 97 | + selState = 1; |
| 98 | + end |
| 99 | + |
| 100 | + function [data, selState] = readBackgroundImpl(obj, fid, data, fileInfo, length, identifier, selState) |
| 101 | + % Optional: Implement background data reading if your module has background data |
| 102 | + % Leave empty if no background data |
| 103 | + end |
| 104 | + end |
| 105 | +end |
| 106 | +``` |
| 107 | + |
| 108 | +#### Creating Custom Header Classes |
| 109 | + |
| 110 | +If your module requires a custom header format, create a class extending `StandardModuleHeader`: |
| 111 | + |
| 112 | +```matlab |
| 113 | +classdef YourModuleHeader < pgmatlab.core.standard.StandardModuleHeader |
| 114 | + methods |
| 115 | + function data = readImpl(obj, fid, data, fileInfo, length, identifier) |
| 116 | + % Call parent implementation first |
| 117 | + data = [email protected](obj, fid, data, fileInfo, length, identifier); |
| 118 | + |
| 119 | + % Read custom header fields |
| 120 | + data.customHeaderField = fread(fid, 1, 'int32'); |
| 121 | + |
| 122 | + % Process additional header data as needed |
| 123 | + end |
| 124 | + end |
| 125 | +end |
| 126 | +``` |
| 127 | + |
| 128 | +#### Creating Custom Footer Classes |
| 129 | + |
| 130 | +Similarly, for custom footers, extend `StandardModuleFooter`: |
| 131 | + |
| 132 | +```matlab |
| 133 | +classdef YourModuleFooter < pgmatlab.core.standard.StandardModuleFooter |
| 134 | + methods |
| 135 | + function data = readImpl(obj, fid, data, fileInfo, length, identifier) |
| 136 | + % Call parent implementation first |
| 137 | + data = [email protected](obj, fid, data, fileInfo, length, identifier); |
| 138 | + |
| 139 | + % Read custom footer fields |
| 140 | + data.customFooterField = fread(fid, 1, 'int32'); |
| 141 | + end |
| 142 | + end |
| 143 | +end |
| 144 | +``` |
| 145 | + |
| 146 | +#### Creating Custom Background Classes |
| 147 | + |
| 148 | +For modules with background data, extend `StandardBackground`: |
| 149 | + |
| 150 | +```matlab |
| 151 | +classdef YourModuleBackground < pgmatlab.core.standard.StandardBackground |
| 152 | + properties (Access = public) |
| 153 | + objectType = 'Your Background Object Type'; |
| 154 | + end |
| 155 | + |
| 156 | + methods |
| 157 | + function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState) |
| 158 | + % Read background-specific data |
| 159 | + data.backgroundField1 = fread(fid, 1, 'double'); |
| 160 | + data.backgroundField2 = fread(fid, [1, 10], 'int16'); |
| 161 | + |
| 162 | + selState = 1; |
| 163 | + end |
| 164 | + end |
| 165 | +end |
| 166 | +``` |
| 167 | + |
| 168 | +### Registering Your Module |
| 169 | + |
| 170 | +After creating your module class, you need to register it in the main loading function. Add your module to the switch statement in `loadPamguardBinaryFile.m`: |
| 171 | + |
| 172 | +```matlab |
| 173 | +% In the file header case (-1) switch statement: |
| 174 | +case 'Your Module Type' |
| 175 | + switch fileInfo.fileHeader.streamName |
| 176 | + case 'Your Stream Name' |
| 177 | + moduleObj = pgmatlab.core.modules.YourModuleName(); |
| 178 | + % Add additional stream cases if needed |
| 179 | + end |
| 180 | +``` |
| 181 | + |
| 182 | +The module type should match the string used by PAMGuard's module (found in the Java code), and the stream name should match the data stream name used by your PAMGuard module. |
| 183 | + |
| 184 | +### Testing Your Module |
| 185 | + |
| 186 | +1. Create test data using your PAMGuard module |
| 187 | +2. Add test cases to the appropriate test file in the `tests/` folder |
| 188 | +3. Run the tests to ensure your module loads data correctly: |
| 189 | + |
| 190 | +```matlab |
| 191 | +cd tests; |
| 192 | +runtests('YourModuleTest'); |
| 193 | +``` |
| 194 | + |
| 195 | +### Example: Complete Module Implementation |
| 196 | + |
| 197 | +Here's a complete example of a simple module: |
| 198 | + |
| 199 | +```matlab |
| 200 | +classdef ExampleModule < pgmatlab.core.standard.StandardModule |
| 201 | + properties (Access = public) |
| 202 | + objectType = 'Example Detection'; |
| 203 | + end |
| 204 | + |
| 205 | + methods |
| 206 | + function obj = ExampleModule() |
| 207 | + obj.header = @pgmatlab.core.standard.StandardModuleHeader; |
| 208 | + obj.footer = @pgmatlab.core.standard.StandardModuleFooter; |
| 209 | + obj.background = -1; |
| 210 | + end |
| 211 | + |
| 212 | + function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState) |
| 213 | + % Read example-specific fields |
| 214 | + data.detectionType = fread(fid, 1, 'int32'); |
| 215 | + data.confidence = fread(fid, 1, 'double'); |
| 216 | + data.frequency = fread(fid, 1, 'double'); |
| 217 | + |
| 218 | + % Validate data |
| 219 | + if data.confidence < 0 || data.confidence > 1 |
| 220 | + warning('Invalid confidence value: %f', data.confidence); |
| 221 | + end |
| 222 | + |
| 223 | + selState = 1; |
| 224 | + end |
| 225 | + end |
| 226 | +end |
| 227 | +``` |
| 228 | + |
| 229 | +Then register it in `loadPamguardBinaryFile.m`: |
| 230 | + |
| 231 | +```matlab |
| 232 | +case 'Example Detector' |
| 233 | + switch fileInfo.fileHeader.streamName |
| 234 | + case 'Example Detections' |
| 235 | + moduleObj = pgmatlab.core.modules.ExampleModule(); |
| 236 | + end |
| 237 | +``` |
0 commit comments