Skip to content

Conversation

17-sugiyama
Copy link
Contributor

This PR modifies the scripts to enable fancy counter 1/f filter and common mode subtraction.

The changes for counter 1/f filter:
The filter function 'counter_1_over_f' is modified to accept the array-like input of parameters.
This enables to apply independent 1/f parameters to individual detectors.

The changes for common mode subtraction:
The PCAFilter is modified to have signal and model_signal independently.
This enables to estimate PCA model from the down sampled signal, and then subtract it from the signal.
For the fancy operations, I introduced get_common_noise_params to estimate knee frequency of the common noise.
The estimated knee frequency is applied to the down sample frequency of model_signal .
However, this deployment requires many changes in fft_ops.
(As common mode aman doesn't have 'dets' LabelAxis, aman.dets is changed to aman[LabelAxis].)
These changes can be discarded if they are too much.

The following lines are the example of the process pipeline after the demodulation.
I have confirmed the processes run with this config.

process_pipe:
    # correct boresight rotation to zero
    - name : "rotate_focal_plane"
      process:
        hwp: True

    # telescope coordinate
    - name : "rotate_qu"
      process:
        sign: 1 
        offset: 0 
        update_focal_plane: True

    # radial coordinate
    - name : "rotate_qu"
      process:
        sign: 1 
        offset: 0 
        radial: True
        update_focal_plane: True

    # calculate psd
    - name: "psd"
      skip_on_sim: False
      signal: "demodQ"
      wrap: "psdQ"
      calc:
        max_samples: 524288 #2**19
        merge: False
        nperseg: 65536 #2**16
        noverlap: 0
        full_output: True
      save: True

    # fit psd
    - name: "noise"
      skip_on_sim: False
      psd: "psdQ"
      fit: True
      calc:
        fwhite: [1, 2]
        f_max: 2
        merge_name: 'noise_fit_statsQ'
      save:
        wrap_name: "noise_fitQ"

    # apply counter 1/f
    - name: "fourier_filter"
      skip_on_sim: False
      signal_name: "demodQ"
      wrap_name: "demodQ"
      process:
        filt_function: "counter_1_over_f"
        filter_params:
          noise_fit_stats: "noise_fit_statsQ"
          
    # get common mode and its fknee
    - name: "get_common_mode"
      noise_fit: True
      f_max: 2
      wrap_name: "demodQ_commonmode"
      calc:
        signal: "demodQ"
        method: "median"
      save: True

    # get signal downsampled to fknee
    - name: "fourier_filter"
      signal_name: "demodQ"
      wrap_name: "lpf_demodQ"
      process:
        noise_fit_array: "demodQ_commonmode"
        filters:
          - name: "low_pass_butter4"

    # estimate and subtract common noise estimated from 
    # the down sampled signal
    - name: "pca_filter"
      signal: "demodQ"
      model_signal: "lpf_demodQ"
      process:
        n_modes: 3

@17-sugiyama
Copy link
Contributor Author

I guess it would be better to modify fft_ops.build_hpf_params_dict following the changes in counter_1_over_f filter, but I haven't changed this yet.

Copy link
Contributor

@msilvafe msilvafe left a comment

Choose a reason for hiding this comment

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

Just a few requests and questions inline

overwrite=True,
subscan=False,
full_output=False,
LabelAxis='dets',
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to add a description for this new LabelAxis variable in the docstring. Also do not use capitals in the function arguments that's reserved for class definitions.

Comment on lines 1218 to 1220
Signal sized nsamps or ndets x nsamps.
When signal is given in 2D shape, the common mode across detectors
is calculated with median method.
Copy link
Contributor

Choose a reason for hiding this comment

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

It's also valid to pass in a precomputed common mode signal with shape 1 x nsamps. That option should be explained here in the docstring.

if np.isscalar(fk) and np.isscalar(n):
return 1/(1+(fk/freqs)**n)

if fk is None and n is None and noise_fit_stats is not None:
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 if you should raise an error if all 3 (fk, n and noise_fit_stats) are None at this point in the code and describe this combination of inputs in the docstring.

E, R = np.linalg.eigh(cov)
E[np.isnan(E)] = 0.
E, R = E.real, R.real
if np.isrealobj(signal):
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you add this isrealobj check? What issue were you running into?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't make any issue as long as the input signal is real object.
I thought that we need this kind of change if we want to derive the common mode of Q+iU, for example.
But I didn't implement an option for the complex common mode derivation in the pipeline so far.

Comment on lines +1985 to +1989
if self.noise_fit:
common_aman = tod_ops.fft_ops.get_common_noise_params(aman, signal=common_mode,
f_max=self.f_max)
samps = core.OffsetAxis('samps', aman.samps.count)
common_aman.wrap(self.wrap_name, common_mode, [(0, samps)])
Copy link
Contributor

Choose a reason for hiding this comment

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

This noise_fit option should be described better in the docstring here.

@msilvafe
Copy link
Contributor

In the following places, here:

The changes for common mode subtraction: The PCAFilter is modified to have signal and model_signal independently. This enables to estimate PCA model from the down sampled signal

and here:

# get signal downsampled to fknee
- name: "fourier_filter"
  signal_name: "demodQ"
  wrap_name: "lpf_demodQ"
  process:
    noise_fit_array: "demodQ_commonmode"
    filters:
      - name: "low_pass_butter4"

You mention downsampling but that's not implemented here do you just mean demodulated and/or low pass filtered?

@msilvafe
Copy link
Contributor

Also why do you need these 3 different rotation steps, especially for telescope vs radial Q/U rotation shouldn't it be one or the other not both?

# correct boresight rotation to zero
- name : "rotate_focal_plane"
  process:
    hwp: True

# telescope coordinate
- name : "rotate_qu"
  process:
    sign: 1 
    offset: 0 
    update_focal_plane: True

# radial coordinate
- name : "rotate_qu"
  process:
    sign: 1 
    offset: 0 
    radial: True
    update_focal_plane: True

@17-sugiyama
Copy link
Contributor Author

Also why do you need these 3 different rotation steps, especially for telescope vs radial Q/U rotation shouldn't it be one or the other not both?

These steps operate,

  • Rotate the focal plane by the boresight angle, and then set boresight value to zero
  • Rotate the detectors to the telescope coordinate (horizontal, vertical angle is Q)
  • Rotate the detectors to the radial coordinate (radial, tangential is Q)

respectively.
This function coords.demod.rotate_demodQU cannot convert the detector coordinate to the radial coordinate at a time.
I can modify pipeline.py to apply multiple rotations like the Fourier filter function.

@17-sugiyama
Copy link
Contributor Author

You mention downsampling but that's not implemented here do you just mean demodulated and/or low pass filtered?

I'm sorry! Exactly, downsample is a mistake for low pass filtering.

@17-sugiyama 17-sugiyama requested a review from msilvafe October 4, 2025 03:01
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.

2 participants