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

Added NAFE #24

Closed
wants to merge 1 commit into from
Closed

Added NAFE #24

wants to merge 1 commit into from

Conversation

MinaSGorgi
Copy link

@MinaSGorgi MinaSGorgi commented Apr 8, 2019

Description

This is a complete working implementation of the NAFE filter. Only missing feature is setting default values for input and output margins according to image contrast which is provided by original version. Otherwise, it supports all features including default parameters for different wavelengths. Original code is not open-sourced, hence can't compare it code-wise and thorough testing against original code results is still in progress. Differences between this implementation and other available open-source version are mainly implementation-wise. The only non-trivial difference is using constant noise removal sigma.

An output sample: (400x400) in ~10 mins
Screenshot from 2019-04-08 05-56-46

Creating this PR for feedback. @nabobalis @dpshelio

Fixes #6

@pep8speaks
Copy link

pep8speaks commented Apr 8, 2019

Hello @MinaSGorgy! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

Line 44:20: E251 unexpected spaces around keyword / parameter equals
Line 92:101: E501 line too long (142 > 100 characters)
Line 214:9: E731 do not assign a lambda expression, use a def

Comment last updated at 2019-05-04 01:12:18 UTC

@ghost
Copy link

ghost commented Apr 8, 2019

Thanks for the pull request @MinaSGorgy! Everything looks great!

@nabobalis
Copy link
Contributor

nabobalis commented Apr 8, 2019

Thanks for this, I will have a proper review later on but any chance you can change your image to focus on the edges of the Sun?

I'd like to see the output on a patch of loop structures nearer the edge.

@MinaSGorgi
Copy link
Author

I get what you want but current sample images I am working with doesn't have such feature. If you can point a specific image in mind I can definitely provide that. This is the best I have currently.
Screenshot from 2019-04-08 08-24-12

NB: I currently remove all pixels out of ROI while instead I should keep them but evaluate new pixels for only the ones inside ROI. Will change locally for now.

@nabobalis
Copy link
Contributor

That region works, are you doing any extra normalization to plot the image?

return new_margin[0] + (new_margin[1] - new_margin[0]) * (((old_value - old_margin[0]) / (old_margin[1] - old_margin[0])) ** (1.0 / power))


class _Nafe():
Copy link
Contributor

@nabobalis nabobalis Apr 8, 2019

Choose a reason for hiding this comment

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

I am not sure I am in favour of having a class here, is it not possible to avoid this?

Copy link
Author

Choose a reason for hiding this comment

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

Probably yes. Will apply or report if it's not.

Copy link
Author

Choose a reason for hiding this comment

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

I am curious why would a class not be preferred. I come from a C++ background so that's why it seemed the way to go for me.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I don't understand why we need a class here.

Copy link
Author

Choose a reason for hiding this comment

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

Basically used for keeping reference of the shared variables across the processes pool. Just making the code more readable and the mapping function cleaner.

Copy link
Author

Choose a reason for hiding this comment

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

This: pool.imap(nafe_instance.transform, indices), total=map_data.size) is what I mean. I need to use imap because the order of the results matter since they are pixels values. AFAIK the only other way to pass the arguments to imap would be creating an iterable whose elements will mainly be in the form (row, col, some constant arguments). I thought since they are constant arguments this seemed a typical case for using a class.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well if it's the best way to do it, it can stay.

Copy link
Author

Choose a reason for hiding this comment

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

Will keep for now then.

Copy link
Member

Choose a reason for hiding this comment

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

I'm ok with the class, but why not having it all as one? I mean to include the nafe function as to execute it?

Copy link
Author

Choose a reason for hiding this comment

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

I have no preference but current implementation doesn't require a class anymore.

axes[0].set_title('Original Image')
axes[1].imshow(nafe_image, cmap='gray')
axes[1].set_title('Enhanced Image')
plt.show()
Copy link
Contributor

Choose a reason for hiding this comment

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

This can all go. We don't want to be able to call the function like this. All those arser.add_argument( should the parameters in the corresponding doc strings.

This could be turned into a seperate gallery example.

Copy link
Author

Choose a reason for hiding this comment

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

It's basically for debugging purposes but will apply for final PR.

Copy link
Member

Choose a reason for hiding this comment

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

Though I agree with @nabobalis and probably this wouldn't be the place. I imagine sunkit-image could have some command line programs that run some of these functions. This would need, however some extra additions to the setup.py.

Copy link
Author

Choose a reason for hiding this comment

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

Will remove from file for now and investigate where to migrate after finishing testing.

def nafe(input_map, imargin=None, gamma=None, omargin=None, nafe=False,
nafe_weight=None, roi_p=None, roi_f=None, nproc=None, hbins=None,
nrs=15, gb=0.3, lb=0.7, gw=0.3, lw=0.7, n=129,
c=[1 for _ in range(12)]):
Copy link
Contributor

Choose a reason for hiding this comment

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

This just creates a list of 1 that is 12 long?

Copy link
Author

Choose a reason for hiding this comment

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

Yes.

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't put a loop as part of the arguments. Rather None and then check it later and create the ones. Then, maybe this is more readable:

c = np.ones(12)

Copy link
Author

Choose a reason for hiding this comment

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

Will apply the ones one.

----------
input_map : `~sunpy.map.sources.sdo.AIAMap`
The original input image to be enhanced.

Copy link
Contributor

Choose a reason for hiding this comment

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

You should remove these line breaks between parameters.

Copy link
Author

Choose a reason for hiding this comment

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

Will apply.

input_map : `~sunpy.map.sources.sdo.AIAMap`
The original input image to be enhanced.

imargin: `~tuple`
Copy link
Contributor

Choose a reason for hiding this comment

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

The ~ shorterns the name in the sphinx build, you only want it for long import. For the basic python types, you don't need them.

Copy link
Author

Choose a reason for hiding this comment

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

Will apply.


Parameters
----------
input_map : `~sunpy.map.sources.sdo.AIAMap`
Copy link
Contributor

Choose a reason for hiding this comment

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

This will only work on AIAMap and not any Map?

Copy link
Author

Choose a reason for hiding this comment

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

For current implementation: anything with .meta["wavelnth"] and .data

Copy link
Author

Choose a reason for hiding this comment

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

Should I change this to take a numpy array instead and an optional wavelength argument? The original program uses the wavelength to calculate the optimal values for some other parameters. I can assign the default values for these parameters instead if not given a wavelength.

Copy link
Contributor

Choose a reason for hiding this comment

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

That would be a good idea.

Copy link
Author

Choose a reason for hiding this comment

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

Will apply.

Copy link
Member

Choose a reason for hiding this comment

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

I think I prefer a map instead of a numpy array as an input, as it would be the most used. If I have a numpy array, then I can create a map instead to run it through here.

Copy link
Author

Choose a reason for hiding this comment

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

I thought it would be easier to pass the map.data rather than possibly having to create a map from a numpy array. Again I have no preference. If a map would be sent I will check if its header contains info about the wavelength else I will use default value.

@MinaSGorgi
Copy link
Author

Thanks @nabobalis for your review and feedback. Regarding extra normalization, nothing not mentioned in the algorithm.

@nabobalis
Copy link
Contributor

In one of the examples in the SunPy Gallery, we apply just a log scale to the intensity. Any chance for the second example, you apply that to both images?

@MinaSGorgi
Copy link
Author

Applying logNorm on the last 2 images gives:
Screenshot from 2019-04-08 20-50-31

@nabobalis
Copy link
Contributor

nabobalis commented Apr 9, 2019

Can you do a comparison with the other Python version?

Also with the original version, are any of the examples got a timestamp we can trace? If so, we could download the original data and do a more "direct" comparison.

@nabobalis
Copy link
Contributor

Applying logNorm on the last 2 images gives:
Screenshot from 2019-04-08 20-50-31

Thanks for that! Maybe it's just my old age, but I can't really spot any difference between these two images with the scaling now the same.

@nabobalis nabobalis marked this pull request as ready for review April 9, 2019 10:45
Copy link
Member

@dpshelio dpshelio left a comment

Choose a reason for hiding this comment

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

Great work @MinaSGorgy
This still needs tests!

sunkit_image/nafe.py Show resolved Hide resolved
sunkit_image/nafe.py Outdated Show resolved Hide resolved
def nafe(input_map, imargin=None, gamma=None, omargin=None, nafe=False,
nafe_weight=None, roi_p=None, roi_f=None, nproc=None, hbins=None,
nrs=15, gb=0.3, lb=0.7, gw=0.3, lw=0.7, n=129,
c=[1 for _ in range(12)]):
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't put a loop as part of the arguments. Rather None and then check it later and create the ones. Then, maybe this is more readable:

c = np.ones(12)


def nafe(input_map, imargin=None, gamma=None, omargin=None, nafe=False,
nafe_weight=None, roi_p=None, roi_f=None, nproc=None, hbins=None,
nrs=15, gb=0.3, lb=0.7, gw=0.3, lw=0.7, n=129,
Copy link
Member

Choose a reason for hiding this comment

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

I dislike very short variable names. nrs, gb, ...? I like to look at the options and know what these parameters are, if I don't need to read the documentation, then the better, that means is self-explanatory.

Copy link
Author

Choose a reason for hiding this comment

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

Ideally the user would never change these parameters values. Some of them even aren't mentioned in the paper but I adopted them from the original software implemented by the paper's author. In this software they are even read from a file containing fine tuned default values. The author exposes changing them through changing the file, so I thought I would rather expose them as functions arguments. I totally agree with having self-explanatory variables as I normally go for but I took these as named in the mentioned file. Of course I can change if you find it better to.

sunkit_image/nafe.py Outdated Show resolved Hide resolved
sunkit_image/nafe.py Outdated Show resolved Hide resolved
sunkit_image/nafe.py Outdated Show resolved Hide resolved
return new_margin[0] + (new_margin[1] - new_margin[0]) * (((old_value - old_margin[0]) / (old_margin[1] - old_margin[0])) ** (1.0 / power))


class _Nafe():
Copy link
Member

Choose a reason for hiding this comment

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

I'm ok with the class, but why not having it all as one? I mean to include the nafe function as to execute it?

axes[0].set_title('Original Image')
axes[1].imshow(nafe_image, cmap='gray')
axes[1].set_title('Enhanced Image')
plt.show()
Copy link
Member

Choose a reason for hiding this comment

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

Though I agree with @nabobalis and probably this wouldn't be the place. I imagine sunkit-image could have some command line programs that run some of these functions. This would need, however some extra additions to the setup.py.

sunkit_image/nafe.py Outdated Show resolved Hide resolved
@nabobalis
Copy link
Contributor

Thanks for the review @dpshelio

@MinaSGorgi
Copy link
Author

Thanks @dpshelio for your review 😄 I think we mainly need to decide on using a map or numpy array as input. Meanwhile I am working on testing. Will reopen a PR once all changes are applied and testing is complete.

@dpshelio dpshelio added the [WIP] label Apr 14, 2019
@dpshelio
Copy link
Member

No need to re-open the PR, you can add to this, bit by bit. I've just labelled as WIP as you did on the title.

@MinaSGorgi MinaSGorgi force-pushed the master branch 3 times, most recently from 3b0edb7 to 3d8de26 Compare May 3, 2019 14:49
@MinaSGorgi
Copy link
Author

MinaSGorgi commented May 3, 2019

Sorry for late reply, last two weeks have been TOO hectic.

Applied all changes requested mainly:

  • Removed NAFE class and improved multiprocessing.
  • Changed to accepting a sunpy.map.Map and returning same.[1]
  • Removed custom sub-mapping.
  • Changed printing warnings to Python warn.
  • Added missing documentation.
  • Added testing.[2]

[1] Ideally nafe should support ROI but it's removed for now since some information is currently missing.
[2] Breaks CI for introducing PyTest. Missing main test comparing module output to original software because current AIA maps in samples are rejected by original software(says it treats them as colored images which aren't supported) and I am facing trouble getting AIA images due to some internal server errors.

TODO:

  • Add missing test which mainly would be a very small portion of an AIA map(something like 300x300) to run fast and just test calculations.
  • Support ROI (future work)

Finally for comparing results, the only maps I currently have that are accepted by the original software are 4k*4k which would take quite some time to process. Cropping them with SunPy produces rejected maps for some reason.

@nabobalis @dpshelio

@MinaSGorgi MinaSGorgi changed the title Added nafe.py [WIP] Added NAFE May 3, 2019
@nabobalis
Copy link
Contributor

Thanks for the update. I will have to review it in more detail later on.

Just a few comments tho:

I don't see why adding pytest would break any of the CI.

I think ROI would be nice but not really important. People should probably pass in submaps if they want a smaller area.

I did try to load in some data I downloaded and NAFE would not accept it, I don't know why tho.

Can you not crop a region within the NAFE windows program? Should hopefully crop the 4k image down a bit.

I have to reiterate tho, if the before and after images are the same after we apply some normalization, has the code worked correctly?

@nabobalis nabobalis added this to the v0.1.0 milestone May 3, 2019
@MinaSGorgi
Copy link
Author

Welcome @nabobalis . Take your time.

Regarding CI break: I thought I saw something suggesting that in the error log but seems there isn't. Will investigate this. Any hints though?

Regarding the ROI: the thing is the original software most probably doesn't discard the out of ROI pixels. ie it doesn't really crop the image but just calculates new values for the ROI pixels including in the calculations the out of ROI neighbor pixels. It even does this with a ratio which we can change in the ini file but I am not 100% sure it's linear. Hence only full size images would provide reasonable comparison.

Regarding before and after images: if I am getting it correctly that you are even questioning the benefit of the filter itself against simpler normalization filters, this I can test using original software.

@nabobalis
Copy link
Contributor

I'm not sure what to hint about, the CI has all passed bar the install one. That is a known fault and I need to fix it separately.

Yes I guess I'm questing the need for this filter if it's just the same as normalizing it with a simple function. Which makes me suspect that if the original method is "correct", then there is a mistake somewhere in the version here.

@MinaSGorgi MinaSGorgi force-pushed the master branch 6 times, most recently from 97f6b59 to 9a7ce1e Compare May 3, 2019 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Noise Adaptive Fuzzy Equalization method (NAFE)
4 participants