Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .MATLABDriveTag

This file was deleted.

File renamed without changes.
36 changes: 36 additions & 0 deletions .github/workflows/build-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This workflow will automatically create a ZIP file
# of the distribution files, and add them to a GitHub
# release. It only runs, on creation of the GitHub
# release.

name: Build Package

on:
release:
types: [published]

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Zip code to distribute
run: |
zip -r "pgmatlab-${{ github.event.release.tag_name }}.zip" pgmatlab/* LICENSE README.md

- name: Upload code to distribute
uses: actions/upload-artifact@v4
with:
name: dist-files
path: "pgmatlab-${{ github.event.release.tag_name }}.zip"

- name: Append distribution code to release
uses: softprops/action-gh-release@v2
with:
files: "pgmatlab-${{ github.event.release.tag_name }}.zip"
35 changes: 35 additions & 0 deletions .github/workflows/build-toolbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Build MATLAB Toolbox

on:
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set MATLAB version
uses: matlab-actions/setup-matlab@v2.1.2
with:
release: 'R2025a'

- name: Determine toolbox version
id: version
run: |
# Remove leading 'v' from git tag
echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV

- name: Build toolbox
run: |
matlab -batch "
opts = matlab.addons.toolbox.ToolboxOptions('pgmatlab_toolbox.prj');
opts.ToolboxVersion = getenv('VERSION');
matlab.addons.toolbox.packageToolbox(opts, sprintf('pgmatlab_%s.mltbx', getenv('VERSION')));"

- name: Attach to GitHub release
uses: softprops/action-gh-release@v2
with:
files: "pgmatlab_*.mltbx"
38 changes: 38 additions & 0 deletions .github/workflows/matlab-testing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: MATLAB Testing

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: [ "v2", "main" ]
pull_request:
branches: [ "v2", "main" ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ${{ matrix.os }}
strategy:
matrix:
matlab-version: [r2020a, r2021a, r2022a, r2023a, r2025a]
os: [ubuntu-latest, windows-latest, macos-latest]


steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up MATLAB
uses: matlab-actions/setup-matlab@v2.1.2
with:
version: ${{ matrix.matlab-version }}

- name: Run MATLAB Tests
uses: matlab-actions/run-tests@v2.1.2
with:
select-by-folder: tests
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pgmatlab/Array/.MATLABDriveTag
.MATLABDriveTag
pgmatlab/.MATLABDriveTag
pgmatlab/Array/.MATLABDriveTag
*.mltbx
237 changes: 237 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Contributing Guidelines

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.

## Getting Started

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.

## Testing

There is a comprehensive testing suite located in the [tests](tests) folder. To run these tests, run the following commands.

```commandline
cd tests;
runtests;
```

If you add new functionality to PAMGuardMatlab, please ensure you write appropriate unit tests in the testing suite.

> 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.

## Making a Pull Request

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.

The GitHub repository will automatically run the unit tests in MacOS, Linux, and Windows - and you can see this by viewing your pull request.

## Creating a New Release

Stable code is maintained through new releases. This allows users to download a lightweight copy of the code without development tools such as tests.

Upon the creation of a release, the following CI action is executed (allow 30-60 seconds for this to complete):

- The user-facing code (README.md, LICENCE, pgmatlab/*) is put in an archive and attached to the release.

Releases should be semantically named and tagged like so. These tags are dynamically inserted in the tarball and wheel uploaded to PyPI.

- V1.2.3
- Tag: v1.2.3
- V1.2.3 Beta 1
- Tag: v1.2.3-b1
- V1.2.3 Alpha 1
- Tag: v1.2.3-a1

## Structure

All the source code is found in the [pgmatlab/+pgmatlab](pgmatlab/+pgmatlab/) folder.

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.

PAMGuardMatlab has three main sub-packages:

1. [+core](pgmatlab/+pgmatlab/+core/): contains classes for reading chunks from data files.

2. [+db](pgmatlab/+pgmatlab/+db/): contains functions for interacting with the database (legacy).

3. [+utils](pgmatlab/+pgmatlab/+utils/): contains functions
for utilities used by the rest of the library.

## Adding New Modules

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.

### Creating a New Module Class

To create a new module, you need to extend the `StandardModule` class and implement the required abstract methods.

#### Template for a New Module

Create a new file in `pgmatlab/+pgmatlab/+core/+modules/` with the following template:

```matlab
classdef YourModuleName < pgmatlab.core.standard.StandardModule
properties (Access = public)
objectType = 'Your Object Type'; % Set this to match PAMGuard's object type
end

methods
function obj = YourModuleName()
% Constructor - set custom header/footer classes if needed
obj.header = @pgmatlab.core.standard.StandardModuleHeader;
obj.footer = @pgmatlab.core.standard.StandardModuleFooter;
obj.background = -1; % Set to a background class if needed
end

function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState)
% Read module-specific data from the binary file
% This is where you implement the actual data reading logic

% Example: Read some custom fields
data.customField1 = fread(fid, 1, 'int32');
data.customField2 = fread(fid, 1, 'double');

% Additional processing can be done here

% Return selState (1 = keep, 0 = skip, 2 = stop if sorted)
selState = 1;
end

function [data, selState] = readBackgroundImpl(obj, fid, data, fileInfo, length, identifier, selState)
% Optional: Implement background data reading if your module has background data
% Leave empty if no background data
end
end
end
```

#### Creating Custom Header Classes

If your module requires a custom header format, create a class extending `StandardModuleHeader`:

```matlab
classdef YourModuleHeader < pgmatlab.core.standard.StandardModuleHeader
methods
function data = readImpl(obj, fid, data, fileInfo, length, identifier)
% Call parent implementation first
data = readImpl@pgmatlab.core.standard.StandardModuleHeader(obj, fid, data, fileInfo, length, identifier);

% Read custom header fields
data.customHeaderField = fread(fid, 1, 'int32');

% Process additional header data as needed
end
end
end
```

#### Creating Custom Footer Classes

Similarly, for custom footers, extend `StandardModuleFooter`:

```matlab
classdef YourModuleFooter < pgmatlab.core.standard.StandardModuleFooter
methods
function data = readImpl(obj, fid, data, fileInfo, length, identifier)
% Call parent implementation first
data = readImpl@pgmatlab.core.standard.StandardModuleFooter(obj, fid, data, fileInfo, length, identifier);

% Read custom footer fields
data.customFooterField = fread(fid, 1, 'int32');
end
end
end
```

#### Creating Custom Background Classes

For modules with background data, extend `StandardBackground`:

```matlab
classdef YourModuleBackground < pgmatlab.core.standard.StandardBackground
properties (Access = public)
objectType = 'Your Background Object Type';
end

methods
function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState)
% Read background-specific data
data.backgroundField1 = fread(fid, 1, 'double');
data.backgroundField2 = fread(fid, [1, 10], 'int16');

selState = 1;
end
end
end
```

### Registering Your Module

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`:

```matlab
% In the file header case (-1) switch statement:
case 'Your Module Type'
switch fileInfo.fileHeader.streamName
case 'Your Stream Name'
moduleObj = pgmatlab.core.modules.YourModuleName();
% Add additional stream cases if needed
end
```

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.

### Testing Your Module

1. Create test data using your PAMGuard module
2. Add test cases to the appropriate test file in the `tests/` folder
3. Run the tests to ensure your module loads data correctly:

```matlab
cd tests;
runtests('YourModuleTest');
```

### Example: Complete Module Implementation

Here's a complete example of a simple module:

```matlab
classdef ExampleModule < pgmatlab.core.standard.StandardModule
properties (Access = public)
objectType = 'Example Detection';
end

methods
function obj = ExampleModule()
obj.header = @pgmatlab.core.standard.StandardModuleHeader;
obj.footer = @pgmatlab.core.standard.StandardModuleFooter;
obj.background = -1;
end

function [data, selState] = readImpl(obj, fid, data, fileInfo, length, identifier, selState)
% Read example-specific fields
data.detectionType = fread(fid, 1, 'int32');
data.confidence = fread(fid, 1, 'double');
data.frequency = fread(fid, 1, 'double');

% Validate data
if data.confidence < 0 || data.confidence > 1
warning('Invalid confidence value: %f', data.confidence);
end

selState = 1;
end
end
end
```

Then register it in `loadPamguardBinaryFile.m`:

```matlab
case 'Example Detector'
switch fileInfo.fileHeader.streamName
case 'Example Detections'
moduleObj = pgmatlab.core.modules.ExampleModule();
end
```
Loading
Loading