Repository: Roman-Supernova-PIT/MultiSMP
Pull request #65: Renamed background and added a calculate bg function
Author: login: ColeFMeldorf
Created: 2025-05-20T15:09:05Z | Merged: 2025-05-20T19:19:44Z

1. [x] Renamed `background` to `fit_background` for clarity.
2. [x] Pulled all of the background calculation steps into their own function, `calculate_background_level`
3. [x] Added a test for this function.

Passes all tests!


Diff:
diff --git a/ASP_tests.py b/ASP_tests.py
index 6d3f359d..d7b371b5 100644
--- a/ASP_tests.py
+++ b/ASP_tests.py
@@ -185,6 +185,7 @@ def test_regression():
     config['turn_grid_off'] = False
     config['size'] = 19
     config['weighting'] = True
+    config['subtract_background'] = True
     # Weighting is a Gaussian width 1000 when this was made
     # In the future, this should be True, but random seeds not working rn.
     config['source_phot_ops'] = False
@@ -255,4 +256,20 @@ def test_plot_lc():
     assert output[2][0] == 0.3464661982648008
     assert output[3][0] == 23.164154309471726
     assert output[4] == 182.088
-    assert output[5] == 0.0
\ No newline at end of file
+    assert output[5] == 0.0
+
+
+def test_calculate_background_level():
+    from AllASPFuncs import calculate_background_level
+    test_data = np.ones((12, 12))
+    test_data[5:7, 5:7] = 1000
+    test_data[0:2, 0:12:2] = 10
+    test_data[-3:-1, 0:12:2] = 10
+    test_data[0:12:2, 0:2] = 10
+    test_data[0:12:2, -1:-3] = 10 # Adding some noise
+    # Expected output
+    expected_output = 1
+
+    output = calculate_background_level(test_data)
+    msg = f"Expected {expected_output}, but got {output}"
+    assert output == expected_output, msg
\ No newline at end of file
diff --git a/AllASPFuncs.py b/AllASPFuncs.py
index daf7b5a3..826d9c89 100644
--- a/AllASPFuncs.py
+++ b/AllASPFuncs.py
@@ -200,7 +200,6 @@ def generateGuess(imlist, wcslist, ra_grid, dec_grid):
     return all_vals/len(wcslist)
 
 
-
 def construct_psf_background(ra, dec, wcs, x_loc, y_loc, stampsize, bpass,
                              use_roman, color=0.61, psf=None, pixel=False,
                              include_photonOps=False, util_ref=None,
@@ -542,16 +541,18 @@ def gaussian(x, A, mu, sigma):
     return A*np.exp(-(x-mu)**2/(2*sigma**2))
 
 
-def constructImages(exposures, ra, dec, size=7, background=False,
+def constructImages(exposures, ra, dec, size=7, subtract_background=True,
                     roman_path=None, truth='simple_model'):
 
     '''
-    Constructs the array of Roman images in the format required for the linear algebra operations
+    Constructs the array of Roman images in the format required for the linear
+    algebra operations
 
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    subtract_background: If False, the background level is fit as a free
+        parameter in the forward modelling. Otherwise, we subtract it here.
     roman_path: the path to the Roman data
 
     Returns:
@@ -597,35 +598,22 @@ def constructImages(exposures, ra, dec, size=7, background=False,
         im = cutout * zero
         '''
 
-        # If we are not fitting the background we manually subtract it here.
-        if background:
-            # When background is true, we are including the background level as
-            # a free parameter in our fit, so it should not be subtracted here.
-            bg = 0
-        elif not background and not truth == 'truth':
-            # However, if we are not fitting the background, we want to get
-            # rid of it here, either by reading the SKY_MEAN value from the
-            # image header...
-            bg = image_cutout._get_header()['SKY_MEAN']
-        elif not background and truth == 'truth':
-            # ....or manually calculating it!
-            # Maybe repackage all of this stuff into a function? TODO
-            im = imagedata
-            bgarr = np.concatenate((im[0:size//4,0:size//4].flatten(),\
-                                im[0:size,size//4:size].flatten(),\
-                                    im[size//4:size,0:size//4].flatten(),\
-                                        im[size//4:size,size//4:size].flatten()))
-            bgarr = bgarr[bgarr != 0]
-            if len(bgarr) == 0:
-                med = 0
-                bg = 0
-            else:
-                pc = np.percentile(bgarr, 84)
-                med = np.median(bgarr)
-                bgarr = bgarr[bgarr < pc]
-                bg = np.median(bgarr)
-
-        bgflux.append(bg) # This currently isn't returned, but might be a good
+        # If we are not fitting the background we subtract it here.
+        # When subtract_background is False, we are including the background
+        # level as a free parameter in our fit, so it should not be subtracted
+        # here.
+        bg = 0
+        if subtract_background:
+            if not truth == 'truth':
+                # However, if we are subtracting the background, we want to get
+                # rid of it here, either by reading the SKY_MEAN value from the
+                # image header...
+                bg = image_cutout._get_header()['SKY_MEAN']
+            elif truth == 'truth':
+                # ....or manually calculating it!
+                bg = calculate_background_level(imagedata)
+
+        bgflux.append(bg)  # This currently isn't returned, but might be a good
         # thing to put in output? TODO
 
         image_cutout._data -= bg
@@ -639,6 +627,35 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+    For now, we take the corners of the image, sigma clip, and then return
+    the median as the background level.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, 3*(size//4):size].flatten(),
+                            im[3*(size//4):size, 0:size//4].flatten(),
+                            im[3*(size//4):size, 3*(size//4):size].flatten()))
+    if len(bgarr) == 0:
+        bg = 0
+    else:
+        pc = np.percentile(bgarr, 84)
+        bgarr = bgarr[bgarr < pc]
+        bg = np.median(bgarr)
+
+    return bg
+
+
 def getPSF_Image(self,stamp_size,x=None,y=None, x_center = None, y_center= None, pupil_bin=8,sed=None,
                         oversampling_factor=1,include_photonOps=False,n_phot=1e6, pixel = False, flux = 1):
 
@@ -691,7 +708,7 @@ def getPSF_Image(self,stamp_size,x=None,y=None, x_center = None, y_center= None,
     return result
 
 
-def fetchImages(num_total_images, num_detect_images, ID, sn_path, band, size, fit_background,
+def fetchImages(num_total_images, num_detect_images, ID, sn_path, band, size, subtract_background,
                 roman_path, object_type, lc_start=-np.inf, lc_end=np.inf):
     '''
     This function gets the list of exposures to be used for the analysis.
@@ -704,7 +721,8 @@ def fetchImages(num_total_images, num_detect_images, ID, sn_path, band, size, fi
     sn_path: str, the path to the supernova data
     band: str, the band to be used
     size: int, cutout will be of shape (size, size)
-    fit_background: bool, whether to manually fit the background or not
+    subtract_background: If True, subtract sky bg from images. If false, leave
+            bg as a free parameter in the forward modelling.
     roman_path: str, the path to the Roman data
     obj_type: str, the type of object to be used (SN or star)
     lc_start, lc_end: ints, MJD bounds on where to fetch images.
@@ -735,7 +753,8 @@ def fetchImages(num_total_images, num_detect_images, ID, sn_path, band, size, fi
                                  lc_start=lc_start, lc_end=lc_end)
     cutout_image_list, image_list =\
         constructImages(exposures, ra, dec, size=size,
-                        background=fit_background, roman_path=roman_path)
+                        subtract_background=subtract_background,
+                        roman_path=roman_path)
 
     # THIS IS TEMPORARY. In this PR, I am refactoring constructImages to return
     # Image objects. However, the rest of the code is not refactored yet. This
@@ -1476,7 +1495,7 @@ def prep_data_for_fit(images, err, sn_matrix, wgt_matrix):
 
 def run_one_object(ID, object_type, num_total_images, num_detect_images, roman_path,
                    sn_path, size, band, fetch_SED, use_real_images, use_roman,
-                   fit_background, turn_grid_off, adaptive_grid, npoints,
+                   subtract_background, turn_grid_off, adaptive_grid, npoints,
                    make_initial_guess, initial_flux_guess, weighting, method,
                    make_contour_grid, single_grid_point, pixel, source_phot_ops,
                    lc_start, lc_end, do_xshift, bg_gal_flux, do_rotation, airy,
@@ -1504,7 +1523,7 @@ def run_one_object(ID, object_type, num_total_images, num_detect_images, roman_p
             exposures = fetchImages(num_total_images,
                                                  num_detect_images, ID,
                                                  sn_path, band, size,
-                                                 fit_background,
+                                                 subtract_background,
                                                  roman_path,
                                                  object_type,
                                                  lc_start=lc_start,
@@ -1633,7 +1652,7 @@ def run_one_object(ID, object_type, num_total_images, num_detect_images, roman_p
                                                 band=band)
         # TODO comment this
 
-        if fit_background:
+        if not subtract_background:
             for j in range(num_total_images):
                 if i == j:
                     bg = np.ones(size**2).reshape(-1, 1)
@@ -1718,7 +1737,7 @@ def run_one_object(ID, object_type, num_total_images, num_detect_images, roman_p
     if not make_initial_guess:
         x0test = np.zeros(psf_matrix.shape[1])
 
-    if fit_background:
+    if not subtract_background:
         x0test = np.concatenate([x0test, np.zeros(num_total_images)], axis=0)
 
     if method == 'lsqr':
diff --git a/RomanASP.py b/RomanASP.py
index decec0e8..70fbde62 100644
--- a/RomanASP.py
+++ b/RomanASP.py
@@ -140,7 +140,7 @@ def main():
     method = config['method']
     make_initial_guess = config['make_initial_guess']
     adaptive_grid = config['adaptive_grid']
-    fit_background = config['fit_background']
+    subtract_background = config['subtract_background']
     weighting = config['weighting']
     pixel = config['pixel']
     roman_path = config['roman_path']
@@ -182,7 +182,7 @@ def main():
             confusion_metric, X, cutout_wcs_list, sim_lc = \
             run_one_object(ID, object_type, num_total_images, num_detect_images, roman_path,
                            sn_path, size, band, fetch_SED, use_real_images,
-                           use_roman, fit_background, turn_grid_off,
+                           use_roman, subtract_background, turn_grid_off,
                            adaptive_grid, npoints,
                            make_initial_guess, initial_flux_guess,
                            weighting, method, make_contour_grid,
diff --git a/config.yaml b/config.yaml
index 5193c65b..a7ef1424 100644
--- a/config.yaml
+++ b/config.yaml
@@ -8,7 +8,7 @@ deltafcn_profile: false
 do_rotation: true
 do_xshift: true
 fetch_SED: true
-fit_background: false
+subtract_background: true
 initial_flux_guess: 3000
 make_exact: false
 make_initial_guess: true


Reviews:
user: login: wmwv
body: I still am confused about the meaning of `fit_background` in `constructImages`.  I think you've named it based on how it's used in some other function.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:28:06Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:41:36Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:47:05Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:51:27Z
user: login: wmwv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:52:04Z
user: login: wmwv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:53:21Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:53:32Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:54:04Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:56:12Z
user: login: wmwv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:59:38Z
user: login: wmwv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T16:00:58Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T16:01:36Z
user: login: ColeFMeldorf
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T16:04:42Z
user: login: wmwv
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T16:56:43Z

Review comments:
diff_hunk: @@ -255,4 +255,20 @@ def test_plot_lc():
     assert output[2][0] == 0.3464661982648008
     assert output[3][0] == 23.164154309471726
     assert output[4] == 182.088
-    assert output[5] == 0.0
\ No newline at end of file
+    assert output[5] == 0.0
+
+
+def test_calculate_background_level():
+    from AllASPFuncs import calculate_background_level
+    test_data = np.ones((12, 12))
+    test_data[5:7, 5:7] = 1000
+    test_data[0:2, 0:12:2] = 10
+    test_data[-3:-1, 0:12:2] = 10
+    test_data[0:12:2, 0:2] = 10
+    test_data[0:12:2, -1:-3] = 10 # Adding some noise
path: ASP_tests.py
user: login: wmwv
body: Why is this "noise"?
created_at: 2025-05-20T15:13:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
path: AllASPFuncs.py
user: login: wmwv
body: What does this step do?  I don't understand why it's necessary.
created_at: 2025-05-20T15:16:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
path: AllASPFuncs.py
user: login: wmwv
body: Document what the current naive method does to estimate the background level.
created_at: 2025-05-20T15:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
+    bgarr = bgarr[bgarr != 0]
path: AllASPFuncs.py
user: login: wmwv
body: Why is the value of `0` special?
created_at: 2025-05-20T15:18:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: wmwv
body: Think of a name of the variable in the context of the function.

E.g., for me `fit_background` means the opposite of how it's used in this function.

I think you're currently naming this variable based on what happens in a different function.
created_at: 2025-05-20T15:20:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -598,34 +598,20 @@ def constructImages(exposures, ra, dec, size=7, background=False,
         '''
 
         # If we are not fitting the background we manually subtract it here.
-        if background:
-            # When background is true, we are including the background level as
+        if fit_background:
+            # When fit_background is true, we are including the background level as
             # a free parameter in our fit, so it should not be subtracted here.
             bg = 0
-        elif not background and not truth == 'truth':
+        elif not fit_background and not truth == 'truth':
             # However, if we are not fitting the background, we want to get
             # rid of it here, either by reading the SKY_MEAN value from the
             # image header...
             bg = image_cutout._get_header()['SKY_MEAN']
-        elif not background and truth == 'truth':
+        elif not fit_background and truth == 'truth':
             # ....or manually calculating it!
-            # Maybe repackage all of this stuff into a function? TODO
-            im = imagedata
-            bgarr = np.concatenate((im[0:size//4,0:size//4].flatten(),\
-                                im[0:size,size//4:size].flatten(),\
-                                    im[size//4:size,0:size//4].flatten(),\
-                                        im[size//4:size,size//4:size].flatten()))
-            bgarr = bgarr[bgarr != 0]
-            if len(bgarr) == 0:
-                med = 0
-                bg = 0
-            else:
-                pc = np.percentile(bgarr, 84)
-                med = np.median(bgarr)
-                bgarr = bgarr[bgarr < pc]
-                bg = np.median(bgarr)
+            bg = calculate_background_level(imagedata)
path: AllASPFuncs.py
user: login: wmwv
body: "manually"?
created_at: 2025-05-20T15:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -598,34 +598,20 @@ def constructImages(exposures, ra, dec, size=7, background=False,
         '''
 
         # If we are not fitting the background we manually subtract it here.
-        if background:
-            # When background is true, we are including the background level as
+        if fit_background:
+            # When fit_background is true, we are including the background level as
             # a free parameter in our fit, so it should not be subtracted here.
             bg = 0
-        elif not background and not truth == 'truth':
+        elif not fit_background and not truth == 'truth':
path: AllASPFuncs.py
user: login: wmwv
body: 1. I would nest this if, so that the `if truth` cases are just within the `elif not fit_background` block.
2. I would also just skip the `if fit_background:` part and replace that with set `bg = 0`, and then `if not fit_background:`.
created_at: 2025-05-20T15:27:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: To clarify, this is leftover Pedro code. However, it is my understanding that this is taking the corners of the image where there is less likely to be contamination from the supernova in order to determine the background. However, upon closer inspection, it seems like there are typos that means it's selecting most of the image. I believe where it says `size//4:size`, it should read `3*size//4:size`. Would that make sense?
created_at: 2025-05-20T15:41:36Z
author_association: COLLABORATOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: Sorry, I am a little confused because `fit_background` was a name that you suggested to switch it to in the previous PR, which is why I chose it.
However, I see your point about the fact that the background is not being fit currently in this function. However I think if I called it something like `dont_subtract_background`, would that not be even more confusing because the user wouldn't know why we are doing that?
created_at: 2025-05-20T15:47:05Z
author_association: COLLABORATOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
+    bgarr = bgarr[bgarr != 0]
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: Admittedly, I am not sure, it's a Pedro holdover. I'll remove it and see if anything breaks.
created_at: 2025-05-20T15:51:27Z
author_association: COLLABORATOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: wmwv
body: Don't rely on variable name suggestions from a person who's complaining they don't understand what the variable is used for :)  
created_at: 2025-05-20T15:52:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: wmwv
body: Write out the purpose of `constructImages`.  Go ahead, add a paragraph to the docstring.  As you write out that purpose, the meaning and utility of doing background subtraction or not should become more clear.
created_at: 2025-05-20T15:53:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: #61 :
> Also, I think you could more clearly restructure the use of background to be subtract_background. But now that I mention that I wonder if it's `subtract_background` or `fit_background`.

I could make it `subtract_background` instead! That would require inverting all the bools though.
created_at: 2025-05-20T15:53:32Z
author_association: COLLABORATOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
+    bgarr = bgarr[bgarr != 0]
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: Does not seem to matter.
created_at: 2025-05-20T15:54:04Z
author_association: COLLABORATOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: wmwv
body: I think that if you find yourself writing `if not foo:` as the main test (as essentially suggested by another comment I made), then use inverting that to be `if bar:` where `bar = not foo` likely makes sense.
created_at: 2025-05-20T15:55:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -255,4 +255,20 @@ def test_plot_lc():
     assert output[2][0] == 0.3464661982648008
     assert output[3][0] == 23.164154309471726
     assert output[4] == 182.088
-    assert output[5] == 0.0
\ No newline at end of file
+    assert output[5] == 0.0
+
+
+def test_calculate_background_level():
+    from AllASPFuncs import calculate_background_level
+    test_data = np.ones((12, 12))
+    test_data[5:7, 5:7] = 1000
+    test_data[0:2, 0:12:2] = 10
+    test_data[-3:-1, 0:12:2] = 10
+    test_data[0:12:2, 0:2] = 10
+    test_data[0:12:2, -1:-3] = 10 # Adding some noise
path: ASP_tests.py
user: login: ColeFMeldorf
body: Because I am sigma clipping (now explicitly stated in the documentation) if the background is all the same number, all of the array gets cut when I try to cut values greater than 2 sigma above mean, because the mean is 1, 68th percentile is 1, 84th percentile is 1, etc. When that happened it would return nan thanks to no data. Now I am adding some other numbers so that the 84th percentile is > 1, the tens get sigma clipped and the ones are left behind!
created_at: 2025-05-20T15:56:12Z
author_association: COLLABORATOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: wmwv
body: Ah!  It just clicked, I understand what background is supposed to be doing.  Yes, `subtract_background` is the variable name I would suggest.

I think `constructImages` has an opportunity for a future name improvement, but we can leave that for later.
created_at: 2025-05-20T15:56:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
path: AllASPFuncs.py
user: login: wmwv
body: "To clarify, this is leftover Pedro code."
It's all your code now :)
created_at: 2025-05-20T15:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
+    bgarr = bgarr[bgarr != 0]
path: AllASPFuncs.py
user: login: wmwv
body: Write out the explanation for what this function does, and then questions such as what regions to calculate the background from and why 0 is special or not should be come more clear.
created_at: 2025-05-20T15:59:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -255,4 +255,20 @@ def test_plot_lc():
     assert output[2][0] == 0.3464661982648008
     assert output[3][0] == 23.164154309471726
     assert output[4] == 182.088
-    assert output[5] == 0.0
\ No newline at end of file
+    assert output[5] == 0.0
+
+
+def test_calculate_background_level():
+    from AllASPFuncs import calculate_background_level
+    test_data = np.ones((12, 12))
+    test_data[5:7, 5:7] = 1000
+    test_data[0:2, 0:12:2] = 10
+    test_data[-3:-1, 0:12:2] = 10
+    test_data[0:12:2, 0:2] = 10
+    test_data[0:12:2, -1:-3] = 10 # Adding some noise
path: ASP_tests.py
user: login: wmwv
body: Perhaps "outliers" rather than "noise".  To me "noise" naively means some random sample from a normal (gaussian) distribution.  And add the explanation you just wrote to me above to the code.
created_at: 2025-05-20T16:00:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +625,34 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     return cutout_image_list, image_list
 
 
+def calculate_background_level(im):
+    '''
+    A function for naively estimating the background level from a given image.
+    This may be replaced by a more sophisticated function later.
+
+    Inputs:
+    im, numpy array of floats, the image to be used.
+
+    Returns:
+    bg, float, the estimated background level.
+
+    '''
+    size = im.shape[0]
+    bgarr = np.concatenate((im[0:size//4, 0:size//4].flatten(),
+                            im[0:size, size//4:size].flatten(),
+                            im[size//4:size, 0:size//4].flatten(),
+                            im[size//4:size, size//4:size].flatten()))
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: Point taken, a poor workman blames his tools. 
I am 90% sure what I said above is what he meant so I made that change!
created_at: 2025-05-20T16:01:36Z
author_association: COLLABORATOR
diff_hunk: @@ -551,7 +550,8 @@ def constructImages(exposures, ra, dec, size=7, background=False,
     Inputs:
     exposures is a list of exposures from findAllExposures
     ra,dec: the RA and DEC of the SN
-    background: whether to subtract the background from the images
+    fit_background: If True, the background level is fit as a free parameter
path: AllASPFuncs.py
user: login: ColeFMeldorf
body: Sounds good! And point taken re: variable names haha
created_at: 2025-05-20T16:04:42Z
author_association: COLLABORATOR

PR comments:
user: login: ColeFMeldorf
created_at: 2025-05-20T15:57:02Z
body: All comments addressed or clarification requested!
author_association: COLLABORATOR
user: login: ColeFMeldorf
created_at: 2025-05-20T16:19:00Z
body: all comments addressed!
author_association: COLLABORATOR