Skip to content

Conversation

@YooSunYoung
Copy link
Member

@YooSunYoung YooSunYoung commented Sep 6, 2024

Normalization method is implemented.

Tests will come from different PR Test included in this PR.

"""
sample_images = _select_images_by_key(image_stacks, ImageKey.SAMPLE)
dark_current = _select_image_by_key(bg_images, ImageKey.DARK_CURRENT)
return CleansedSampleImages(sample_images - dark_current)
Copy link
Member Author

Choose a reason for hiding this comment

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

This part is very slow... something must be wrong here.

Comment on lines 108 to 117
def calculate_d0(background: BackgroundImage) -> D0:
"""Calculate the D0 value from background image stack.
:math:`D0 = mean(background counts of all pixels)`
"""
return D0(sc.mean(background))


def calculate_d(samples: CleansedSampleImages) -> D:
"""Calculate the D value from the sample image stack.
Copy link
Member Author

Choose a reason for hiding this comment

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

This part is so slow too.

@YooSunYoung YooSunYoung force-pushed the normalization branch 2 times, most recently from 84922f8 to dab573f Compare September 13, 2024 13:04
Base automatically changed from ymir-imaging to main September 16, 2024 11:33
@YooSunYoung YooSunYoung force-pushed the normalization branch 2 times, most recently from ca2c9ac to b20ae4f Compare September 16, 2024 12:50
@YooSunYoung YooSunYoung changed the base branch from main to test-data September 18, 2024 07:39
@YooSunYoung YooSunYoung marked this pull request as ready for review September 18, 2024 07:40
@nvaytet nvaytet self-assigned this Sep 19, 2024
OpenBeam = mean(open_beam, 'time')
"""
return OpenBeamImage(sc.mean(open_beam, dim=TIME_COORD_NAME))
Copy link
Member

Choose a reason for hiding this comment

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

Question: Is the time coordinate here time during the measurement, or is it time-of-flight?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's timestamp of each image, when it was taken.
It's an optical 2-d detector so it doesn't have time-of-flight.

Threshold for the background pixel values.
Any pixel values less than ``background_threshold``
are replaced with ``background_threshold``.
Default is 1.0[counts].
Copy link
Member

Choose a reason for hiding this comment

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

Is this done to avoid nans when doing the division in the normalization?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. There is another python project called pymureh and I mostly followed their method,
but I thought it was a bit weird to hard-code the number so I exposed it as an argument.

.. math::
Background = mean(OpenBeam, 'time') - mean(DarkCurrent, 'time')
Copy link
Member

Choose a reason for hiding this comment

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

Can you also add (in words or in the math) the fact that you are applying a threshold after the operation?

.. math::
CleansedSample_{i} = Sample_{i} - mean(DarkCurrent, dim='time')
Copy link
Member

Choose a reason for hiding this comment

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

Same here: also add (in words or in the math) the fact that you are applying a threshold after the operation?

and returned negative values.
"""
return AverageSamplePixelCounts(
_mean_all_dims(sample_images.data) - dark_current.data.mean()
Copy link
Member

Choose a reason for hiding this comment

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

I am wondering if sums should be used instead of means for both the AverageBackgroundPixelCounts and the AverageSamplePixelCounts?

Usually when we normalize by e.g. a monitor, we sum the counts?

Copy link
Member Author

Choose a reason for hiding this comment

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

But we don't take same number of dark-current/open-beam images as the sample images.

Copy link
Member

Choose a reason for hiding this comment

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

Probably best to discuss in person / over a call, as I am wondering if the reduction over the time dimension should also be sums instead of means?

If I understand correctly, it's to make sure everything has been scaled to the same integration time, before performing the division, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

If I understand correctly, it's to make sure everything has been scaled to the same integration time, before performing the division, right?

Yes, that was the idea.

Copy link
Member

@nvaytet nvaytet Sep 19, 2024

Choose a reason for hiding this comment

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

Or maybe it should be more like:

(sample_image.sum('time') / sample_image_integration_time - dark_frame.sum('time') / dark_frame_integration_time) / (openbeam_image.sum('time') / openbeam_image_integration_time - dark_frame.sum('time') / dark_frame_integration_time)

?

Copy link
Member Author

Choose a reason for hiding this comment

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

Number of images is same as the integration_time since the shutter(?) speed is fixed.

Copy link
Member

@nvaytet nvaytet Sep 19, 2024

Choose a reason for hiding this comment

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

Ah so you're saying that doing a mean is essentially doing image.sum(time) / integration_time since you divide by the number of images (there is just a factor of time_shutter_is_open which cancels out everywhere)?

I guess if the shutter speed is adjustable this should be verified?

Are the dark frames and open beam always taken just before the sample is, or is a dark frame sometimes taken weeks in advance and used for several experiments in a row?

If it's the former, then I guess we are fine with means.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah so you're saying that doing a mean is essentially doing image.sum(time) / integration_time since you divide by the number of images (there is just a factor of time_shutter_is_open which cancels out everywhere)?

Yes...! That's what I meant.
But maybe I should check with Robin once more and mention this in the docstring.

I guess if the shutter speed is adjustable this should be verified?

Yes. Totally.

Are the dark frames and open beam always taken just before the sample is, or is a dark frame sometimes taken weeks in advanced and used for several experiments in a row?

They will be taken again every time just before or after the sample images.

Background images(dark-current and open-beam) takes much less time than samples and
Odin team wanted every types of images in one file.

If it's the former, then I guess we are fine with means.

Yes. In either case, I'll update the docstring.

Copy link
Member Author

@YooSunYoung YooSunYoung Sep 19, 2024

Choose a reason for hiding this comment

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

I double checked with Søren and opened an issue as a reminder.

#48

So the exposure time is constant within a single file, but may vary in different files.

And it gives warning in the average/normalization functions about exposure time.

I chose warning over docstring because I think we should use exposure-time as you said,
and it should be avilable in the file.

def calculate_white_beam_background(
open_beam: OpenBeamImage,
dark_current: DarkCurrentImage,
background_threshold: BackgroundPixelThreshold = DEFAULT_BACKGROUND_THRESHOLD,
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 thinking that BackgroundPixelThreshold should not have a default value here, but instead a default param should be set on the workflow/pipeline ?

Copy link
Member Author

Choose a reason for hiding this comment

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

It does have a default param in the workflow/pipeline.

The default value in the function signature is not used by the sciline.Pipeline.

I thought it'll make it easier to use the provider itself, i.e. for testing...

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 say if you want to use it for testing, then it's good that the parameter is required, so that you understand exactly what is being done?

An alternative would be to split it up into 2 providers: one that does the subtraction, and one that applies the threshold afterwards, with a very obvious name like ReplaceZerosAndNegativesWithOne?

Copy link
Member Author

Choose a reason for hiding this comment

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

I would say if you want to use it for testing, then it's good that the parameter is required, so that you understand exactly what is being done?

You can still test it with the default argument value though.

An alternative would be to split it up into 2 providers: one that does the subtraction, and one that applies the threshold afterwards, with a very obvious name like ReplaceZerosAndNegativesWithOne?

I didn't want to hard-code the numbers so the name should be different but
I can split this up into two steps if that makes it more interpretable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Second thoughts... I think rounding up should be done within the normalization step since it's actually where it's needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

@nvaytet I ended up inserting threshold applying steps instead of merging them into the normalization step
since they are used for calculating scale factor as well...

Copy link
Member Author

Choose a reason for hiding this comment

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

But I still don't see why we can't use the default argument value though

Copy link
Member

@nvaytet nvaytet Sep 20, 2024

Choose a reason for hiding this comment

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

I'm saying that because I think in esssans, we don't have default arguments in providers, only default parameters on the workflow (unless I missed something somewhere?).
So I thought we should try to be consistent across techniques.

Copy link
Member Author

Choose a reason for hiding this comment

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

@neil Okay I fixed it accordingly...!

def cleanse_sample_images(
sample_images: SampleImageStacks,
dark_current: DarkCurrentImage,
sample_threshold: SamplePixelThreshold = DEFAULT_SAMPLE_THRESHOLD,
Copy link
Member

Choose a reason for hiding this comment

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

Same as above: set the parameter on the workflow?

SamplePixelThreshold: DEFAULT_SAMPLE_THRESHOLD,
FileLock: DEFAULT_FILE_LOCK,
},
)
Copy link
Member

Choose a reason for hiding this comment

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

Tests will come from different PR

Unless this change is urgent (e.g. for the STAP demo), I would rather see the unit tests as part of the same PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I wanted to release it before Christian leaves for vacation.
But I think I can just test them by myself.... I'll add tests here.

@YooSunYoung YooSunYoung marked this pull request as draft September 19, 2024 15:19
@YooSunYoung YooSunYoung marked this pull request as ready for review September 20, 2024 08:50
Base automatically changed from test-data to main September 20, 2024 11:12
assert isinstance(normalized, sc.DataArray)
assert normalized.sizes['time'] == 2
assert normalized.unit == "dimensionless"
assert_allclose(normalized, expected_normalized_sample_images)
Copy link
Member

Choose a reason for hiding this comment

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

Can you also add some tests on the functions that apply the thresholds on the sample and dark images?

Copy link
Member Author

@YooSunYoung YooSunYoung Sep 25, 2024

Choose a reason for hiding this comment

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

@nvaytet Done...!

YooSunYoung and others added 2 commits September 25, 2024 11:20
Co-authored-by: Neil Vaytet <39047984+nvaytet@users.noreply.github.com>
@YooSunYoung YooSunYoung merged commit 92818cd into main Sep 25, 2024
@YooSunYoung YooSunYoung deleted the normalization branch September 25, 2024 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants