Uploaded image for project: 'Data Management'
  1. Data Management
  2. DM-9567

Create Donut Fit Pipeline

    Details

    • Type: Story
    • Status: Done
    • Resolution: Done
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None

      Description

      Incorporate various external python scripts that find and fit out-of-focus donut images into a DM stack command line task (and potentially subtasks). I've got a hacky-but-functional version of this on branch u/jemeyers/donutPipeline. Items remaining:

      • Pull out relevant pieces of characterizeImage into donutFit, so that I don't have to forcibly ignore many of its subtasks. This includes reducing the schema to what's actually important.
      • Add quarter rotations.
      • Move obscuration code into afw.cameraGeom and/or obs_* packages.
      • Make maximum Zernike coefficient a list so that complicated fits can be developed progressively (first fit Z4-Z11, then Z4-Z15 using previous results as starting point, then Z4-Z21... etc.)
      • Add debugging. Should display donut, model, and residual.
      • Extract ZFit code from donutFit task.

        Attachments

          Issue Links

            Activity

            Hide
            jmeyers314 Joshua Meyers added a comment - - edited

            Quick update since we're near the end of the current sprint. Here's my todo list from the past couple weeks, with items marked as completed or still pending ( ) :

            add fit parameter ranges to config
            add fit convergence criterion to config
            add an automatic guess for appropriate pupil size and scale
            separate donut selection into a subtask
            remove persistence overrides in favor of updating datasets.yaml.
            extract zernikeFitter from fitDonut
            handle mask planes
            add goodness-of-fit statistics to output catalog
            add fit parameter covariance matrix to output catalog
            pull out relevant portions of characterizeImage into something like characterizeDonutImage.
            complete the documentation
            ( ) add unit tests?
            add donutDriver.py

            One outstanding question I have (that may be pushed to a future ticket):

            • How should we handle the difference between intrafocal and extrafocal donuts? This is currently handled by the config parameter "flip", but that means the intra/extra focal reductions are forced to exist in separate output repositories.
            Show
            jmeyers314 Joshua Meyers added a comment - - edited Quick update since we're near the end of the current sprint. Here's my todo list from the past couple weeks, with items marked as completed or still pending ( ) : add fit parameter ranges to config add fit convergence criterion to config add an automatic guess for appropriate pupil size and scale separate donut selection into a subtask remove persistence overrides in favor of updating datasets.yaml . extract zernikeFitter from fitDonut handle mask planes add goodness-of-fit statistics to output catalog add fit parameter covariance matrix to output catalog pull out relevant portions of characterizeImage into something like characterizeDonutImage . complete the documentation ( ) add unit tests? add donutDriver.py One outstanding question I have (that may be pushed to a future ticket): How should we handle the difference between intrafocal and extrafocal donuts? This is currently handled by the config parameter "flip", but that means the intra/extra focal reductions are forced to exist in separate output repositories.
            Hide
            yusra Yusra AlSayyad added a comment -

            REVIEW:

            Really nice work on creating a new package. I was able to run your example. The code and output is easy to understand. This is all super cool. I have some general comments and more detailed comments before this can become maintained as part of the stack.

            General Comments

            I'm not comfortable with incorporating into the stack the copied/pasted/edited characterizeDonutImageTask. Too much code duplication. Take a look at the PSF estimation stuff in ip_diffim that was copied and pasted from other packages. It still has lots of old, outdated API calls. Its still causing maintainence issues today. There are a coouple options: remove CharaterizeDonutImageTask completely and set the config for CharacterizeImageTask in ProcessDonut. OR If you think you'll need to completely rewrite some of characterize's methods in the future, you can still have a specialized CharacterizeDonutImageTask class that inherits from CharacterizeImage, setting the appropriate defaults like:

            from __future__ import absolute_import, division, print_function
            from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask, CharacterizeImageConfig
             
            __all__ = ["CharacterizeDonutImageConfig", "CharacterizeDonutImageTask"]
             
             
            class CharacterizeDonutImageConfig(CharacterizeImageConfig):
                """!Config for CharacterizeDonutImageTask"""
             
                def setDefaults(self):
                    CharacterizeImageConfig.setDefaults(self)
                    self.doMeasurePsf = False
                    self.psfIterations = 1
                    self.doApCorr = False
                    self.measurement.plugins.names = [
                        "base_PixelFlags",
                        "base_SdssCentroid",
                        "base_SdssShape",
                        "base_GaussianFlux",
                        "base_PsfFlux",
                        "base_CircularApertureFlux",
                        "base_FPPosition",
                    ]
             
                    self.installSimplePsf.width = 61
                    self.installSimplePsf.fwhm = 20.0
                    self.detection.thresholdValue = 1.5
                    self.detection.doTempLocalBackground = False
             
                def validate(self):
            	    if self.doMeasurePsf:
            		    raise("Cannot measure PSFs on out of focus images.")
             
            class CharacterizeDonutImageTask(CharacterizeImageTask):
                ConfigClass = CharacterizeDonutImageConfig
                _DefaultName = "CharacterizeDonutImage"
             
                def __init__(self, schema=None, **kwargs):
                    CharacterizeImageTask.__init__(self, **kwargs)
             
                # if needed at a later date
                # def detectAndMeasurePsf(...
            ```
            

            The defaults in setDefaults are sacred, and no one will touch them. If you're really worried about someone doing something wrong on the commandline, that may be handled with the validate method in the config.

            However, from what I can tell, characterizeDonutImageTask is just a configuration of pipe_tasks/CharacterizeTask. If my understanding is right, I'd much rather see the writing of CharacterizeDonutImageTask when you need to override detectAndMeasurePsf() and not in anticipation of needing to do so.

            I tested this out on your example out of focus visit:

            # ...
            from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask, CharacterizeImageConfig
             
            __all__ = ["ProcessDonutConfig", "ProcessDonutTask"]
             
            class ProcessDonutConfig(pexConfig.Config):
                """Config for ProcessDonut"""
                isr = pexConfig.ConfigurableField(
                    target=IsrTask,
                    doc="""Task to perform instrumental signature removal or load a
                        post-ISR image; ISR consists of:
                        - assemble raw amplifier images into an exposure with image,
                          variance and mask planes
                        - perform bias subtraction, flat fielding, etc.
                        - mask known bad pixels
                        - provide a preliminary WCS
                        """,
                )
                charImage = pexConfig.ConfigurableField(
                    target=CharacterizeImageTask,
                    doc="""Task to characterize a donut exposure:
                        - detect sources, usually at high S/N
                        - estimate the background, which is subtracted from the image and
                          returned as field "background"
                        - interpolate over defects and cosmic rays, updating the image,
                          variance and mask planes
                        """,
                )
                fitDonut = pexConfig.ConfigurableField(
                    target=FitDonutTask,
                    doc="""Task to select and fit donuts:
                        - Selects sources that look like isolated donuts
                        - Fit a wavefront forward model to donut images
                        """,
                )
             
                def setDefaults(self):
                    # Put these EITHER here if using CharacterizeImageTask
                    # or in CharacterizeDonutImageTask if you're creating CharacterizeDonutImageTask
                    self.charImage.doMeasurePsf = False
                    self.charImage.psfIterations = 1
                    self.charImage.doApCorr = False
                    self.charImage.measurement.plugins.names = [
                        "base_PixelFlags",
                        "base_SdssCentroid",
                        "base_SdssShape",
                        "base_GaussianFlux",
                        "base_PsfFlux",
                        "base_CircularApertureFlux",
                        "base_FPPosition",
                    ]
             
                    self.charImage.installSimplePsf.width = 61
                    self.charImage.installSimplePsf.fwhm = 20.0
                    self.charImage.detection.thresholdValue = 1.5
                    self.charImage.detection.doTempLocalBackground = False
             
             
            class ProcessDonutTask(pipeBase.CmdLineTask):
            # ...
            

            I'm not 100% comfortable with processDonut because it is just a configuration of processCcd with an afterburner, but we already talked about that. I think the above is OK.

            Details

            donut/bin.src

            1) Inconsistent copyright stubs. This is a good time to fix it before you incorporate it into the stack. As of writing the the copyright stub in donutDriver.py is the right one. BUT, we're switching to a new system per RFC-45. And a new package is the perfect opportunity to use it. It's been officially approved, and you have the green light to model your headers and license files after: https://github.com/lsst/verify

            From DM-Square Room May 10 2017:

            Jonathan Sick [4:35 PM]
            To stay safe you can do exactly what you've always done. In https://github.com/lsst/verify I've implemented what I think is the correct outcome of RFC-45 (see the LICENSE, COPYRIGHT and boilerplate in Python files)
            So you can try the new method, though someone might complain that this method hasn't been approved yet :slightly_smiling_face:

            Tim Jenness
            [4:37 PM]
            it’s been approved
            it just hasn’t been implemented. So the dev guide does not back you up

            Jonathan Sick [4:37 PM]
            yeah, as in reviewed in the dev guide

            2) donutDriver.py: I would not recommend using anything in pipe_drivers as an example. It was put together quickly for HSC and was incorporated into lsst_distrib quickly without strict adherence to the DM coding style.

            donut/processDonut

            3) Why does the subtask slot have to be called charDonutImage? Even if you decide that you need a CharacterizeDonutImageTask, keeping the same subtask slot name would make it more maintainable.

            charImage = pexConfig.ConfigurableField(
                target=CharacterizeDonutImageTask,
                doc ...
            

            donut/fitDonut

            • Nice work keeping it all < 80 characters
            • Printed "Fit Statistics" could be demoted from INFO to DEBUG. Author's choice.
            • 22: Standard imports from furture are:
              from _future_ import absolute_import, division, print_function
            • 29: My linter does not like that there is a line of code before another set of imports.
            • 90-127: Why put the default in the comments and the in the actual default? Seems redundant to me. I haven't seen that anywhere else in the stack.
              119: What do the centroidRange and fluxRelativeRange constrain? Why ~0 and the other ~1?
            • 202: My linter does not like 8 spaces rather than 4 for hanging indents within function calls. It complains E126 for lines (213, 236, 237, 242, 259, 266, 271, 350, 353)
            • 214: self.bic, chisqr, redchi, success, errorbars are Keys too right? May be clearer and more consistent to call them self.bicKey, self.chisqrKey etc...
            • 252: whitespace around +
            • 284: Could be cleaner if the display stuff were in its own method:

            if display:
            	displayFitter(zFitter, pupil)
            

            • 308: A PipeTask.run() method should return a PipeBase.Struct. Wrap it in a PipeBase.Struct for future maintainabiilty.
            • 320: pupilSize = 2*diam
              wavelength*1e-9/(donutSize.asRadians()). Units? Meters per radian?

            zernikeFitter.py:

            • Extracting pointers to arrays from a MaskedImage and storing them as object variables is un-DMStack-like. (Apologies for being unable to articulate that better) Is it possible to store the MaskedImage and construct the chi-array in the _chi quickly still? It seems like the expensive part there is the evaluating the model, and that getting pointers to arrays would be negligible.
            • 116: Name of the model method: with model as a verb, I'd expect it to model something. Do you mean "evaluate"? or maybe "constructImage"?
            • Same whitespace issues as fitDonut.py. Do you have a linter set up or other automated way to check?

            afw: OK

            obs_base:

            donut:
                persistable: BaseCatalog
                storage: FitsCatalogStorage
                python: lsst.afw.table.BaseCatalog
                tables: raw
                template: ''
            

            You've been using donut as an adjective in all the other dataset names. So, it is now unclear what this datatype is. Is it a schema? A source catalog? Maybe call it donut_src?

            obs_subaru:
            python/lsst/obs/hsc/hscPupil.py"
            66 + telescopeDiameter = 8.2
            keep in the comment # meters?

            Show
            yusra Yusra AlSayyad added a comment - REVIEW: Really nice work on creating a new package. I was able to run your example. The code and output is easy to understand. This is all super cool. I have some general comments and more detailed comments before this can become maintained as part of the stack. General Comments I'm not comfortable with incorporating into the stack the copied/pasted/edited characterizeDonutImageTask . Too much code duplication. Take a look at the PSF estimation stuff in ip_diffim that was copied and pasted from other packages. It still has lots of old, outdated API calls. Its still causing maintainence issues today. There are a coouple options: remove CharaterizeDonutImageTask completely and set the config for CharacterizeImageTask in ProcessDonut . OR If you think you'll need to completely rewrite some of characterize's methods in the future, you can still have a specialized CharacterizeDonutImageTask class that inherits from CharacterizeImage , setting the appropriate defaults like: from __future__ import absolute_import, division, print_function from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask, CharacterizeImageConfig   __all__ = [ "CharacterizeDonutImageConfig" , "CharacterizeDonutImageTask" ]     class CharacterizeDonutImageConfig(CharacterizeImageConfig): """!Config for CharacterizeDonutImageTask"""   def setDefaults( self ): CharacterizeImageConfig.setDefaults( self ) self .doMeasurePsf = False self .psfIterations = 1 self .doApCorr = False self .measurement.plugins.names = [ "base_PixelFlags" , "base_SdssCentroid" , "base_SdssShape" , "base_GaussianFlux" , "base_PsfFlux" , "base_CircularApertureFlux" , "base_FPPosition" , ]   self .installSimplePsf.width = 61 self .installSimplePsf.fwhm = 20.0 self .detection.thresholdValue = 1.5 self .detection.doTempLocalBackground = False   def validate( self ): if self .doMeasurePsf: raise ( "Cannot measure PSFs on out of focus images." )   class CharacterizeDonutImageTask(CharacterizeImageTask): ConfigClass = CharacterizeDonutImageConfig _DefaultName = "CharacterizeDonutImage"   def __init__( self , schema = None , * * kwargs): CharacterizeImageTask.__init__( self , * * kwargs)   # if needed at a later date # def detectAndMeasurePsf(... ``` The defaults in setDefaults are sacred, and no one will touch them. If you're really worried about someone doing something wrong on the commandline, that may be handled with the validate method in the config. However, from what I can tell, characterizeDonutImageTask is just a configuration of pipe_tasks/CharacterizeTask . If my understanding is right, I'd much rather see the writing of CharacterizeDonutImageTask when you need to override detectAndMeasurePsf() and not in anticipation of needing to do so. I tested this out on your example out of focus visit: # ... from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask, CharacterizeImageConfig   __all__ = [ "ProcessDonutConfig" , "ProcessDonutTask" ]   class ProcessDonutConfig(pexConfig.Config): """Config for ProcessDonut""" isr = pexConfig.ConfigurableField( target = IsrTask, doc = """Task to perform instrumental signature removal or load a post-ISR image; ISR consists of: - assemble raw amplifier images into an exposure with image, variance and mask planes - perform bias subtraction, flat fielding, etc. - mask known bad pixels - provide a preliminary WCS """ , ) charImage = pexConfig.ConfigurableField( target = CharacterizeImageTask, doc = """Task to characterize a donut exposure: - detect sources, usually at high S/N - estimate the background, which is subtracted from the image and returned as field "background" - interpolate over defects and cosmic rays, updating the image, variance and mask planes """ , ) fitDonut = pexConfig.ConfigurableField( target = FitDonutTask, doc = """Task to select and fit donuts: - Selects sources that look like isolated donuts - Fit a wavefront forward model to donut images """ , )   def setDefaults( self ): # Put these EITHER here if using CharacterizeImageTask # or in CharacterizeDonutImageTask if you're creating CharacterizeDonutImageTask self .charImage.doMeasurePsf = False self .charImage.psfIterations = 1 self .charImage.doApCorr = False self .charImage.measurement.plugins.names = [ "base_PixelFlags" , "base_SdssCentroid" , "base_SdssShape" , "base_GaussianFlux" , "base_PsfFlux" , "base_CircularApertureFlux" , "base_FPPosition" , ]   self .charImage.installSimplePsf.width = 61 self .charImage.installSimplePsf.fwhm = 20.0 self .charImage.detection.thresholdValue = 1.5 self .charImage.detection.doTempLocalBackground = False     class ProcessDonutTask(pipeBase.CmdLineTask): # ... I'm not 100% comfortable with processDonut because it is just a configuration of processCcd with an afterburner, but we already talked about that. I think the above is OK. Details donut/bin.src 1) Inconsistent copyright stubs. This is a good time to fix it before you incorporate it into the stack. As of writing the the copyright stub in donutDriver.py is the right one. BUT, we're switching to a new system per RFC-45 . And a new package is the perfect opportunity to use it. It's been officially approved, and you have the green light to model your headers and license files after: https://github.com/lsst/verify From DM-Square Room May 10 2017: Jonathan Sick [4:35 PM] To stay safe you can do exactly what you've always done. In https://github.com/lsst/verify I've implemented what I think is the correct outcome of RFC-45 (see the LICENSE, COPYRIGHT and boilerplate in Python files) So you can try the new method, though someone might complain that this method hasn't been approved yet :slightly_smiling_face: Tim Jenness [4:37 PM] it’s been approved it just hasn’t been implemented. So the dev guide does not back you up Jonathan Sick [4:37 PM] yeah, as in reviewed in the dev guide 2) donutDriver.py : I would not recommend using anything in pipe_drivers as an example. It was put together quickly for HSC and was incorporated into lsst_distrib quickly without strict adherence to the DM coding style. donut/processDonut 3) Why does the subtask slot have to be called charDonutImage ? Even if you decide that you need a CharacterizeDonutImageTask , keeping the same subtask slot name would make it more maintainable. charImage = pexConfig.ConfigurableField( target=CharacterizeDonutImageTask, doc ... donut/fitDonut Nice work keeping it all < 80 characters Printed "Fit Statistics" could be demoted from INFO to DEBUG. Author's choice. 22: Standard imports from furture are: from _ future _ import absolute_import, division, print_function 29: My linter does not like that there is a line of code before another set of imports. 44+ Our config definitions use spaces between assignment operators (which conflicts with PEP8) per: https://developer.lsst.io/coding/python_style_guide.html#keyword-assignment-operators-should-be-surrounded-by-a-space-when-statements-appear-on-multiple-lines 90-127: Why put the default in the comments and the in the actual default? Seems redundant to me. I haven't seen that anywhere else in the stack. 119: What do the centroidRange and fluxRelativeRange constrain? Why ~0 and the other ~1? 194: Standards say spaces around + and -, but no spaces around *. Check this throughout donut. https://developer.lsst.io/coding/python_style_guide.html#binary-operators-should-be-surrounded-by-a-single-space-except-for 202: My linter does not like 8 spaces rather than 4 for hanging indents within function calls. It complains E126 for lines (213, 236, 237, 242, 259, 266, 271, 350, 353) 214: self.bic, chisqr, redchi, success, errorbars are Keys too right? May be clearer and more consistent to call them self.bicKey, self.chisqrKey etc... 252: whitespace around + 284: Could be cleaner if the display stuff were in its own method: if display: displayFitter(zFitter, pupil) 308: A PipeTask.run() method should return a PipeBase.Struct . Wrap it in a PipeBase.Struct for future maintainabiilty. 320: pupilSize = 2*diam wavelength*1e-9/(donutSize.asRadians()). Units? Meters per radian? zernikeFitter.py : Extracting pointers to arrays from a MaskedImage and storing them as object variables is un-DMStack-like. (Apologies for being unable to articulate that better) Is it possible to store the MaskedImage and construct the chi-array in the _chi quickly still? It seems like the expensive part there is the evaluating the model, and that getting pointers to arrays would be negligible. 116: Name of the model method: with model as a verb, I'd expect it to model something. Do you mean "evaluate"? or maybe "constructImage"? Same whitespace issues as fitDonut.py . Do you have a linter set up or other automated way to check? afw: OK obs_base: donut: persistable: BaseCatalog storage: FitsCatalogStorage python: lsst.afw.table.BaseCatalog tables: raw template: '' You've been using donut as an adjective in all the other dataset names. So, it is now unclear what this datatype is. Is it a schema? A source catalog? Maybe call it donut_src ? obs_subaru: python/lsst/obs/hsc/hscPupil.py" 66 + telescopeDiameter = 8.2 keep in the comment # meters ?
            Hide
            jmeyers314 Joshua Meyers added a comment -

            Thanks for the careful read Yusra AlSayyad. Here are a few quick responses:

            I agree with you discomfort regarding CharacterizeDonutImageTask. As I was copy/paste/editing, it started feeling less and less like a good idea, but I was reluctant to throw it away without someone else taking a look first. So thanks for this critique, and for the thoughtful suggestions for improvement. I'll get on that.

            Regarding not taking examples from pipe_drivers.py - are you suggesting to remove donutDriver.py? I'd be sad to eliminate the ability to (easily) parallelize over cluster nodes. Is there another way to do this besides mimicking pipe drivers?

            And sorry for all the lack of linting. I actually do use a linter, believe it or not, but I've disabled most of the warnings b/c they produce a ton of warnings on some other projects I work on with less prescribed coding conventions. I'll see if I can figure out how to set different warnings in different environments.

            Show
            jmeyers314 Joshua Meyers added a comment - Thanks for the careful read Yusra AlSayyad . Here are a few quick responses: I agree with you discomfort regarding CharacterizeDonutImageTask. As I was copy/paste/editing, it started feeling less and less like a good idea, but I was reluctant to throw it away without someone else taking a look first. So thanks for this critique, and for the thoughtful suggestions for improvement. I'll get on that. Regarding not taking examples from pipe_drivers.py - are you suggesting to remove donutDriver.py ? I'd be sad to eliminate the ability to (easily) parallelize over cluster nodes. Is there another way to do this besides mimicking pipe drivers? And sorry for all the lack of linting. I actually do use a linter, believe it or not, but I've disabled most of the warnings b/c they produce a ton of warnings on some other projects I work on with less prescribed coding conventions. I'll see if I can figure out how to set different warnings in different environments.
            Hide
            yusra Yusra AlSayyad added a comment -

            I'm not suggesting removing donutDriver. I had wondered why the spacing, header and formatting was different from the other bin/ scripts, and noticed it was because it was copied and pasted from pipe_drivers. I wanted to point it out because when I first started on the project, I didn't know which packages could be used as examples and which ones couldn't.

            Show
            yusra Yusra AlSayyad added a comment - I'm not suggesting removing donutDriver. I had wondered why the spacing, header and formatting was different from the other bin/ scripts, and noticed it was because it was copied and pasted from pipe_drivers. I wanted to point it out because when I first started on the project, I didn't know which packages could be used as examples and which ones couldn't.
            Hide
            jmeyers314 Joshua Meyers added a comment -

            Yusra AlSayyad, I think I've now addressed all of your comments. Please take a look at the updated PRs and updated master branch of donut. (Note that for donut, since it still lives under lsst-dm and not lsst, I was lazy and simply made "fix it" commits rather than change old commits).

            A few comments relating to changes:

            What do the centroidRange and fluxRelativeRange constrain? Why ~0 and the other ~1?

            I updated the doc for these to be more explicit:

                centroidRange = pexConfig.ListField(
                    dtype = float,
                    default = [-2.0, 2.0],
                    doc = "Fitting range for donut centroid in pixels with respect to "
                          "stamp center"
                )
                fluxRelativeRange = pexConfig.ListField(
                    dtype = float,
                    default = [0.8, 1.2],
                    doc = "Relative fitting range for donut flux with respect to stamp "
                          "pixel sum"
                )
            

            My linter does not like 8 spaces rather than 4 for hanging indents within function calls.

            I like to use 8 spaces for line-continuations instead of 4 to distinguish them from scope changes. I guess PEP8 says I'm wrong though, so I switched these.

            Show
            jmeyers314 Joshua Meyers added a comment - Yusra AlSayyad , I think I've now addressed all of your comments. Please take a look at the updated PRs and updated master branch of donut. (Note that for donut, since it still lives under lsst-dm and not lsst, I was lazy and simply made "fix it" commits rather than change old commits). A few comments relating to changes: What do the centroidRange and fluxRelativeRange constrain? Why ~0 and the other ~1? I updated the doc for these to be more explicit: centroidRange = pexConfig.ListField( dtype = float , default = [- 2.0 , 2.0 ], doc = "Fitting range for donut centroid in pixels with respect to " "stamp center" ) fluxRelativeRange = pexConfig.ListField( dtype = float , default = [ 0.8 , 1.2 ], doc = "Relative fitting range for donut flux with respect to stamp " "pixel sum" ) My linter does not like 8 spaces rather than 4 for hanging indents within function calls. I like to use 8 spaces for line-continuations instead of 4 to distinguish them from scope changes. I guess PEP8 says I'm wrong though, so I switched these.
            Hide
            yusra Yusra AlSayyad added a comment -

            Nice work.

            I hope I didn't steer you wrong with the E251 I struggle to keep track on the status of the transitioning standards. Sounds like there will be an RFC to drop this from the python style guide at some point.

            Show
            yusra Yusra AlSayyad added a comment - Nice work. I hope I didn't steer you wrong with the E251 I struggle to keep track on the status of the transitioning standards. Sounds like there will be an RFC to drop this from the python style guide at some point.

              People

              • Assignee:
                jmeyers314 Joshua Meyers
                Reporter:
                jmeyers314 Joshua Meyers
                Reviewers:
                Yusra AlSayyad
                Watchers:
                Gregory Dubois-Felsmann, Joshua Meyers, Yusra AlSayyad
              • Votes:
                0 Vote for this issue
                Watchers:
                3 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:

                  Summary Panel