Skip to content
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

The tutorial accompanying the pull request #7308 for MONAI core, which adds SURE-loss and Conjugate Gradient #1631

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

Conversation

cxlcl
Copy link

@cxlcl cxlcl commented Feb 4, 2024

The tutorial accompanying the pull request #7308 for MONAI core, which adds SURE-loss and Conjugate Gradient

Description

A few sentences describing the changes proposed in this pull request.

Checks

  • Avoid including large-size files in the PR.
  • Clean up long text outputs from code cells in the notebook.
  • For security purposes, please check the contents and remove any sensitive info such as user names and private key.
  • Ensure (1) hyperlinks and markdown anchors are working (2) use relative paths for tutorial repo files (3) put figure and graphs in the ./figure folder
  • Notebook runs automatically ./runner.sh -t <path to .ipynb file>

Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

from .normalization import get_normalization


class UNet(nn.Module):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we may need to use the components from the MONAI repo.

Copy link
Author

@cxlcl cxlcl Feb 6, 2024

Choose a reason for hiding this comment

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

Did you mean this one? We defined the UNet ourselves here since the pretrained model we are using is from this definition. Otherwise, we need to convert the weights/names to be consistent to the Unet from MONAI.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, do we have specific reason to add this pretrained model otherwise we can just ensure functionality level works?
What do you think? cc @ericspod

Copy link
Member

Choose a reason for hiding this comment

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

It would be best wherever possible to use MONAI components. We have our own UNet class but is structured differently from this one, and we do have other blocks and layers that could be substituted for those defined here. If possible please use those in MONAI unless it causes significant headaches converting the weight names in your pretrained data.

Copy link
Member

Choose a reason for hiding this comment

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

I would put the code for SMRDOptimizer into its own file since it's so large in this notebook.


self.end_conv = nn.Conv2d(ngf, config.data.channels, 3, stride=1, padding=1)

self.res1 = nn.ModuleList(
Copy link
Member

Choose a reason for hiding this comment

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

You could use Sequential instead of ModuleList here and elsewhere in this class, then you wouldn't need _compute_cond_module.

Comment on lines 154 to 159
class NoneNorm2d(nn.Module):
def __init__(self, num_features, bias=True):
super().__init__()

def forward(self, x):
return x
Copy link
Member

@ericspod ericspod Feb 7, 2024

Choose a reason for hiding this comment

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

You can actually just use torch.nn.Identity whose constructor takes any arguments and just does what this class does.

generative/smrd/README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
generative/README.md Outdated Show resolved Hide resolved
Copy link

review-notebook-app bot commented Feb 7, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-07T13:03:59Z
----------------------------------------------------------------

Line #1.    def denoise_cg_step(

Would it make sense to put this in Core with the loss function definition?


Copy link

review-notebook-app bot commented Feb 7, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-07T13:04:00Z
----------------------------------------------------------------

The key thing in this tutorial is what the optimizer is doing with the loss function to do the reconstruction. I know it's explained in the paper but I would much rather have more explanation here with more insightful comments in the code of the optimizer. I have mentioned in other comments about simplifying the class by removing unneeded definitions and moving utility methods elsewhere, making the class simpler will help in understanding what it's doing. You can also link/cite to the paper here to point people in the right direction.


Copy link

review-notebook-app bot commented Feb 7, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-07T13:04:00Z
----------------------------------------------------------------

Line #23.        def _dict2namespace(self, config):

Methods like this which don't use self are better as utility functions in a utility source file somewhere else. Moving these methods out could reduce the amount of code in this class and make it more understandable.


Copy link

review-notebook-app bot commented Feb 7, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-07T13:04:01Z
----------------------------------------------------------------

Line #60.                return MulticoilForwardMRI(self.config["orientation"])(

I would suggest creating an object instance of MulticoilForwardMRI in the constructor and use that in this function. This avoids creating the object every time the function is called.


@marksgraham
Copy link
Contributor

Thanks for the contribution @cxlcl.

There's a lot of code in this tutorial to load your pretrained model. I went through it a bit with some minor suggestions but not in great detail. If it's possible to save your pretrained model as a Torchscript object we can do away with the model code in this tutorial and point people to the repo for the paper if they want to see what the model is. If not then, wherever possible without breaking your saved weights too much, please replace your layer/block definitions with those from Core to reduce overall code volume.

I would still move the code for the optimizer to its own file so that the notebook isn't huge, unless you can reduce its size given the comments I made. The notebook would benefit with more description of what's going on with this technique.

There's quite a few things here but it looks like a good addition overall. The CI fails aren't major issues we can fix later.

@marksgraham you may want to look over this as well.

Thanks!

Looks good to me, I think this stuff is complementary to the diffusion work we're doing. As already mentioned by others, would have been great to use the monai networks already defined for the pretrained network (would cut down on the code added and make it easier for others to integrate this into their own work). I wonder if it would be hard to re-train the network using a MONAI implementation and then integrate that? Might be easier than trying to convert weights over

cxlcl and others added 6 commits February 24, 2024 10:32
@cxlcl
Copy link
Author

cxlcl commented Feb 25, 2024

Thanks a lot for all the suggestions. Based on the suggestions, I've made several changes:

  1. Removed the dependency on the UNet structure definition on code files. Following @ericspod 's suggestion, the model definition are saved in TorchScript. So any user can load it directly without relying on additional files. This reduces the code size for the tutorial. The pointer to the definition for the UNet is added in case the user is interested. After a closer look at the layers in the pre-trained model, I found it is not feasible to replace the layers there with the ones in MONAI, since the ResBlock and RefineBlock within their UNet is different from the ones in MONAI. Rewrite those layers end up with writing costumed ResBlock and RefineBlock.

  2. Revised the notebook. The notebook is revised such that each of the Conjugate Gradient and SURE loss has a toy example, illustrating what it does. Then the two modules are combined in the SMRDoptimizer, in a separate file. More descriptions are added to illustrate the basic ideas.

  3. Minor changes to the code, followed the suggestions from the revision

Please let me know how it looks and thanks again for providing the feedbacks!

Comment on lines +15 to +39
class EMAHelper:
def __init__(self, mu=0.999):
self.mu = mu
self.shadow = {}

def register(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
self.shadow[name] = param.data.clone()

def update(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
self.shadow[name].data = (1.0 - self.mu) * param.data + self.mu * self.shadow[name].data

def ema(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
param.data.copy_(self.shadow[name].data)
Copy link
Member

Choose a reason for hiding this comment

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

A suggested shortening:

Suggested change
class EMAHelper:
def __init__(self, mu=0.999):
self.mu = mu
self.shadow = {}
def register(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
self.shadow[name] = param.data.clone()
def update(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
self.shadow[name].data = (1.0 - self.mu) * param.data + self.mu * self.shadow[name].data
def ema(self, module):
if isinstance(module, nn.DataParallel):
module = module.module
for name, param in module.named_parameters():
if param.requires_grad:
param.data.copy_(self.shadow[name].data)
class EMAHelper:
def __init__(self, mu=0.999):
self.mu = mu
self.shadow = {}
def _get_parameters(self,module):
if isinstance(module, nn.DataParallel):
module = module.module
return module.named_parameters()
def register(self, module):
for name, param in self._get_parameters(module):
if param.requires_grad:
self.shadow[name] = param.data.clone()
def update(self, module):
for name, param in self._get_parameters(module):
if param.requires_grad:
self.shadow[name].data = (1.0 - self.mu) * param.data + self.mu * self.shadow[name].data
def ema(self, module):
for name, param in self._get_parameters(module):
if param.requires_grad:
param.data.copy_(self.shadow[name].data)

def ema_copy(self, module):
if isinstance(module, nn.DataParallel):
inner_module = module.module
module_copy = type(inner_module)(inner_module.config).to(inner_module.config.device)
Copy link
Member

Choose a reason for hiding this comment

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

Can we not do inner_module.clone()? This line will work if the type of the inner module is something constructable like this, but the Pytorch pattern is to use the clone method then the load_state_dict() method call isn't needed.

Copy link

review-notebook-app bot commented Feb 26, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-26T18:27:23Z
----------------------------------------------------------------

Here I would suggest using download_and_extract since these commands will not work under Windows. You should be able to set things up correctly in Python code and not have to resort to command line calls.


Copy link

review-notebook-app bot commented Feb 26, 2024

View / edit / reply to this conversation on ReviewNB

ericspod commented on 2024-02-26T18:27:24Z
----------------------------------------------------------------

This is great explanation. I would add a short paragraph just after the title at the top of this notebook echoing this here, describing what SMRD is and what the SURE loss is used for ("The SURE loss is used to decide when to stop the generation of the dense MRI, to avoid artifacts due to excessive sampling iterations." is a great summation of the motivation).


@ericspod
Copy link
Member

This looks much better and much smaller. I had a few comments in the notebook and code but overall it's looking great. The CI fails can be fixed as well, it's too bad this can't be tested with papermill at the moment but we can live with it.

Copy link

review-notebook-app bot commented Feb 27, 2024

View / edit / reply to this conversation on ReviewNB

KumoLiu commented on 2024-02-27T08:12:24Z
----------------------------------------------------------------

All of our tutorial notebooks are standardized to commence with these three sections: License, Setup Environment, and Setup Imports. To ensure that the ci checks pass successfully, could you please adjust your notebook to align with this structure?

https://github.com/Project-MONAI/tutorials/actions/runs/8035965820/job/21949132998?pr=1631#step:7:10

You can refer here:

https://github.com/Project-MONAI/tutorials/blob/main/CONTRIBUTING.md#create-a-notebook

https://github.com/Project-MONAI/tutorials/blob/main/.github/contributing_templates/notebook/example_feature.ipynb


Copy link

review-notebook-app bot commented Feb 27, 2024

View / edit / reply to this conversation on ReviewNB

KumoLiu commented on 2024-02-27T08:12:25Z
----------------------------------------------------------------

Line #7.    import pickle, gzip

https://github.com/Project-MONAI/tutorials/actions/runs/8035965813/job/21949133004?pr=1631#step:7:335

I've noticed several discrepancies with the PEP8 style guide in the current work. Could you kindly address these to ensure our codebase maintains its consistency and readability? I appreciate your cooperation in adhering to our coding standards.


Copy link

review-notebook-app bot commented Feb 27, 2024

View / edit / reply to this conversation on ReviewNB

KumoLiu commented on 2024-02-27T08:12:25Z
----------------------------------------------------------------

Line #15.    from smrd_optimizer import SMRDOptimizer

Where does this SMRDOptimizer define now?


cxlcl commented on 2024-02-28T00:21:13Z
----------------------------------------------------------------

Added in the latest commit. Forgot to include it in the previous commit... Thanks for the comments for the code style. I will address it later this week.

Copy link

review-notebook-app bot commented Feb 27, 2024

View / edit / reply to this conversation on ReviewNB

KumoLiu commented on 2024-02-27T08:12:26Z
----------------------------------------------------------------

Line #2.    from monai.networks.layers import ConjugateGradient

I also recommend moving these import parts into "Setup imports".


Copy link

review-notebook-app bot commented Feb 27, 2024

View / edit / reply to this conversation on ReviewNB

KumoLiu commented on 2024-02-27T08:12:27Z
----------------------------------------------------------------

Line #2.    from monai.losses.sure_loss import SURELoss

Also here.


Copy link
Author

cxlcl commented Feb 28, 2024

Added in the latest commit. Forgot to include it in the previous commit... Thanks for the comments for the code style. I will address it later this week.


View entire conversation on ReviewNB

@ericspod
Copy link
Member

Hi @cxlcl sorry for losing track on this PR. Could you please fix the DCO issue and see if there's anything in the unresolved comments that need to be addressed still? Thanks!

@cxlcl
Copy link
Author

cxlcl commented Apr 29, 2024

Hi @ericspod , sorry for having not attended to this for long. I will look into those DCO issue this week. As for the comments, I guess all the remaining comments and suggestions are about the DCO or pep8 standard.

@ericspod
Copy link
Member

Hi @cxlcl if you're still wanting to finish this tutorial we can definitely review once the issues are sorted. Things have changed a bit so the conflicts and other things will have to be sorted before review and merging. Thanks!

@cxlcl
Copy link
Author

cxlcl commented Sep 26, 2024

Hi Eric, Sorry for the late response. Yes, I would like to resolve the DCO issue. I've tried to resolve it by first run the local check script and upload. But the issue is still there in the CI . Is there any other way to check it locally or quickly without waiting for the results from the online CI process?

@ericspod
Copy link
Member

Hi Eric, Sorry for the late response. Yes, I would like to resolve the DCO issue. I've tried to resolve it by first run the local check script and upload. But the issue is still there in the CI . Is there any other way to check it locally or quickly without waiting for the results from the online CI process?

The DCO details describes how to do an empty remedial commit. I don't see that here in the commit logs so I don't think you've pushed it if you tried. The other CI fails are for different things you have to resolve, as well as the conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants