Skip to content

Add ChangeDetectionTask #2422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open

Conversation

keves1
Copy link

@keves1 keves1 commented Nov 21, 2024

This PR is to add a change detection trainer as mentioned in #2382.

Key points/items to discuss:

  • I used the OSCD dataset to test with and modified this dataset to use a temporal dimension.
  • With the added temporal dimension, Kornia’s AugmentationSequential doesn’t work, but can be combined with VideoSequential to support the temporal dimension (see Kornia docs). I overrode self.aug in the OSCDDataModule to do this but not sure if this should be incorporated into the BaseDataModule instead.
  • VideoSequential adds a temporal dimension to the mask. Not sure if there is a way to avoid this, or if this is desirable, but I added an if statement to the AugmentationSequential wrapper to check for and remove this added dimension.
  • The OSCDDataModule applies _RandomNCrop augmentation, but this does not work for time series data. I'm not sure how to modify _RandomNCrop to fix this and would appreciate some help/guidance.
  • There are a few tests that I need to still make pass.

cc @robmarkcole

@github-actions github-actions bot added datasets Geospatial or benchmark datasets testing Continuous integration testing trainers PyTorch Lightning trainers transforms Data augmentation transforms datamodules PyTorch Lightning datamodules labels Nov 21, 2024
@robmarkcole
Copy link
Contributor

I wonder if we should limit the scope to change between two timesteps and binary change - then we can use binary metrics and provide a template for the plot methods. I say this because this is the most common change detection task by a mile. Might also simplify the augmentations approach? Treating as a video sequence seems overkill.
Also I understand there is support for multitemporal coming later.

@adamjstewart
Copy link
Collaborator

I wonder if we should limit the scope to change between two timesteps

I'm personally okay with this, although @hfangcat has a recent work using multiple pre-event images that would be nice to support someday (could be a subclass if necessary).

and binary change

Again, this would probably be fine as a starting point, although I would someday like to make all trainers support binary/multiclass/multilabel, e.g., #2219.

provide a template for the plot methods.

Could also do this in the datasets (at least for benchmark NonGeoDatasets). We're also trying to remove explicit plotting in the trainers: #2184

I say this because this is the most common change detection task by a mile.

Agreed.

Might also simplify the augmentations approach? Treating as a video sequence seems overkill.

I actually like the video augmentations, but let me loop in the Kornia folks to get their opinion: @edgarriba @johnnv1

Also I understand there is support for multitemporal coming later.

Correct, see #2382 for the big picture (I think I also sent you a recording of my presented plan).

@adamjstewart
Copy link
Collaborator

VideoSequential adds a temporal dimension to the mask. Not sure if there is a way to avoid this

Can you try keepdim=True?

I added an if statement to the AugmentationSequential wrapper to check for and remove this added dimension.

@ashnair1 would this work directly with K.AugmentationSequential now? We are trying to phase out our AugmentationSequential wrapper now that upstream supports (almost?) everything we need.

@keves1
Copy link
Author

keves1 commented Nov 22, 2024

I will go ahead and make changes for this to be for binary change and two timesteps, sounds like a good starting point.

Can you try keepdim=True?

I tried this and it didn't get rid of the other dimension. I also looked into extra_args but didn't see any options to help with this.

Could also do this in the datasets (at least for benchmark NonGeoDatasets). We're also trying to remove explicit plotting in the trainers

I was going to add plotting in the trainer, but would you rather not then? What would this look like in the dataset?

@robmarkcole
Copy link
Contributor

Perhaps there should even be a base class ChangeDetection and subclasses for BinaryChangeDetection etc?

@adamjstewart
Copy link
Collaborator

That's exactly what I'm trying to undo in #2219.

@adamjstewart
Copy link
Collaborator

I was going to add plotting in the trainer, but would you rather not then?

We can copy-n-paste the validation_step plotting stuff used by other trainers, but that's probably going to disappear soon (I think we're just waiting on testing in #2184.

What would this look like in the dataset?

See OSCD.plot()

@github-actions github-actions bot added the losses Geospatial loss functions label Dec 5, 2024
@keves1
Copy link
Author

keves1 commented Dec 5, 2024

I've updated this to now support only binary change with two timesteps.
To get test_weight_file in test_change.py to work with the two images stacked on the channel dimension for Unet, I modified the pytest fixture model() in conftest.py to use timm to create the model instead of torchvision, so that an in_channels parameter can be passed.

I still haven't been able to figure out how to make transforms.transforms._RandomNCrop work with the added temporal dimension. It seems to have something to do with _NCropGenerator not properly handling the temporal dimension but I really don't understand what is going on there.

Copy link
Collaborator

@adamjstewart adamjstewart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you resolve the merge conflicts so we can run the tests?

@keves1
Copy link
Author

keves1 commented Dec 17, 2024

I'm going to need some help figuring out how to get transforms.transforms._RandomNCrop to work with the added temporal dimension (this is used by the OSCD dataset). I've delved into this a few times but with my lack of familiarity with Kornia I haven't been able to track down the source of the issue. You can see the issue by running tests/trainers/test_change.py::TestChangeDetectionTask::test_trainer.

Also, disregard my earlier comments about Kornia VideoSequential adding a dimension to the mask, this seems to have resolved with the latest Kornia version.

@adamjstewart adamjstewart added this to the 0.7.0 milestone Dec 19, 2024
@github-actions github-actions bot removed the losses Geospatial loss functions label Dec 19, 2024
@keves1
Copy link
Author

keves1 commented Jan 7, 2025

I see that in the automated pytest checks (in tests / minimum) there was a syntax error, but I don't see this issue locally. How do I resolve this?

@adamjstewart
Copy link
Collaborator

Couldn't reproduce either, might be a bug specific to older Python versions. Anyway, undid the change on the line it was complaining about, let's see if that fixes it.

@adamjstewart adamjstewart mentioned this pull request Dec 18, 2024
37 tasks
@keves1
Copy link
Author

keves1 commented Jan 8, 2025

I noticed I also need to update tests/datasets/test_oscd.py. And I think we are removing tests/datamodules/test_oscd.py per #978 now that we have a change detection task, right?

Also, how do you usually run prettier locally? I see there is an issue in the prettier check here but it isn't installed in the devcontainer unless I'm mistaken.

@adamjstewart
Copy link
Collaborator

I think we are removing tests/datamodules/test_oscd.py per #978 now that we have a change detection task, right?

Correct.

Also, how do you usually run prettier locally? I see there is an issue in the prettier check here but it isn't installed in the devcontainer unless I'm mistaken.

To be honest, I rarely run prettier locally. But here are the docs on how to do it: https://torchgeo.readthedocs.io/en/latest/user/contributing.html#linters

I've also never used the devcontainer before. But it looks like there is a VS Code extension for prettier: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode. Feel free to add that to the devcontainer in a separate PR if you want.

@keves1
Copy link
Author

keves1 commented Feb 24, 2025

How hard would it be to do late fusion, so pass each image through the encoder separately, then concatenate them, then pass them through the decoder?

@adamjstewart I think this is a remaining question to resolve on this PR, and I realized that this is already what torchgeo.models.FCSiamConc is doing, right? And FCSiamConc is one of the model options for this trainer. So maybe it makes sense to also have an option that concatenates the images before passing through the model (keeping the Unet how it is).

@adamjstewart adamjstewart removed this from the 0.7.0 milestone Mar 13, 2025
@isaaccorley
Copy link
Collaborator

I'll take a look and se what can be done about running with the temporal dim.

@adamjstewart
Copy link
Collaborator

We need to upstream _RandomNCrop and _ExtractPatches to Kornia so we don't have to maintain them anymore. For now, if RandomCrop and CenterCrop work, I would be fine with using them until Kornia has a better option.

@keves1
Copy link
Author

keves1 commented Apr 18, 2025

@isaaccorley I'm trying to get test coverage for predict_step in ChangeDetectionTask, but it looks like this is not being tested because there isn't a predict dataset defined in the OSCD datamodule. Is it valid to just define self.dataset = OSCD(split='test') in setup of the OSCD datamodule?

@isaaccorley
Copy link
Collaborator

@isaaccorley I'm trying to get test coverage for predict_step in ChangeDetectionTask, but it looks like this is not being tested because there isn't a predict dataset defined in the OSCD datamodule. Is it valid to just define self.dataset = OSCD(split='test') in setup of the OSCD datamodule?

Yes I think that should be sufficient.

@keves1
Copy link
Author

keves1 commented Apr 18, 2025

@isaaccorley and @adamjstewart I think this is ready. Let me know what I should put in ChangeDetectionTask for versionadded.

@keves1 keves1 requested a review from isaaccorley April 18, 2025 19:13
@isaaccorley isaaccorley added this to the 0.8.0 milestone Apr 18, 2025
isaaccorley
isaaccorley previously approved these changes Apr 18, 2025
Copy link
Collaborator

@isaaccorley isaaccorley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lgtm. I believe it should be version 0.8 since this would be a new feature and that is the next milestone. Will wait for Adam's review though before merging.

@isaaccorley isaaccorley marked this pull request as ready for review April 18, 2025 19:23
@adamjstewart adamjstewart added the backwards-incompatible Changes that are not backwards compatible label Apr 19, 2025
Copy link
Collaborator

@adamjstewart adamjstewart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SemanticSegmentationTask, on which this trainer is based, changed a lot in #2560, #2219, and #2690. We should update this PR with those same changes.

@keves1
Copy link
Author

keves1 commented Apr 21, 2025

SemanticSegmentationTask, on which this trainer is based, changed a lot in #2560, #2219, and #2690. We should update this PR with those same changes.

I could add the denormalization for plotting, but as far as adding support for multiclass and multilabel, we specifically decided that this PR would just support binary change detection, so I'd like to keep it like that and someone else can extend it to those cases later.

@hkristen
Copy link

SemanticSegmentationTask, on which this trainer is based, changed a lot in #2560, #2219, and #2690. We should update this PR with those same changes.

I could add the denormalization for plotting, but as far as adding support for multiclass and multilabel, we specifically decided that this PR would just support binary change detection, so I'd like to keep it like that and someone else can extend it to those cases later.

I could have a look at adding multiclass & multilabel support once this PR is merged. As I have done something similar already for my research. If you @adamjstewart would guide me a bit for my first PR to torchgeo on this one?

@adamjstewart
Copy link
Collaborator

@keves1 yes, let's add denormalizing and switch from if-statements to match-statements.

I'll let @hkristen take a stab at non-binary change detection in a follow-up PR after this is merged. I know @hfangcat is also interested in change detection for 3+ images, I'm not sure if that would be a feature to add to ChangeDetectionTask or SemanticSegmentationTask. I would like to merge this PR soon as I think it's almost ready, we can iterate on additional features later.

@keves1
Copy link
Author

keves1 commented May 8, 2025

@adamjstewart I added denormalizing and switched to match statements, as well as made the other changes you suggested. I think it's ready now.

@keves1
Copy link
Author

keves1 commented May 8, 2025

Looks like the way I did the monkeypatch to test predict_step when there isn't a predict dataset didn't work (it didn't run predict_step). What would be the right way to do this? I tried setting predict_dataloader and predict_dataset.

keepdim=True,
)
batch = aug(batch)
batch['prediction'] = y_hat.argmax(dim=-1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be y_hat >= threshold, not argmax? See SemanticSegmentationTask

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right that it should be using the threshold, not argmax. But in SemanticSegmentationTask (and here), shouldn't sigmoid be applied to y_hat before thresholding? So it would be batch['prediction'] = (y_hat.sigmoid() >= 0.5).long().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right, good catch! Want to submit a separate PR to fix ClassificationTask and SemanticSegmentationTask? Then we can backport it to 0.7.1.

)

def forward(self, x: torch.Tensor) -> torch.Tensor:
return cast(torch.Tensor, self.conv1(x))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return cast(torch.Tensor, self.conv1(x))
x = self.conv1(x)
return x

Then we don't need to mess with cast


def test_predict(self, fast_dev_run: bool) -> None:
datamodule = PredictChangeDetectionDataModule(
root='tests/data/oscd',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
root='tests/data/oscd',
root=os.path.join('tests', 'data', 'oscd'),

Windows, same below

class ChangeDetectionTask(BaseTask):
"""Change Detection. Currently supports binary change between two timesteps.

.. versionadded: 0.8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. versionadded: 0.8
.. versionadded:: 0.8

Comment on lines +60 to +61
model does not support pretrained weights. Pretrained ViT weight enums
are not supported yet.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isaaccorley is this still true? I thought SMP now supports ViT backbones. Or is this something we need to change in our weight loader?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true anymore but will need to do some testing to make sure it works out of the box in our trainers.

@adamjstewart
Copy link
Collaborator

Very close to being ready to merge, excited to finally get this in!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backwards-incompatible Changes that are not backwards compatible datamodules PyTorch Lightning datamodules datasets Geospatial or benchmark datasets testing Continuous integration testing trainers PyTorch Lightning trainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants